mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-15 13:16:43 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2965ed0b1 | |||
| 5113f503d1 | |||
| a5f1c2bcb0 | |||
| 2e432c9d17 | |||
| 6637810fe6 | |||
| 8118557c17 | |||
| 1925726b96 | |||
| acf7deea95 | |||
| 84fdbbaaa1 | |||
| 638663b28e | |||
| df838a57fd | |||
| b2b73ecb62 |
+18
-2
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
NUM_JOBS=$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)
|
||||
@@ -29,6 +29,7 @@ Options:
|
||||
-b, --build-type <TYPE> Build type (variable: TYPE)
|
||||
Valid values are: Release, RelWithDebInfo, Debug
|
||||
Default: Debug
|
||||
-n, --nightly Create a nightly build.
|
||||
|
||||
Extra arguments are passed to CMake (e.g. -DCMAKE_OPTION_NAME=VALUE)
|
||||
Set the CCACHE variable to "true" to enable build caching.
|
||||
@@ -61,6 +62,7 @@ while true; do
|
||||
-r|--release) DEVEL=false ;;
|
||||
-t|--target) target "$2"; shift ;;
|
||||
-b|--build-type) type "$2"; shift ;;
|
||||
-n|--nightly) NIGHTLY=true ;;
|
||||
-h|--help) usage ;;
|
||||
*) break ;;
|
||||
esac
|
||||
@@ -101,7 +103,20 @@ cd src/android
|
||||
chmod +x ./gradlew
|
||||
|
||||
set -- "$@" -DUSE_CCACHE="${CCACHE}"
|
||||
[ "$DEVEL" != "true" ] && set -- "$@" -DENABLE_UPDATE_CHECKER=ON
|
||||
|
||||
nightly() {
|
||||
[ "$NIGHTLY" = "true" ]
|
||||
}
|
||||
|
||||
if nightly || [ "$DEVEL" != "true" ]; then
|
||||
set -- "$@" -DENABLE_UPDATE_CHECKER=ON
|
||||
fi
|
||||
|
||||
if nightly; then
|
||||
NIGHTLY=true
|
||||
else
|
||||
NIGHTLY=false
|
||||
fi
|
||||
|
||||
echo "-- building..."
|
||||
|
||||
@@ -110,6 +125,7 @@ echo "-- building..."
|
||||
-Dorg.gradle.parallel="${CCACHE}" \
|
||||
-Dorg.gradle.workers.max="${NUM_JOBS}" \
|
||||
-PYUZU_ANDROID_ARGS="$*" \
|
||||
-Pnightly="$NIGHTLY" \
|
||||
--info
|
||||
|
||||
if [ -n "${ANDROID_KEYSTORE_B64}" ]; then
|
||||
|
||||
@@ -227,6 +227,8 @@ option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android"
|
||||
|
||||
option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.g. Snapdragon 865) at the cost of performance" OFF)
|
||||
|
||||
option(NIGHTLY_BUILD "Use Nightly qualifiers in the update checker and build metadata" OFF)
|
||||
|
||||
cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF)
|
||||
cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project
|
||||
@@ -15,27 +15,40 @@ endfunction()
|
||||
get_timestamp(BUILD_DATE)
|
||||
|
||||
if (DEFINED GIT_RELEASE)
|
||||
set(BUILD_VERSION "${GIT_TAG}")
|
||||
set(GIT_REFSPEC "${GIT_RELEASE}")
|
||||
set(IS_DEV_BUILD false)
|
||||
set(BUILD_VERSION "${GIT_TAG}")
|
||||
set(GIT_REFSPEC "${GIT_RELEASE}")
|
||||
set(IS_DEV_BUILD false)
|
||||
else()
|
||||
string(SUBSTRING ${GIT_COMMIT} 0 10 BUILD_VERSION)
|
||||
set(BUILD_VERSION "${BUILD_VERSION}-${GIT_REFSPEC}")
|
||||
set(IS_DEV_BUILD true)
|
||||
string(SUBSTRING ${GIT_COMMIT} 0 10 BUILD_VERSION)
|
||||
set(BUILD_VERSION "${BUILD_VERSION}-${GIT_REFSPEC}")
|
||||
set(IS_DEV_BUILD true)
|
||||
endif()
|
||||
|
||||
if (NIGHTLY_BUILD)
|
||||
set(IS_NIGHTLY_BUILD true)
|
||||
else()
|
||||
set(IS_NIGHTLY_BUILD false)
|
||||
endif()
|
||||
|
||||
set(GIT_DESC ${BUILD_VERSION})
|
||||
|
||||
# Generate cpp with Git revision from template
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
|
||||
set(REPO_NAME "Eden")
|
||||
set(BUILD_ID ${GIT_REFSPEC})
|
||||
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
|
||||
set(CXX_COMPILER "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
|
||||
# Auto-updater metadata! Must somewhat mirror GitHub API endpoint
|
||||
set(BUILD_AUTO_UPDATE_WEBSITE "https://github.com")
|
||||
set(BUILD_AUTO_UPDATE_API "http://api.github.com")
|
||||
set(BUILD_AUTO_UPDATE_REPO "eden-emulator/Releases")
|
||||
|
||||
if (NIGHTLY_BUILD)
|
||||
set(BUILD_AUTO_UPDATE_REPO "Eden-CI/Nightly")
|
||||
set(REPO_NAME "Eden Nightly")
|
||||
else()
|
||||
set(BUILD_AUTO_UPDATE_REPO "eden-emulator/Releases")
|
||||
set(REPO_NAME "Eden")
|
||||
endif()
|
||||
|
||||
set(BUILD_ID ${GIT_REFSPEC})
|
||||
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
|
||||
set(CXX_COMPILER "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
|
||||
configure_file(scm_rev.cpp.in scm_rev.cpp @ONLY)
|
||||
|
||||
@@ -20,6 +20,10 @@ if (YUZU_STATIC_BUILD)
|
||||
add_compile_definitions(QT_STATICPLUGIN)
|
||||
endif()
|
||||
|
||||
if (NIGHTLY_BUILD)
|
||||
add_compile_definitions(NIGHTLY_BUILD)
|
||||
endif()
|
||||
|
||||
# Set compilation flags
|
||||
if (MSVC AND NOT CXX_CLANG)
|
||||
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// import android.annotation.SuppressLint
|
||||
import com.android.build.gradle.api.ApplicationVariant
|
||||
import kotlin.collections.setOf
|
||||
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
|
||||
import com.github.triplet.gradle.androidpublisher.ReleaseStatus
|
||||
@@ -37,6 +38,9 @@ android {
|
||||
compileSdkVersion = "android-36"
|
||||
ndkVersion = "28.2.13676358"
|
||||
|
||||
val isNightly =
|
||||
providers.gradleProperty("nightly").orNull?.toBooleanStrictOrNull() ?: false
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
}
|
||||
@@ -71,6 +75,7 @@ android {
|
||||
val extraCMakeArgs =
|
||||
(project.findProperty("YUZU_ANDROID_ARGS") as String?)?.split("\\s+".toRegex())
|
||||
?: emptyList()
|
||||
|
||||
arguments.addAll(
|
||||
listOf(
|
||||
"-DENABLE_QT=0", // Don't use QT
|
||||
@@ -89,6 +94,13 @@ android {
|
||||
)
|
||||
)
|
||||
|
||||
if (isNightly) {
|
||||
arguments.addAll(listOf(
|
||||
"-DENABLE_UPDATE_CHECKER=ON",
|
||||
"-DNIGHTLY_BUILD=ON",
|
||||
))
|
||||
}
|
||||
|
||||
abiFilters("arm64-v8a")
|
||||
}
|
||||
}
|
||||
@@ -125,7 +137,12 @@ android {
|
||||
signingConfigs.getByName("default")
|
||||
}
|
||||
|
||||
manifestPlaceholders += mapOf("appNameSuffix" to "")
|
||||
if (isNightly) {
|
||||
applicationIdSuffix = ".nightly"
|
||||
manifestPlaceholders += mapOf("appNameSuffix" to " Nightly")
|
||||
} else {
|
||||
manifestPlaceholders += mapOf("appNameSuffix" to "")
|
||||
}
|
||||
|
||||
isMinifyEnabled = true
|
||||
isDebuggable = false
|
||||
@@ -239,6 +256,15 @@ android {
|
||||
path = file("${edenDir}/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors.all {
|
||||
val currentName = manifestPlaceholders["appNameBase"] as? String ?: "Eden"
|
||||
val suffix = if (isNightly) " Nightly" else ""
|
||||
|
||||
// apply nightly suffix I/A
|
||||
resValue("string", "app_name_suffixed", "$currentName$suffix")
|
||||
resValue("string", "app_name", "Eden$suffix")
|
||||
}
|
||||
}
|
||||
|
||||
idea {
|
||||
@@ -258,7 +284,7 @@ tasks.register<Delete>("ktlintReset", fun Delete.() {
|
||||
val showFormatHelp = {
|
||||
logger.lifecycle(
|
||||
"If this check fails, please try running \"gradlew ktlintFormat\" for automatic " +
|
||||
"codestyle fixes"
|
||||
"codestyle fixes"
|
||||
)
|
||||
}
|
||||
tasks.getByPath("ktlintKotlinScriptCheck").doFirst { showFormatHelp.invoke() }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -218,7 +218,7 @@ object NativeLibrary {
|
||||
/**
|
||||
* Checks for available updates.
|
||||
*/
|
||||
external fun checkForUpdate(): String?
|
||||
external fun checkForUpdate(): Array<String>?
|
||||
|
||||
/**
|
||||
* Return the URL to the release page
|
||||
@@ -228,13 +228,18 @@ object NativeLibrary {
|
||||
/**
|
||||
* Return the URL to download the APK for the given version
|
||||
*/
|
||||
external fun getUpdateApkUrl(version: String, packageId: String): String
|
||||
external fun getUpdateApkUrl(tag: String, artifact: String, packageId: String): String
|
||||
|
||||
/**
|
||||
* Returns whether the update checker is enabled through CMAKE options.
|
||||
*/
|
||||
external fun isUpdateCheckerEnabled(): Boolean
|
||||
|
||||
/**
|
||||
* Returns whether or not this is a nightly build.
|
||||
*/
|
||||
external fun isNightlyBuild(): Boolean
|
||||
|
||||
/**
|
||||
* Returns the build version generated by CMake (BUILD_VERSION).
|
||||
*/
|
||||
|
||||
+8
-1
@@ -72,7 +72,14 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
USE_LRU_CACHE("use_lru_cache"),
|
||||
|
||||
DONT_SHOW_DRIVER_SHADER_WARNING("dont_show_driver_shader_warning"),
|
||||
ENABLE_OVERLAY("enable_overlay");
|
||||
ENABLE_OVERLAY("enable_overlay"),
|
||||
|
||||
// GPU Logging
|
||||
GPU_LOGGING_ENABLED("gpu_logging_enabled"),
|
||||
GPU_LOG_VULKAN_CALLS("gpu_log_vulkan_calls"),
|
||||
GPU_LOG_SHADER_DUMPS("gpu_log_shader_dumps"),
|
||||
GPU_LOG_MEMORY_TRACKING("gpu_log_memory_tracking"),
|
||||
GPU_LOG_DRIVER_DEBUG("gpu_log_driver_debug");
|
||||
|
||||
|
||||
// external fun isFrameSkippingEnabled(): Boolean
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
@@ -9,7 +9,8 @@ package org.yuzu.yuzu_emu.features.settings.model
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
enum class ByteSetting(override val key: String) : AbstractByteSetting {
|
||||
AUDIO_VOLUME("volume"),;
|
||||
AUDIO_VOLUME("volume"),
|
||||
GPU_LOG_LEVEL("gpu_log_level");
|
||||
|
||||
override fun getByte(needsGlobal: Boolean): Byte = NativeConfig.getByte(key, needsGlobal)
|
||||
|
||||
|
||||
+2
-1
@@ -67,7 +67,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
|
||||
MY_PAGE_APPLET("my_page_applet_mode"),
|
||||
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"),
|
||||
OVERLAY_GRID_SIZE("overlay_grid_size"),
|
||||
DEBUG_KNOBS("debug_knobs")
|
||||
DEBUG_KNOBS("debug_knobs"),
|
||||
GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size")
|
||||
;
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
|
||||
|
||||
+56
@@ -836,6 +836,62 @@ abstract class SettingsItem(
|
||||
)
|
||||
)
|
||||
|
||||
// GPU Logging settings
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.GPU_LOGGING_ENABLED,
|
||||
titleId = R.string.gpu_logging_enabled,
|
||||
descriptionId = R.string.gpu_logging_enabled_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SingleChoiceSetting(
|
||||
ByteSetting.GPU_LOG_LEVEL,
|
||||
titleId = R.string.gpu_log_level,
|
||||
descriptionId = R.string.gpu_log_level_description,
|
||||
choicesId = R.array.gpuLogLevelEntries,
|
||||
valuesId = R.array.gpuLogLevelValues
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.GPU_LOG_VULKAN_CALLS,
|
||||
titleId = R.string.gpu_log_vulkan_calls,
|
||||
descriptionId = R.string.gpu_log_vulkan_calls_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.GPU_LOG_SHADER_DUMPS,
|
||||
titleId = R.string.gpu_log_shader_dumps,
|
||||
descriptionId = R.string.gpu_log_shader_dumps_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.GPU_LOG_MEMORY_TRACKING,
|
||||
titleId = R.string.gpu_log_memory_tracking,
|
||||
descriptionId = R.string.gpu_log_memory_tracking_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.GPU_LOG_DRIVER_DEBUG,
|
||||
titleId = R.string.gpu_log_driver_debug,
|
||||
descriptionId = R.string.gpu_log_driver_debug_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SpinBoxSetting(
|
||||
IntSetting.GPU_LOG_RING_BUFFER_SIZE,
|
||||
titleId = R.string.gpu_log_ring_buffer_size,
|
||||
descriptionId = R.string.gpu_log_ring_buffer_size_description,
|
||||
valueHint = R.string.gpu_log_ring_buffer_size_hint,
|
||||
min = 64,
|
||||
max = 4096
|
||||
)
|
||||
)
|
||||
|
||||
val fastmem = object : AbstractBooleanSetting {
|
||||
override fun getBoolean(needsGlobal: Boolean): Boolean =
|
||||
BooleanSetting.FASTMEM.getBoolean() &&
|
||||
|
||||
+9
@@ -1220,6 +1220,15 @@ class SettingsFragmentPresenter(
|
||||
add(HeaderSetting(R.string.general))
|
||||
|
||||
add(IntSetting.DEBUG_KNOBS.key)
|
||||
|
||||
add(HeaderSetting(R.string.gpu_logging_header))
|
||||
add(BooleanSetting.GPU_LOGGING_ENABLED.key)
|
||||
add(ByteSetting.GPU_LOG_LEVEL.key)
|
||||
add(BooleanSetting.GPU_LOG_VULKAN_CALLS.key)
|
||||
add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key)
|
||||
add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key)
|
||||
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key)
|
||||
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
@@ -222,6 +222,14 @@ class HomeSettingsFragment : Fragment() {
|
||||
{ shareLog() }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.share_gpu_log,
|
||||
R.string.share_gpu_log_description,
|
||||
R.drawable.ic_log,
|
||||
{ shareGpuLog() }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.open_user_folder,
|
||||
@@ -408,6 +416,40 @@ class HomeSettingsFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareGpuLog() {
|
||||
val currentLog = DocumentFile.fromSingleUri(
|
||||
mainActivity,
|
||||
DocumentsContract.buildDocumentUri(
|
||||
DocumentProvider.AUTHORITY,
|
||||
"${DocumentProvider.ROOT_ID}/log/eden_gpu.log"
|
||||
)
|
||||
)!!
|
||||
val oldLog = DocumentFile.fromSingleUri(
|
||||
mainActivity,
|
||||
DocumentsContract.buildDocumentUri(
|
||||
DocumentProvider.AUTHORITY,
|
||||
"${DocumentProvider.ROOT_ID}/log/eden_gpu.log.old.txt"
|
||||
)
|
||||
)!!
|
||||
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
.setDataAndType(currentLog.uri, FileUtil.TEXT_PLAIN)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
if (!Log.gameLaunched && oldLog.exists()) {
|
||||
intent.putExtra(Intent.EXTRA_STREAM, oldLog.uri)
|
||||
startActivity(Intent.createChooser(intent, getText(R.string.share_gpu_log)))
|
||||
} else if (currentLog.exists()) {
|
||||
intent.putExtra(Intent.EXTRA_STREAM, currentLog.uri)
|
||||
startActivity(Intent.createChooser(intent, getText(R.string.share_gpu_log)))
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
getText(R.string.share_gpu_log_missing),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.ui.main
|
||||
@@ -183,18 +183,26 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
val latestVersion = NativeLibrary.checkForUpdate()
|
||||
if (latestVersion != null) {
|
||||
runOnUiThread {
|
||||
showUpdateDialog(latestVersion)
|
||||
val tag: String = latestVersion[0]
|
||||
val name: String = latestVersion[1]
|
||||
showUpdateDialog(tag, name)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun showUpdateDialog(version: String) {
|
||||
private fun showUpdateDialog(tag: String, name: String) {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.update_available)
|
||||
.setMessage(getString(R.string.update_available_description, version))
|
||||
.setMessage(getString(R.string.update_available_description, name))
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
downloadAndInstallUpdate(version)
|
||||
var artifact = tag
|
||||
// Nightly builds have a slightly different format
|
||||
if (NativeLibrary.isNightlyBuild()) {
|
||||
val splitTag = tag.split('.')
|
||||
artifact = splitTag.subList(1, splitTag.size - 1).joinToString(".")
|
||||
}
|
||||
downloadAndInstallUpdate(tag, artifact)
|
||||
}
|
||||
.setNeutralButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
@@ -207,11 +215,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun downloadAndInstallUpdate(version: String) {
|
||||
private fun downloadAndInstallUpdate(version: String, artifact: String) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val packageId = applicationContext.packageName
|
||||
val apkUrl = NativeLibrary.getUpdateApkUrl(version, packageId)
|
||||
val apkFile = File(cacheDir, "update-$version.apk")
|
||||
val apkUrl = NativeLibrary.getUpdateApkUrl(version, artifact, packageId)
|
||||
val apkFile = File(cacheDir, "update-$artifact.apk")
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
showDownloadProgressDialog()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -1595,7 +1595,6 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_updatePowerState(
|
||||
g_has_battery.store(hasBattery, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// return #ifdef ENABLE_UPDATE_CHECKER
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isUpdateCheckerEnabled(
|
||||
JNIEnv* env,
|
||||
jobject obj) {
|
||||
@@ -1606,22 +1605,39 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isUpdateChecker
|
||||
#endif
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isNightlyBuild(
|
||||
JNIEnv* env,
|
||||
jobject obj) {
|
||||
#ifdef NIGHTLY_BUILD
|
||||
return JNI_TRUE;
|
||||
#else
|
||||
return JNI_FALSE;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ENABLE_UPDATE_CHECKER
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
|
||||
|
||||
JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
|
||||
JNIEnv* env,
|
||||
jobject obj) {
|
||||
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != nullptr) ||
|
||||
(strstr(Common::g_build_version, "alpha") != nullptr) ||
|
||||
(strstr(Common::g_build_version, "beta") != nullptr) ||
|
||||
(strstr(Common::g_build_version, "rc") != nullptr));
|
||||
const std::optional<std::string> latest_release_tag =
|
||||
UpdateChecker::GetLatestRelease(is_prerelease);
|
||||
std::optional<UpdateChecker::Update> release = UpdateChecker::GetUpdate();
|
||||
if (!release) return nullptr;
|
||||
|
||||
if (latest_release_tag && latest_release_tag.value() != Common::g_build_version) {
|
||||
return env->NewStringUTF(latest_release_tag.value().c_str());
|
||||
}
|
||||
return nullptr;
|
||||
const std::string tag = release->tag;
|
||||
const std::string name = release->name;
|
||||
|
||||
jobjectArray result = env->NewObjectArray(2, env->FindClass("java/lang/String"), nullptr);
|
||||
|
||||
const jstring jtag = env->NewStringUTF(tag.c_str());
|
||||
const jstring jname = env->NewStringUTF(name.c_str());
|
||||
|
||||
env->SetObjectArrayElement(result, 0, jtag);
|
||||
env->SetObjectArrayElement(result, 1, jname);
|
||||
env->DeleteLocalRef(jtag);
|
||||
env->DeleteLocalRef(jname);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
|
||||
@@ -1640,9 +1656,11 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
|
||||
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
||||
JNIEnv* env,
|
||||
jobject obj,
|
||||
jstring version,
|
||||
jstring tag,
|
||||
jstring artifact,
|
||||
jstring packageId) {
|
||||
const char* version_str = env->GetStringUTFChars(version, nullptr);
|
||||
const char* version_str = env->GetStringUTFChars(tag, nullptr);
|
||||
const char* artifact_str = env->GetStringUTFChars(artifact, nullptr);
|
||||
const char* package_id_str = env->GetStringUTFChars(packageId, nullptr);
|
||||
|
||||
std::string variant;
|
||||
@@ -1653,7 +1671,11 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
||||
} else if (package_id.find("com.miHoYo.Yuanshen") != std::string::npos) {
|
||||
variant = "optimized";
|
||||
} else {
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
variant = "standard";
|
||||
#else
|
||||
variant = "chromeos";
|
||||
#endif
|
||||
}
|
||||
|
||||
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", version_str, variant);
|
||||
@@ -1663,7 +1685,7 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
|
||||
version_str,
|
||||
apk_filename);
|
||||
|
||||
env->ReleaseStringUTFChars(version, version_str);
|
||||
env->ReleaseStringUTFChars(tag, version_str);
|
||||
env->ReleaseStringUTFChars(packageId, package_id_str);
|
||||
return env->NewStringUTF(url.c_str());
|
||||
}
|
||||
|
||||
@@ -631,4 +631,21 @@
|
||||
<item>@string/error_keys_invalid_filename</item>
|
||||
<item>@string/error_keys_failed_init</item>
|
||||
</string-array>
|
||||
|
||||
<!-- GPU Logging Arrays -->
|
||||
<string-array name="gpuLogLevelEntries">
|
||||
<item>Off</item>
|
||||
<item>Errors Only</item>
|
||||
<item>Standard</item>
|
||||
<item>Verbose</item>
|
||||
<item>All</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="gpuLogLevelValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</integer-array>
|
||||
</resources>
|
||||
|
||||
@@ -325,6 +325,9 @@
|
||||
<string name="share_log">Share debug logs</string>
|
||||
<string name="share_log_description">Share Eden\'s log file to debug issues</string>
|
||||
<string name="share_log_missing">No log file found</string>
|
||||
<string name="share_gpu_log">Share GPU logs</string>
|
||||
<string name="share_gpu_log_description">Share Eden\'s GPU log file to debug graphics issues</string>
|
||||
<string name="share_gpu_log_missing">No GPU log file found</string>
|
||||
<string name="install_game_content">Install game content</string>
|
||||
<string name="install_game_content_description">Install game updates or DLC</string>
|
||||
<string name="installing_game_content">Installing content…</string>
|
||||
@@ -552,6 +555,26 @@
|
||||
<string name="flush_by_line">Flush debug logs by line</string>
|
||||
<string name="flush_by_line_description">Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing.</string>
|
||||
|
||||
<!-- GPU Logging strings -->
|
||||
<string name="gpu_logging">GPU Logging</string>
|
||||
<string name="gpu_logging_header">GPU Logging</string>
|
||||
<string name="gpu_logging_enabled">Enable GPU Logging</string>
|
||||
<string name="gpu_logging_enabled_description">Log GPU operations to eden_gpu.log for debugging Adreno drivers</string>
|
||||
<string name="gpu_log_level">Log Level</string>
|
||||
<string name="gpu_log_level_description">Detail level for GPU logs (higher = more detail, more overhead)</string>
|
||||
<string name="gpu_logging_features">Logging Features</string>
|
||||
<string name="gpu_log_vulkan_calls">Log Vulkan API Calls</string>
|
||||
<string name="gpu_log_vulkan_calls_description">Track all Vulkan API calls in ring buffer</string>
|
||||
<string name="gpu_log_shader_dumps">Dump Shaders</string>
|
||||
<string name="gpu_log_shader_dumps_description">Save compiled shader SPIR-V to files</string>
|
||||
<string name="gpu_log_memory_tracking">Track GPU Memory</string>
|
||||
<string name="gpu_log_memory_tracking_description">Monitor GPU memory allocations and deallocations</string>
|
||||
<string name="gpu_log_driver_debug">Driver Debug Info</string>
|
||||
<string name="gpu_log_driver_debug_description">Capture driver-specific debug information (Turnip breadcrumbs, etc.)</string>
|
||||
<string name="gpu_log_ring_buffer_size">Ring Buffer Size</string>
|
||||
<string name="gpu_log_ring_buffer_size_description">Number of recent Vulkan calls to track (default: 512)</string>
|
||||
<string name="gpu_log_ring_buffer_size_hint">64 to 4096 entries</string>
|
||||
|
||||
<string name="general">General</string>
|
||||
|
||||
<!-- Audio settings strings -->
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#define BUILD_AUTO_UPDATE_WEBSITE "@BUILD_AUTO_UPDATE_WEBSITE@"
|
||||
#define BUILD_AUTO_UPDATE_API "@BUILD_AUTO_UPDATE_API@"
|
||||
#define BUILD_AUTO_UPDATE_REPO "@BUILD_AUTO_UPDATE_REPO@"
|
||||
#define IS_NIGHTLY_BUILD @IS_NIGHTLY_BUILD@
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -35,7 +36,9 @@ constexpr const char g_build_id[] = BUILD_ID;
|
||||
constexpr const char g_title_bar_format_idle[] = TITLE_BAR_FORMAT_IDLE;
|
||||
constexpr const char g_title_bar_format_running[] = TITLE_BAR_FORMAT_RUNNING;
|
||||
constexpr const char g_compiler_id[] = COMPILER_ID;
|
||||
|
||||
constexpr const bool g_is_dev_build = IS_DEV_BUILD;
|
||||
constexpr const bool g_is_nightly_build = IS_NIGHTLY_BUILD;
|
||||
|
||||
constexpr const char g_build_auto_update_website[] = BUILD_AUTO_UPDATE_WEBSITE;
|
||||
constexpr const char g_build_auto_update_api[] = BUILD_AUTO_UPDATE_API;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
@@ -20,7 +20,10 @@ extern const char g_title_bar_format_idle[];
|
||||
extern const char g_title_bar_format_running[];
|
||||
extern const char g_shader_cache_version[];
|
||||
extern const char g_compiler_id[];
|
||||
|
||||
extern const bool g_is_dev_build;
|
||||
extern const bool g_is_nightly_build;
|
||||
|
||||
extern const char g_build_auto_update_website[];
|
||||
extern const char g_build_auto_update_api[];
|
||||
extern const char g_build_auto_update_repo[];
|
||||
|
||||
@@ -49,6 +49,7 @@ SWITCHABLE(CpuBackend, true);
|
||||
SWITCHABLE(CpuAccuracy, true);
|
||||
SWITCHABLE(FullscreenMode, true);
|
||||
SWITCHABLE(GpuAccuracy, true);
|
||||
SWITCHABLE(GpuLogLevel, true);
|
||||
SWITCHABLE(Language, true);
|
||||
SWITCHABLE(MemoryLayout, true);
|
||||
SWITCHABLE(NvdecEmulation, false);
|
||||
|
||||
@@ -738,6 +738,18 @@ struct Values {
|
||||
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
|
||||
Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Debugging};
|
||||
|
||||
// GPU Logging
|
||||
Setting<bool> gpu_logging_enabled{linkage, true, "gpu_logging_enabled", Category::Debugging};
|
||||
SwitchableSetting<GpuLogLevel> gpu_log_level{linkage, GpuLogLevel::Standard, "gpu_log_level",
|
||||
Category::Debugging};
|
||||
Setting<bool> gpu_log_vulkan_calls{linkage, true, "gpu_log_vulkan_calls", Category::Debugging};
|
||||
Setting<bool> gpu_log_shader_dumps{linkage, false, "gpu_log_shader_dumps", Category::Debugging};
|
||||
Setting<bool> gpu_log_memory_tracking{linkage, true, "gpu_log_memory_tracking",
|
||||
Category::Debugging};
|
||||
Setting<bool> gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging};
|
||||
Setting<s32> gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size",
|
||||
Category::Debugging};
|
||||
|
||||
SwitchableSetting<u16, true> debug_knobs{linkage,
|
||||
0,
|
||||
0,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project
|
||||
@@ -154,6 +154,7 @@ ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||
|
||||
template <typename Type>
|
||||
inline std::string_view CanonicalizeEnum(Type id) {
|
||||
|
||||
+38
-17
@@ -1,13 +1,15 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "common/error.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/thread.h"
|
||||
#ifdef __APPLE__
|
||||
#include <mach/mach.h>
|
||||
@@ -18,6 +20,8 @@
|
||||
#include "common/string_util.h"
|
||||
#else
|
||||
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
#include <sys/cpuset.h>
|
||||
#include <sys/_cpuset.h>
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
#include <pthread.h>
|
||||
@@ -28,7 +32,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
#define cpu_set_t cpuset_t
|
||||
# define cpu_set_t cpuset_t
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
@@ -77,22 +81,14 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
#ifdef _MSC_VER
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
static auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription");
|
||||
if (pf)
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
if (auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription"); pf)
|
||||
pf(GetCurrentThread(), UTF8ToUTF16W(name).data()); // Windows 10+
|
||||
}
|
||||
|
||||
#else // !MSVC_VER, so must be POSIX threads
|
||||
|
||||
// MinGW with the POSIX threading model does not support pthread_setname_np
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
// See for reference
|
||||
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
|
||||
#ifdef __APPLE__
|
||||
else
|
||||
; // No-op
|
||||
#elif defined(__APPLE__)
|
||||
pthread_setname_np(name);
|
||||
#elif defined(__HAIKU__)
|
||||
rename_thread(find_thread(NULL), name);
|
||||
@@ -112,13 +108,38 @@ void SetCurrentThreadName(const char* name) {
|
||||
pthread_setname_np(pthread_self(), buf);
|
||||
}
|
||||
#elif defined(_WIN32)
|
||||
// mingw stub
|
||||
// MinGW with the POSIX threading model does not support pthread_setname_np
|
||||
// See for reference
|
||||
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
|
||||
(void)name;
|
||||
#else
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id) {
|
||||
ASSERT(core_id < 4);
|
||||
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
|
||||
// run in ANY processor!
|
||||
auto const total_cores = std::thread::hardware_concurrency();
|
||||
if (core_id < total_cores) {
|
||||
#if defined(__ANDROID__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
sched_setaffinity(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
|
||||
#elif defined(_WIN32)
|
||||
DWORD set = 1UL << core_id;
|
||||
SetThreadAffinityMask(GetCurrentThread(), set);
|
||||
#else
|
||||
// No pin functionality implemented
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
@@ -106,7 +106,7 @@ enum class ThreadPriority : u32 {
|
||||
};
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||
|
||||
void SetCurrentThreadName(const char* name);
|
||||
void PinCurrentThreadToPerformanceCore(size_t core_id);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
+11
-12
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/fiber.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_manager.h"
|
||||
@@ -25,11 +26,8 @@ CpuManager::~CpuManager() = default;
|
||||
void CpuManager::Initialize() {
|
||||
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
|
||||
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
|
||||
|
||||
for (std::size_t core = 0; core < num_cores; core++) {
|
||||
core_data[core].host_thread =
|
||||
std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
|
||||
}
|
||||
for (std::size_t core = 0; core < num_cores; core++)
|
||||
core_data[core].host_thread = std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
|
||||
}
|
||||
|
||||
void CpuManager::Shutdown() {
|
||||
@@ -188,14 +186,15 @@ void CpuManager::ShutdownThread() {
|
||||
void CpuManager::RunThread(std::stop_token token, std::size_t core) {
|
||||
/// Initialization
|
||||
system.RegisterCoreThread(core);
|
||||
std::string name;
|
||||
if (is_multicore) {
|
||||
name = "CPUCore_" + std::to_string(core);
|
||||
} else {
|
||||
name = "CPUThread";
|
||||
}
|
||||
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
|
||||
#ifdef __ANDROID__
|
||||
// Aimed specifically for Snapdragon 8 Elite devices
|
||||
// This kills performance on desktop, but boosts perf for UMA devices
|
||||
// like the S8E. Mediatek and Mali likely won't suffer.
|
||||
Common::PinCurrentThreadToPerformanceCore(core);
|
||||
#endif
|
||||
auto& data = core_data[core];
|
||||
data.host_context = Common::Fiber::ThreadToFiber();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -144,7 +144,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
|
||||
{617, nullptr, "UnregisterExternalKey"},
|
||||
{620, nullptr, "SetSdCardEncryptionSeed"},
|
||||
{630, nullptr, "SetSdCardAccessibility"},
|
||||
{631, nullptr, "IsSdCardAccessible"},
|
||||
{631, D<&FSP_SRV::IsSdCardAccessible>, "IsSdCardAccessible"},
|
||||
{640, nullptr, "IsSignedSystemPartitionOnSdCardValid"},
|
||||
{700, nullptr, "OpenAccessFailureResolver"},
|
||||
{701, nullptr, "GetAccessFailureDetectionEvent"},
|
||||
@@ -524,6 +524,14 @@ Result FSP_SRV::OpenDataStorageWithProgramIndex(OutInterface<IStorage> out_inter
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result FSP_SRV::IsSdCardAccessible(Out<bool> out_is_accessible) {
|
||||
LOG_DEBUG(Service_FS, "(STUBBED) called");
|
||||
|
||||
*out_is_accessible = true;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result FSP_SRV::DisableAutoSaveDataCreation() {
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -101,6 +101,7 @@ private:
|
||||
Result OpenPatchDataStorageByCurrentProcess(OutInterface<IStorage> out_interface,
|
||||
FileSys::StorageId storage_id, u64 title_id);
|
||||
Result OpenDataStorageWithProgramIndex(OutInterface<IStorage> out_interface, u8 program_index);
|
||||
Result IsSdCardAccessible(Out<bool> out_is_accessible);
|
||||
Result DisableAutoSaveDataCreation();
|
||||
Result SetGlobalAccessLogMode(AccessLogMode access_log_mode_);
|
||||
Result GetGlobalAccessLogMode(Out<AccessLogMode> out_access_log_mode);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -218,7 +218,7 @@ EmittedBlockInfo EmitArm64(oaknut::CodeGenerator& code, IR::Block block, const E
|
||||
code.l(pass);
|
||||
}
|
||||
|
||||
for (auto iter = block.begin(); iter != block.end(); ++iter) {
|
||||
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) {
|
||||
IR::Inst* inst = &*iter;
|
||||
|
||||
switch (inst->GetOpcode()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -582,11 +582,9 @@ void EmitIR<IR::Opcode::A32BXWritePC>(oaknut::CodeGenerator& code, EmitContext&
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::A32UpdateUpperLocationDescriptor>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst*) {
|
||||
for (auto& inst : ctx.block) {
|
||||
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC) {
|
||||
for (auto& inst : ctx.block.instructions)
|
||||
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC)
|
||||
return;
|
||||
}
|
||||
}
|
||||
EmitSetUpperLocationDescriptor(code, ctx, ctx.block.EndLocation(), ctx.block.Location());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -193,7 +193,6 @@ void RegAlloc::PrepareForCall(std::optional<Argument::copyable_reference> arg0,
|
||||
|
||||
void RegAlloc::DefineAsExisting(IR::Inst* inst, Argument& arg) {
|
||||
defined_insts.insert(inst);
|
||||
|
||||
ASSERT(!ValueLocation(inst));
|
||||
|
||||
if (arg.value.IsImmediate()) {
|
||||
@@ -208,7 +207,6 @@ void RegAlloc::DefineAsExisting(IR::Inst* inst, Argument& arg) {
|
||||
|
||||
void RegAlloc::DefineAsRegister(IR::Inst* inst, oaknut::Reg reg) {
|
||||
defined_insts.insert(inst);
|
||||
|
||||
ASSERT(!ValueLocation(inst));
|
||||
auto& info = reg.is_vector() ? fprs[reg.index()] : gprs[reg.index()];
|
||||
ASSERT(info.IsCompletelyEmpty());
|
||||
@@ -375,7 +373,6 @@ int RegAlloc::RealizeReadImpl(const IR::Value& value) {
|
||||
template<HostLoc::Kind kind>
|
||||
int RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
|
||||
defined_insts.insert(value);
|
||||
|
||||
ASSERT(!ValueLocation(value));
|
||||
|
||||
if constexpr (kind == HostLoc::Kind::Gpr) {
|
||||
@@ -400,7 +397,6 @@ int RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
|
||||
template<HostLoc::Kind kind>
|
||||
int RegAlloc::RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value) {
|
||||
defined_insts.insert(write_value);
|
||||
|
||||
// TODO: Move elimination
|
||||
|
||||
const int write_loc = RealizeWriteImpl<kind>(write_value);
|
||||
@@ -464,7 +460,6 @@ void RegAlloc::SpillFpr(int index) {
|
||||
|
||||
void RegAlloc::ReadWriteFlags(Argument& read, IR::Inst* write) {
|
||||
defined_insts.insert(write);
|
||||
|
||||
const auto current_location = ValueLocation(read.value.GetInst());
|
||||
ASSERT(current_location);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -302,17 +302,12 @@ public:
|
||||
|
||||
private:
|
||||
friend struct Argument;
|
||||
template<typename>
|
||||
friend struct RAReg;
|
||||
template<typename> friend struct RAReg;
|
||||
|
||||
template<HostLoc::Kind kind>
|
||||
int GenerateImmediate(const IR::Value& value);
|
||||
template<HostLoc::Kind kind>
|
||||
int RealizeReadImpl(const IR::Value& value);
|
||||
template<HostLoc::Kind kind>
|
||||
int RealizeWriteImpl(const IR::Inst* value);
|
||||
template<HostLoc::Kind kind>
|
||||
int RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value);
|
||||
template<HostLoc::Kind kind> int GenerateImmediate(const IR::Value& value);
|
||||
template<HostLoc::Kind kind> int RealizeReadImpl(const IR::Value& value);
|
||||
template<HostLoc::Kind kind> int RealizeWriteImpl(const IR::Inst* value);
|
||||
template<HostLoc::Kind kind> int RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value);
|
||||
|
||||
int AllocateRegister(const std::array<HostLocInfo, 32>& regs, const std::vector<int>& order) const;
|
||||
void SpillGpr(int index);
|
||||
@@ -337,7 +332,6 @@ private:
|
||||
std::array<HostLocInfo, SpillCount> spills;
|
||||
|
||||
mutable std::mt19937 rand_gen;
|
||||
|
||||
ankerl::unordered_dense::set<const IR::Inst*> defined_insts;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -15,26 +15,24 @@
|
||||
|
||||
namespace Dynarmic::Backend {
|
||||
|
||||
template<typename ProgramCounterType>
|
||||
void BlockRangeInformation<ProgramCounterType>::AddRange(boost::icl::discrete_interval<ProgramCounterType> range, IR::LocationDescriptor location) {
|
||||
block_ranges.add(std::make_pair(range, std::set<IR::LocationDescriptor>{location}));
|
||||
template<typename P>
|
||||
void BlockRangeInformation<P>::AddRange(boost::icl::discrete_interval<P> range, IR::LocationDescriptor location) {
|
||||
block_ranges.add(std::make_pair(range, ankerl::unordered_dense::set<IR::LocationDescriptor>{location}));
|
||||
}
|
||||
|
||||
template<typename ProgramCounterType>
|
||||
void BlockRangeInformation<ProgramCounterType>::ClearCache() {
|
||||
template<typename P>
|
||||
void BlockRangeInformation<P>::ClearCache() {
|
||||
block_ranges.clear();
|
||||
}
|
||||
|
||||
template<typename ProgramCounterType>
|
||||
ankerl::unordered_dense::set<IR::LocationDescriptor> BlockRangeInformation<ProgramCounterType>::InvalidateRanges(const boost::icl::interval_set<ProgramCounterType>& ranges) {
|
||||
template<typename P>
|
||||
ankerl::unordered_dense::set<IR::LocationDescriptor> BlockRangeInformation<P>::InvalidateRanges(const boost::icl::interval_set<P>& ranges) {
|
||||
ankerl::unordered_dense::set<IR::LocationDescriptor> erase_locations;
|
||||
for (auto invalidate_interval : ranges) {
|
||||
auto pair = block_ranges.equal_range(invalidate_interval);
|
||||
for (auto it = pair.first; it != pair.second; ++it) {
|
||||
for (const auto& descriptor : it->second) {
|
||||
for (auto it = pair.first; it != pair.second; ++it)
|
||||
for (const auto& descriptor : it->second)
|
||||
erase_locations.insert(descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: EFFICIENCY: Remove ranges that are to be erased.
|
||||
return erase_locations;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
@@ -15,15 +18,13 @@
|
||||
|
||||
namespace Dynarmic::Backend {
|
||||
|
||||
template<typename ProgramCounterType>
|
||||
template<typename P>
|
||||
class BlockRangeInformation {
|
||||
public:
|
||||
void AddRange(boost::icl::discrete_interval<ProgramCounterType> range, IR::LocationDescriptor location);
|
||||
void AddRange(boost::icl::discrete_interval<P> range, IR::LocationDescriptor location);
|
||||
void ClearCache();
|
||||
ankerl::unordered_dense::set<IR::LocationDescriptor> InvalidateRanges(const boost::icl::interval_set<ProgramCounterType>& ranges);
|
||||
|
||||
private:
|
||||
boost::icl::interval_map<ProgramCounterType, std::set<IR::LocationDescriptor>> block_ranges;
|
||||
ankerl::unordered_dense::set<IR::LocationDescriptor> InvalidateRanges(const boost::icl::interval_set<P>& ranges);
|
||||
boost::icl::interval_map<P, ankerl::unordered_dense::set<IR::LocationDescriptor>> block_ranges;
|
||||
};
|
||||
|
||||
} // namespace Dynarmic::Backend
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -122,7 +122,7 @@ EmittedBlockInfo EmitRV64(biscuit::Assembler& as, IR::Block block, const EmitCon
|
||||
|
||||
ebi.entry_point = reinterpret_cast<CodePtr>(as.GetCursorPointer());
|
||||
|
||||
for (auto iter = block.begin(); iter != block.end(); ++iter) {
|
||||
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) {
|
||||
IR::Inst* inst = &*iter;
|
||||
|
||||
switch (inst->GetOpcode()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -123,7 +123,7 @@ A32EmitX64::BlockDescriptor A32EmitX64::Emit(IR::Block& block) {
|
||||
|
||||
EmitCondPrelude(ctx);
|
||||
|
||||
for (auto iter = block.begin(); iter != block.end(); ++iter) [[likely]] {
|
||||
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) [[likely]] {
|
||||
auto* inst = &*iter;
|
||||
// Call the relevant Emit* member function.
|
||||
switch (inst->GetOpcode()) {
|
||||
@@ -727,11 +727,9 @@ void A32EmitX64::EmitA32BXWritePC(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32UpdateUpperLocationDescriptor(A32EmitContext& ctx, IR::Inst*) {
|
||||
for (auto& inst : ctx.block) {
|
||||
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC) {
|
||||
for (auto& inst : ctx.block.instructions)
|
||||
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC)
|
||||
return;
|
||||
}
|
||||
}
|
||||
EmitSetUpperLocationDescriptor(ctx.EndLocation(), ctx.Location());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -116,7 +116,7 @@ A64EmitX64::BlockDescriptor A64EmitX64::Emit(IR::Block& block) noexcept {
|
||||
#undef A64OPC
|
||||
};
|
||||
|
||||
for (auto& inst : block) {
|
||||
for (auto& inst : block.instructions) {
|
||||
auto const opcode = inst.GetOpcode();
|
||||
// Call the relevant Emit* member function.
|
||||
switch (opcode) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -38,11 +38,6 @@ EmitContext::EmitContext(RegAlloc& reg_alloc, IR::Block& block)
|
||||
|
||||
EmitContext::~EmitContext() = default;
|
||||
|
||||
void EmitContext::EraseInstruction(IR::Inst* inst) {
|
||||
block.Instructions().erase(inst);
|
||||
inst->ClearArgs();
|
||||
}
|
||||
|
||||
EmitX64::EmitX64(BlockOfCode& code)
|
||||
: code(code) {
|
||||
exception_handler.Register(code);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -56,11 +56,7 @@ using HalfVectorArray = std::array<T, A64FullVectorWidth::value / mcl::bitsizeof
|
||||
struct EmitContext {
|
||||
EmitContext(RegAlloc& reg_alloc, IR::Block& block);
|
||||
virtual ~EmitContext();
|
||||
|
||||
void EraseInstruction(IR::Inst* inst);
|
||||
|
||||
virtual FP::FPCR FPCR(bool fpcr_controlled = true) const = 0;
|
||||
|
||||
virtual bool HasOptimization(OptimizationFlag flag) const = 0;
|
||||
|
||||
RegAlloc& reg_alloc;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -34,14 +34,7 @@ namespace Dynarmic::Backend::X64 {
|
||||
|
||||
using namespace Xbyak::util;
|
||||
|
||||
#define ICODE(NAME) \
|
||||
[&code](auto... args) { \
|
||||
if constexpr (esize == 32) { \
|
||||
code.NAME##d(args...); \
|
||||
} else { \
|
||||
code.NAME##q(args...); \
|
||||
} \
|
||||
}
|
||||
#define ICODE(NAME) [&](auto... args) { if (esize == 32) code.NAME##d(args...); else code.NAME##q(args...); }
|
||||
|
||||
template<typename Function>
|
||||
static void EmitVectorOperation(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, Function fn) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -26,7 +26,7 @@ bool CondCanContinue(const ConditionalState cond_state, const A32::IREmitter& ir
|
||||
return true;
|
||||
|
||||
// TODO: This is more conservative than necessary.
|
||||
return std::all_of(ir.block.begin(), ir.block.end(), [](const IR::Inst& inst) {
|
||||
return std::all_of(ir.block.instructions.begin(), ir.block.instructions.end(), [](const IR::Inst& inst) {
|
||||
return !WritesToCPSR(inst.GetOpcode());
|
||||
});
|
||||
}
|
||||
@@ -66,7 +66,7 @@ bool IsConditionPassed(TranslatorVisitor& v, IR::Cond cond) {
|
||||
|
||||
// non-AL cond
|
||||
|
||||
if (!v.ir.block.empty()) {
|
||||
if (!v.ir.block.instructions.empty()) {
|
||||
// We've already emitted instructions. Quit for now, we'll make a new block here later.
|
||||
v.cond_state = ConditionalState::Break;
|
||||
v.ir.SetTerm(IR::Term::LinkBlockFast{v.ir.current_location});
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
@@ -126,7 +129,7 @@ bool TranslatorVisitor::MRS(Imm<1> o0, Imm<3> op1, Imm<4> CRn, Imm<4> CRm, Imm<3
|
||||
return true;
|
||||
case SystemRegisterEncoding::CNTPCT_EL0:
|
||||
// HACK: Ensure that this is the first instruction in the block it's emitted in, so the cycle count is most up-to-date.
|
||||
if (!ir.block.empty() && !options.wall_clock_cntpct) {
|
||||
if (!ir.block.instructions.empty() && !options.wall_clock_cntpct) {
|
||||
ir.block.CycleCount()--;
|
||||
ir.SetTerm(IR::Term::LinkBlock{*ir.current_location});
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -100,57 +100,39 @@ std::string DumpBlock(const IR::Block& block) noexcept {
|
||||
std::string ret = fmt::format("Block: location={}-{}\n", block.Location(), block.EndLocation())
|
||||
+ fmt::format("cycles={}", block.CycleCount())
|
||||
+ fmt::format(", entry_cond={}", A64::CondToString(block.GetCondition()));
|
||||
if (block.GetCondition() != Cond::AL) {
|
||||
if (block.GetCondition() != Cond::AL)
|
||||
ret += fmt::format(", cond_fail={}", block.ConditionFailedLocation());
|
||||
}
|
||||
ret += '\n';
|
||||
|
||||
const auto arg_to_string = [](const IR::Value& arg) -> std::string {
|
||||
if (arg.IsEmpty()) {
|
||||
return "<null>";
|
||||
} else if (!arg.IsImmediate()) {
|
||||
if (const unsigned name = arg.GetInst()->GetName()) {
|
||||
if (auto const name = arg.GetInst()->GetName())
|
||||
return fmt::format("%{}", name);
|
||||
}
|
||||
return fmt::format("%<unnamed inst {:016x}>", reinterpret_cast<u64>(arg.GetInst()));
|
||||
return fmt::format("%<unnamed inst {:016x}>", u64(arg.GetInst()));
|
||||
}
|
||||
switch (arg.GetType()) {
|
||||
case Type::U1:
|
||||
return fmt::format("#{}", arg.GetU1() ? '1' : '0');
|
||||
case Type::U8:
|
||||
return fmt::format("#{}", arg.GetU8());
|
||||
case Type::U16:
|
||||
return fmt::format("#{:#x}", arg.GetU16());
|
||||
case Type::U32:
|
||||
return fmt::format("#{:#x}", arg.GetU32());
|
||||
case Type::U64:
|
||||
return fmt::format("#{:#x}", arg.GetU64());
|
||||
case Type::U128:
|
||||
return fmt::format("#<u128 imm>");
|
||||
case Type::A32Reg:
|
||||
return A32::RegToString(arg.GetA32RegRef());
|
||||
case Type::A32ExtReg:
|
||||
return A32::ExtRegToString(arg.GetA32ExtRegRef());
|
||||
case Type::A64Reg:
|
||||
return A64::RegToString(arg.GetA64RegRef());
|
||||
case Type::A64Vec:
|
||||
return A64::VecToString(arg.GetA64VecRef());
|
||||
case Type::CoprocInfo:
|
||||
return fmt::format("#<coproc>");
|
||||
case Type::NZCVFlags:
|
||||
return fmt::format("#<NZCV flags>");
|
||||
case Type::Cond:
|
||||
return fmt::format("#<cond={}>", A32::CondToString(arg.GetCond()));
|
||||
case Type::Table:
|
||||
return fmt::format("#<table>");
|
||||
case Type::AccType:
|
||||
return fmt::format("#<acc-type={}>", u32(arg.GetAccType()));
|
||||
default:
|
||||
return fmt::format("<unknown immediate type {}>", arg.GetType());
|
||||
case Type::U1: return fmt::format("#{}", arg.GetU1() ? '1' : '0');
|
||||
case Type::U8: return fmt::format("#{}", arg.GetU8());
|
||||
case Type::U16: return fmt::format("#{:#x}", arg.GetU16());
|
||||
case Type::U32: return fmt::format("#{:#x}", arg.GetU32());
|
||||
case Type::U64: return fmt::format("#{:#x}", arg.GetU64());
|
||||
case Type::U128: return fmt::format("#<u128 imm>");
|
||||
case Type::A32Reg: return A32::RegToString(arg.GetA32RegRef());
|
||||
case Type::A32ExtReg: return A32::ExtRegToString(arg.GetA32ExtRegRef());
|
||||
case Type::A64Reg: return A64::RegToString(arg.GetA64RegRef());
|
||||
case Type::A64Vec: return A64::VecToString(arg.GetA64VecRef());
|
||||
case Type::CoprocInfo: return fmt::format("#<coproc>");
|
||||
case Type::NZCVFlags: return fmt::format("#<NZCV flags>");
|
||||
case Type::Cond: return fmt::format("#<cond={}>", A32::CondToString(arg.GetCond()));
|
||||
case Type::Table: return fmt::format("#<table>");
|
||||
case Type::AccType: return fmt::format("#<acc-type={}>", u32(arg.GetAccType()));
|
||||
default: return fmt::format("<unknown immediate type {}>", arg.GetType());
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& inst : block) {
|
||||
for (const auto& inst : block.instructions) {
|
||||
const Opcode op = inst.GetOpcode();
|
||||
|
||||
ret += fmt::format("[{:016x}] ", reinterpret_cast<u64>(&inst));
|
||||
@@ -180,13 +162,9 @@ std::string DumpBlock(const IR::Block& block) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
ret += fmt::format(" (uses: {})", inst.UseCount());
|
||||
|
||||
ret += '\n';
|
||||
ret += fmt::format(" (uses: {})", inst.UseCount()) + '\n';
|
||||
}
|
||||
|
||||
ret += "terminal = " + TerminalToString(block.GetTerminal()) + '\n';
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "dynarmic/ir/microinstruction.h"
|
||||
#include "dynarmic/ir/terminal.h"
|
||||
#include "dynarmic/ir/value.h"
|
||||
#include "dynarmic/ir/dense_list.h"
|
||||
|
||||
namespace Dynarmic::IR {
|
||||
|
||||
@@ -34,7 +33,7 @@ enum class Opcode;
|
||||
/// Note that this is a linear IR and not a pure tree-based IR: i.e.: there is an ordering to
|
||||
/// the microinstructions. This only matters before chaining is done in order to correctly
|
||||
/// order memory accesses.
|
||||
class Block final {
|
||||
class alignas(4096) Block final {
|
||||
public:
|
||||
//using instruction_list_type = dense_list<Inst>;
|
||||
using instruction_list_type = mcl::intrusive_list<Inst>;
|
||||
@@ -51,37 +50,12 @@ public:
|
||||
Block(Block&&) = default;
|
||||
Block& operator=(Block&&) = default;
|
||||
|
||||
bool empty() const { return instructions.empty(); }
|
||||
size_type size() const { return instructions.size(); }
|
||||
|
||||
Inst& front() { return instructions.front(); }
|
||||
const Inst& front() const { return instructions.front(); }
|
||||
|
||||
Inst& back() { return instructions.back(); }
|
||||
const Inst& back() const { return instructions.back(); }
|
||||
|
||||
iterator begin() { return instructions.begin(); }
|
||||
const_iterator begin() const { return instructions.begin(); }
|
||||
iterator end() { return instructions.end(); }
|
||||
const_iterator end() const { return instructions.end(); }
|
||||
|
||||
reverse_iterator rbegin() { return instructions.rbegin(); }
|
||||
const_reverse_iterator rbegin() const { return instructions.rbegin(); }
|
||||
reverse_iterator rend() { return instructions.rend(); }
|
||||
const_reverse_iterator rend() const { return instructions.rend(); }
|
||||
|
||||
const_iterator cbegin() const { return instructions.cbegin(); }
|
||||
const_iterator cend() const { return instructions.cend(); }
|
||||
|
||||
const_reverse_iterator crbegin() const { return instructions.crbegin(); }
|
||||
const_reverse_iterator crend() const { return instructions.crend(); }
|
||||
|
||||
/// Appends a new instruction to the end of this basic block,
|
||||
/// handling any allocations necessary to do so.
|
||||
/// @param op Opcode representing the instruction to add.
|
||||
/// @param args A sequence of Value instances used as arguments for the instruction.
|
||||
inline void AppendNewInst(const Opcode opcode, const std::initializer_list<IR::Value> args) noexcept {
|
||||
PrependNewInst(end(), opcode, args);
|
||||
inline iterator AppendNewInst(const Opcode opcode, const std::initializer_list<IR::Value> args) noexcept {
|
||||
return PrependNewInst(instructions.end(), opcode, args);
|
||||
}
|
||||
iterator PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args) noexcept;
|
||||
|
||||
@@ -165,9 +139,9 @@ public:
|
||||
inline const size_t& CycleCount() const noexcept {
|
||||
return cycle_count;
|
||||
}
|
||||
private:
|
||||
|
||||
/// "Hot cache" for small blocks so we don't call global allocator
|
||||
boost::container::static_vector<Inst, 14> inlined_inst;
|
||||
boost::container::static_vector<Inst, 30> inlined_inst;
|
||||
/// List of instructions in this block.
|
||||
instruction_list_type instructions;
|
||||
/// "Long/far" memory pool
|
||||
@@ -187,7 +161,7 @@ private:
|
||||
/// Number of cycles this block takes to execute.
|
||||
size_t cycle_count = 0;
|
||||
};
|
||||
static_assert(sizeof(Block) == 2048);
|
||||
static_assert(sizeof(Block) == 4096);
|
||||
|
||||
/// Returns a string representation of the contents of block. Intended for debugging.
|
||||
std::string DumpBlock(const IR::Block& block) noexcept;
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
|
||||
namespace Dynarmic {
|
||||
template<typename T> struct dense_list {
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using size_type = std::size_t;
|
||||
using value_type = T;
|
||||
using pointer = value_type*;
|
||||
using const_pointer = const value_type*;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using iterator = typename std::deque<value_type>::iterator;
|
||||
using const_iterator = typename std::deque<value_type>::const_iterator;
|
||||
using reverse_iterator = typename std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = typename std::reverse_iterator<const_iterator>;
|
||||
|
||||
inline bool empty() const noexcept { return list.empty(); }
|
||||
inline size_type size() const noexcept { return list.size(); }
|
||||
|
||||
inline value_type& front() noexcept { return list.front(); }
|
||||
inline const value_type& front() const noexcept { return list.front(); }
|
||||
|
||||
inline value_type& back() noexcept { return list.back(); }
|
||||
inline const value_type& back() const noexcept { return list.back(); }
|
||||
|
||||
inline iterator begin() noexcept { return list.begin(); }
|
||||
inline const_iterator begin() const noexcept { return list.begin(); }
|
||||
inline iterator end() noexcept { return list.end(); }
|
||||
inline const_iterator end() const noexcept { return list.end(); }
|
||||
|
||||
inline reverse_iterator rbegin() noexcept { return list.rbegin(); }
|
||||
inline const_reverse_iterator rbegin() const noexcept { return list.rbegin(); }
|
||||
inline reverse_iterator rend() noexcept { return list.rend(); }
|
||||
inline const_reverse_iterator rend() const noexcept { return list.rend(); }
|
||||
|
||||
inline const_iterator cbegin() const noexcept { return list.cbegin(); }
|
||||
inline const_iterator cend() const noexcept { return list.cend(); }
|
||||
|
||||
inline const_reverse_iterator crbegin() const noexcept { return list.crbegin(); }
|
||||
inline const_reverse_iterator crend() const noexcept { return list.crend(); }
|
||||
|
||||
inline iterator insert_before(iterator it, value_type& value) noexcept {
|
||||
if (it == list.begin()) {
|
||||
list.push_front(value);
|
||||
return list.begin();
|
||||
}
|
||||
auto const index = std::distance(list.begin(), it - 1);
|
||||
list.insert(it - 1, value);
|
||||
return list.begin() + index;
|
||||
}
|
||||
|
||||
std::deque<value_type> list;
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -65,15 +65,12 @@ enum class MemOp {
|
||||
PREFETCH,
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience class to construct a basic block of the intermediate representation.
|
||||
* `block` is the resulting block.
|
||||
* The user of this class updates `current_location` as appropriate.
|
||||
*/
|
||||
/// @brief Convenience class to construct a basic block of the intermediate representation.
|
||||
/// `block` is the resulting block.
|
||||
/// The user of this class updates `current_location` as appropriate.
|
||||
class IREmitter {
|
||||
public:
|
||||
explicit IREmitter(Block& block)
|
||||
: block(block), insertion_point(block.end()) {}
|
||||
explicit IREmitter(Block& block) : block(block), insertion_point(block.instructions.end()) {}
|
||||
|
||||
Block& block;
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
namespace Dynarmic::Optimization {
|
||||
|
||||
static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
|
||||
for (auto& inst : block) {
|
||||
for (auto& inst : block.instructions) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::Opcode::A32ReadMemory8:
|
||||
case IR::Opcode::A64ReadMemory8: {
|
||||
@@ -131,7 +131,7 @@ static void FlagsPass(IR::Block& block) {
|
||||
|
||||
A32::IREmitter ir{block, A32::LocationDescriptor{block.Location()}, {}};
|
||||
|
||||
for (auto inst = block.rbegin(); inst != block.rend(); ++inst) {
|
||||
for (auto inst = block.instructions.rbegin(); inst != block.instructions.rend(); ++inst) {
|
||||
auto const opcode = inst->GetOpcode();
|
||||
switch (opcode) {
|
||||
case IR::Opcode::A32GetCFlag: {
|
||||
@@ -318,7 +318,7 @@ static void RegisterPass(IR::Block& block) {
|
||||
// Location and version don't matter here.
|
||||
A32::IREmitter ir{block, A32::LocationDescriptor{block.Location()}, {}};
|
||||
|
||||
for (auto inst = block.begin(); inst != block.end(); ++inst) {
|
||||
for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) {
|
||||
auto const opcode = inst->GetOpcode();
|
||||
switch (opcode) {
|
||||
case IR::Opcode::A32GetRegister: {
|
||||
@@ -448,7 +448,7 @@ static void A64CallbackConfigPass(IR::Block& block, const A64::UserConfig& conf)
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& inst : block) {
|
||||
for (auto& inst : block.instructions) {
|
||||
if (inst.GetOpcode() != IR::Opcode::A64DataCacheOperationRaised) {
|
||||
continue;
|
||||
}
|
||||
@@ -541,7 +541,7 @@ static void A64GetSetElimination(IR::Block& block) {
|
||||
do_nothing();
|
||||
};
|
||||
|
||||
for (auto inst = block.begin(); inst != block.end(); ++inst) {
|
||||
for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) {
|
||||
auto const opcode = inst->GetOpcode();
|
||||
switch (opcode) {
|
||||
case IR::Opcode::A64GetW: {
|
||||
@@ -1041,7 +1041,7 @@ static void FoldZeroExtendXToLong(IR::Inst& inst) {
|
||||
}
|
||||
|
||||
static void ConstantPropagation(IR::Block& block) {
|
||||
for (auto& inst : block) {
|
||||
for (auto& inst : block.instructions) {
|
||||
auto const opcode = inst.GetOpcode();
|
||||
switch (opcode) {
|
||||
case Op::LeastSignificantWord:
|
||||
@@ -1221,25 +1221,20 @@ static void ConstantPropagation(IR::Block& block) {
|
||||
static void DeadCodeElimination(IR::Block& block) {
|
||||
// We iterate over the instructions in reverse order.
|
||||
// This is because removing an instruction reduces the number of uses for earlier instructions.
|
||||
for (auto it = block.rbegin(); it != block.rend(); ++it)
|
||||
for (auto it = block.instructions.rbegin(); it != block.instructions.rend(); ++it)
|
||||
if (!it->HasUses() && !MayHaveSideEffects(it->GetOpcode()))
|
||||
it->Invalidate();
|
||||
}
|
||||
|
||||
static void IdentityRemovalPass(IR::Block& block) {
|
||||
boost::container::small_vector<IR::Inst*, 16> to_invalidate;
|
||||
for (auto it = block.begin(); it != block.end();) {
|
||||
const size_t num_args = it->NumArgs();
|
||||
for (size_t i = 0; i < num_args; ++i) {
|
||||
IR::Value arg = it->GetArg(i);
|
||||
if (arg.IsIdentity()) {
|
||||
do {
|
||||
arg = arg.GetInst()->GetArg(0);
|
||||
} while (arg.IsIdentity());
|
||||
boost::container::small_vector<IR::Inst*, 128> to_invalidate;
|
||||
for (auto it = block.instructions.begin(); it != block.instructions.end();) {
|
||||
auto const num_args = it->NumArgs();
|
||||
for (size_t i = 0; i < num_args; ++i)
|
||||
if (IR::Value arg = it->GetArg(i); arg.IsIdentity()) {
|
||||
do arg = arg.GetInst()->GetArg(0); while (arg.IsIdentity());
|
||||
it->SetArg(i, arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (it->GetOpcode() == IR::Opcode::Identity || it->GetOpcode() == IR::Opcode::Void) {
|
||||
to_invalidate.push_back(&*it);
|
||||
it = block.Instructions().erase(it);
|
||||
@@ -1253,7 +1248,7 @@ static void IdentityRemovalPass(IR::Block& block) {
|
||||
|
||||
static void NamingPass(IR::Block& block) {
|
||||
u32 name = 1;
|
||||
for (auto& inst : block)
|
||||
for (auto& inst : block.instructions)
|
||||
inst.SetName(name++);
|
||||
}
|
||||
|
||||
@@ -1402,7 +1397,7 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
|
||||
|
||||
IR::IREmitter ir{block};
|
||||
|
||||
for (auto& inst : block) {
|
||||
for (auto& inst : block.instructions) {
|
||||
ir.SetInsertionPointBefore(&inst);
|
||||
|
||||
switch (inst.GetOpcode()) {
|
||||
@@ -1458,7 +1453,7 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
|
||||
}
|
||||
|
||||
static void VerificationPass(const IR::Block& block) {
|
||||
for (auto const& inst : block) {
|
||||
for (auto const& inst : block.instructions) {
|
||||
for (size_t i = 0; i < inst.NumArgs(); i++) {
|
||||
const IR::Type t1 = inst.GetArg(i).GetType();
|
||||
const IR::Type t2 = IR::GetArgTypeOf(inst.GetOpcode(), i);
|
||||
@@ -1466,7 +1461,7 @@ static void VerificationPass(const IR::Block& block) {
|
||||
}
|
||||
}
|
||||
ankerl::unordered_dense::map<IR::Inst*, size_t> actual_uses;
|
||||
for (auto const& inst : block) {
|
||||
for (auto const& inst : block.instructions) {
|
||||
for (size_t i = 0; i < inst.NumArgs(); i++)
|
||||
if (IR::Value const arg = inst.GetArg(i); !arg.IsImmediate())
|
||||
actual_uses[arg.GetInst()]++;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -93,7 +93,7 @@ bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& ir_inst : block) {
|
||||
for (const auto& ir_inst : block.instructions) {
|
||||
switch (ir_inst.GetOpcode()) {
|
||||
case IR::Opcode::A32ExceptionRaised:
|
||||
case IR::Opcode::A32CallSupervisor:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -45,7 +45,7 @@ static bool ShouldTestInst(u32 instruction, u64 pc, bool is_last_inst) {
|
||||
return false;
|
||||
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal))
|
||||
return false;
|
||||
for (const auto& ir_inst : block) {
|
||||
for (const auto& ir_inst : block.instructions) {
|
||||
switch (ir_inst.GetOpcode()) {
|
||||
case IR::Opcode::A64ExceptionRaised:
|
||||
case IR::Opcode::A64CallSupervisor:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -36,9 +36,9 @@ TEST_CASE("ASIMD Decoder: Ensure table order correctness", "[decode][a32][.]") {
|
||||
|
||||
const auto is_decode_error = [&get_ir](const A32::ASIMDMatcher<A32::TranslatorVisitor>& matcher, u32 instruction) {
|
||||
const auto block = get_ir(matcher, instruction);
|
||||
return std::find_if(block.cbegin(), block.cend(), [](auto const& e) {
|
||||
return std::find_if(block.instructions.cbegin(), block.instructions.cend(), [](auto const& e) {
|
||||
return e.GetOpcode() == IR::Opcode::A32ExceptionRaised && A32::Exception(e.GetArg(1).GetU64()) == A32::Exception::DecodeError;
|
||||
}) != block.cend();
|
||||
}) != block.instructions.cend();
|
||||
};
|
||||
|
||||
for (auto iter = table.cbegin(); iter != table.cend(); ++iter) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -54,7 +54,7 @@ bool ShouldTestInst(IR::Block& block) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& ir_inst : block) {
|
||||
for (const auto& ir_inst : block.instructions) {
|
||||
switch (ir_inst.GetOpcode()) {
|
||||
// A32
|
||||
case IR::Opcode::A32GetFpscr:
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "update_checker.h"
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include <fmt/format.h>
|
||||
#include "update_checker.h"
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
@@ -45,8 +48,7 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
|
||||
};
|
||||
|
||||
client->set_follow_location(true);
|
||||
httplib::Result result;
|
||||
result = client->send(request);
|
||||
httplib::Result result = client->send(request);
|
||||
|
||||
if (!result) {
|
||||
LOG_ERROR(Frontend, "GET to {}{} returned null", url, path);
|
||||
@@ -78,7 +80,7 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prereleases) {
|
||||
std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool include_prereleases) {
|
||||
const auto update_check_url = std::string{Common::g_build_auto_update_api};
|
||||
std::string update_check_path = fmt::format("/repos/{}",
|
||||
std::string{Common::g_build_auto_update_repo});
|
||||
@@ -96,6 +98,9 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
|
||||
|
||||
const std::string latest_tag
|
||||
= nlohmann::json::parse(tags_response.value()).at(0).at("name");
|
||||
const std::string latest_name =
|
||||
nlohmann::json::parse(releases_response.value()).at(0).at("name");
|
||||
|
||||
const bool latest_tag_has_release = releases_response.value().find(
|
||||
fmt::format("\"{}\"", latest_tag))
|
||||
!= std::string::npos;
|
||||
@@ -105,7 +110,7 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
|
||||
if (!latest_tag_has_release)
|
||||
return {};
|
||||
|
||||
return latest_tag;
|
||||
return Update{latest_tag, latest_name};
|
||||
} else { // This is a stable release, only check for other stable releases.
|
||||
update_check_path += "/releases/latest";
|
||||
const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path);
|
||||
@@ -114,7 +119,9 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
|
||||
return {};
|
||||
|
||||
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name");
|
||||
return latest_tag;
|
||||
const std::string latest_name = nlohmann::json::parse(response.value()).at("name");
|
||||
|
||||
return Update{latest_tag, latest_name};
|
||||
}
|
||||
|
||||
} catch (nlohmann::detail::out_of_range &) {
|
||||
@@ -133,3 +140,42 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<UpdateChecker::Update> UpdateChecker::GetUpdate() {
|
||||
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != NULL) ||
|
||||
(strstr(Common::g_build_version, "alpha") != NULL) ||
|
||||
(strstr(Common::g_build_version, "beta") != NULL) ||
|
||||
(strstr(Common::g_build_version, "rc") != NULL));
|
||||
const std::optional<UpdateChecker::Update> latest_release_tag =
|
||||
UpdateChecker::GetLatestRelease(is_prerelease);
|
||||
|
||||
if (!latest_release_tag)
|
||||
goto empty;
|
||||
|
||||
{
|
||||
std::string tag, build;
|
||||
if (Common::g_is_nightly_build) {
|
||||
std::vector<std::string> result;
|
||||
|
||||
boost::split(result, latest_release_tag->tag, boost::is_any_of("."));
|
||||
if (result.size() != 2)
|
||||
goto empty;
|
||||
tag = result[1];
|
||||
|
||||
boost::split(result, std::string{Common::g_build_version}, boost::is_any_of("-"));
|
||||
if (result.empty())
|
||||
goto empty;
|
||||
build = result[0];
|
||||
} else {
|
||||
tag = latest_release_tag->tag;
|
||||
build = Common::g_build_version;
|
||||
}
|
||||
|
||||
if (tag != build)
|
||||
return latest_release_tag.value();
|
||||
}
|
||||
|
||||
empty:
|
||||
return UpdateChecker::Update{};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
@@ -11,6 +11,13 @@
|
||||
#include <string>
|
||||
|
||||
namespace UpdateChecker {
|
||||
|
||||
typedef struct {
|
||||
std::string tag;
|
||||
std::string name;
|
||||
} Update;
|
||||
|
||||
std::optional<std::string> GetResponse(std::string url, std::string path);
|
||||
std::optional<std::string> GetLatestRelease(bool include_prereleases);
|
||||
std::optional<Update> GetLatestRelease(bool include_prereleases);
|
||||
std::optional<Update> GetUpdate();
|
||||
} // namespace UpdateChecker
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,7 +19,7 @@ Block::Block(ObjectPool<Inst>& inst_pool_) : inst_pool{&inst_pool_} {}
|
||||
Block::~Block() = default;
|
||||
|
||||
void Block::AppendNewInst(Opcode op, std::initializer_list<Value> args) {
|
||||
PrependNewInst(end(), op, args);
|
||||
PrependNewInst(instructions.end(), op, args);
|
||||
}
|
||||
|
||||
Block::iterator Block::PrependNewInst(iterator insertion_point, const Inst& base_inst) {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
add_subdirectory(host_shaders)
|
||||
add_subdirectory(gpu_logging)
|
||||
|
||||
if(LIBVA_FOUND)
|
||||
set_source_files_properties(host1x/ffmpeg/ffmpeg.cpp
|
||||
@@ -37,6 +38,10 @@ add_library(video_core STATIC
|
||||
dirty_flags.h
|
||||
dma_pusher.cpp
|
||||
dma_pusher.h
|
||||
engines/sw_blitter/blitter.cpp
|
||||
engines/sw_blitter/blitter.h
|
||||
engines/sw_blitter/converter.cpp
|
||||
engines/sw_blitter/converter.h
|
||||
engines/const_buffer_info.h
|
||||
engines/draw_manager.cpp
|
||||
engines/draw_manager.h
|
||||
@@ -309,7 +314,7 @@ add_library(video_core STATIC
|
||||
)
|
||||
|
||||
target_link_libraries(video_core PUBLIC common core)
|
||||
target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder)
|
||||
target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder gpu_logging)
|
||||
|
||||
if (YUZU_USE_EXTERNAL_FFMPEG)
|
||||
add_dependencies(video_core ffmpeg-build)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
@@ -29,124 +29,104 @@ class MemoryTrackerBase {
|
||||
static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS);
|
||||
static constexpr size_t MANAGER_POOL_SIZE = 32;
|
||||
static constexpr size_t WORDS_STACK_NEEDED = HIGHER_PAGE_SIZE / BYTES_PER_WORD;
|
||||
using Manager = WordManager<DeviceTracker, WORDS_STACK_NEEDED>;
|
||||
using Manager = WordManager<DeviceTracker, WORDS_STACK_NEEDED, HIGHER_PAGE_SIZE>;
|
||||
|
||||
public:
|
||||
MemoryTrackerBase(DeviceTracker& device_tracker_) : device_tracker{&device_tracker_} {}
|
||||
~MemoryTrackerBase() = default;
|
||||
|
||||
/// Returns the inclusive CPU modified range in a begin end pair
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
|
||||
u64 query_size) noexcept {
|
||||
return IteratePairs<true>(
|
||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->template ModifiedRegion<Type::CPU>(offset, size);
|
||||
});
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
return IteratePairs<true>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->ModifiedRegion(Type::CPU, offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the inclusive GPU modified range in a begin end pair
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
|
||||
u64 query_size) noexcept {
|
||||
return IteratePairs<false>(
|
||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->template ModifiedRegion<Type::GPU>(offset, size);
|
||||
});
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
return IteratePairs<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->ModifiedRegion(Type::GPU, offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns true if a region has been modified from the CPU
|
||||
[[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
return IteratePages<true>(
|
||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->template IsRegionModified<Type::CPU>(offset, size);
|
||||
});
|
||||
return IteratePages<true>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->IsRegionModified(Type::CPU, offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns true if a region has been modified from the GPU
|
||||
[[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
return IteratePages<false>(
|
||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->template IsRegionModified<Type::GPU>(offset, size);
|
||||
});
|
||||
return IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->IsRegionModified(Type::GPU, offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns true if a region has been marked as Preflushable
|
||||
[[nodiscard]] bool IsRegionPreflushable(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
return IteratePages<false>(
|
||||
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->template IsRegionModified<Type::Preflushable>(offset, size);
|
||||
});
|
||||
return IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
return manager->IsRegionModified(Type::Preflushable, offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Mark region as CPU modified, notifying the device_tracker about this change
|
||||
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::CPU, true>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Unmark region as CPU modified, notifying the device_tracker about this change
|
||||
void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::CPU, false>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::CPU, false, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Mark region as modified from the host GPU
|
||||
void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::GPU, true>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::GPU, true, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Mark region as modified from the host GPU
|
||||
void MarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::Preflushable, true>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::Preflushable, true, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Unmark region as modified from the host GPU
|
||||
void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::GPU, false>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::GPU, false, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Unmark region as modified from the host GPU
|
||||
void UnmarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
|
||||
IteratePages<true>(dirty_cpu_addr, query_size,
|
||||
[](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ChangeRegionState<Type::Preflushable, false>(
|
||||
manager->GetCpuAddr() + offset, size);
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ChangeRegionState(Type::Preflushable, false, manager->cpu_addr + offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
/// Mark region as modified from the CPU
|
||||
/// but don't mark it as modified until FlusHCachedWrites is called.
|
||||
void CachedCpuWrite(VAddr dirty_cpu_addr, u64 query_size) {
|
||||
IteratePages<true>(
|
||||
dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
|
||||
const VAddr cpu_address = manager->GetCpuAddr() + offset;
|
||||
manager->template ChangeRegionState<Type::CachedCPU, true>(cpu_address, size);
|
||||
cached_pages.insert(static_cast<u32>(cpu_address >> HIGHER_PAGE_BITS));
|
||||
});
|
||||
IteratePages<true>(dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
|
||||
const VAddr cpu_address = manager->cpu_addr + offset;
|
||||
manager->ChangeRegionState(Type::CachedCPU, true, cpu_address, size);
|
||||
cached_pages.insert(u32(cpu_address >> HIGHER_PAGE_BITS));
|
||||
});
|
||||
}
|
||||
|
||||
/// Flushes cached CPU writes, and notify the device_tracker about the deltas
|
||||
void FlushCachedWrites(VAddr query_cpu_addr, u64 query_size) noexcept {
|
||||
IteratePages<false>(query_cpu_addr, query_size,
|
||||
[](Manager* manager, [[maybe_unused]] u64 offset,
|
||||
[[maybe_unused]] size_t size) { manager->FlushCachedWrites(); });
|
||||
IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, [[maybe_unused]] u64 offset, [[maybe_unused]] size_t size) {
|
||||
manager->FlushCachedWrites();
|
||||
});
|
||||
}
|
||||
|
||||
void FlushCachedWrites() noexcept {
|
||||
@@ -159,35 +139,24 @@ public:
|
||||
/// Call 'func' for each CPU modified range and unmark those pages as CPU modified
|
||||
template <typename Func>
|
||||
void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) {
|
||||
IteratePages<true>(query_cpu_range, query_size,
|
||||
[&func](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ForEachModifiedRange<Type::CPU, true>(
|
||||
manager->GetCpuAddr() + offset, size, func);
|
||||
});
|
||||
IteratePages<true>(query_cpu_range, query_size, [&func](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ForEachModifiedRange(Type::CPU, true, manager->cpu_addr + offset, size, func);
|
||||
});
|
||||
}
|
||||
|
||||
/// Call 'func' for each GPU modified range and unmark those pages as GPU modified
|
||||
template <typename Func>
|
||||
void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, bool clear, Func&& func) {
|
||||
IteratePages<false>(query_cpu_range, query_size,
|
||||
[&func, clear](Manager* manager, u64 offset, size_t size) {
|
||||
if (clear) {
|
||||
manager->template ForEachModifiedRange<Type::GPU, true>(
|
||||
manager->GetCpuAddr() + offset, size, func);
|
||||
} else {
|
||||
manager->template ForEachModifiedRange<Type::GPU, false>(
|
||||
manager->GetCpuAddr() + offset, size, func);
|
||||
}
|
||||
});
|
||||
IteratePages<false>(query_cpu_range, query_size, [&func, clear](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ForEachModifiedRange(Type::GPU, clear, manager->cpu_addr + offset, size, func);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 query_size, Func&& func) {
|
||||
IteratePages<false>(query_cpu_range, query_size,
|
||||
[&func](Manager* manager, u64 offset, size_t size) {
|
||||
manager->template ForEachModifiedRange<Type::GPU, true>(
|
||||
manager->GetCpuAddr() + offset, size, func);
|
||||
});
|
||||
IteratePages<false>(query_cpu_range, query_size, [&func](Manager* manager, u64 offset, size_t size) {
|
||||
manager->ForEachModifiedRange(Type::GPU, true, manager->cpu_addr + offset, size, func);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -271,31 +240,24 @@ private:
|
||||
}
|
||||
|
||||
Manager* GetNewManager(VAddr base_cpu_address) {
|
||||
const auto on_return = [&] {
|
||||
auto* new_manager = free_managers.front();
|
||||
new_manager->SetCpuAddress(base_cpu_address);
|
||||
free_managers.pop_front();
|
||||
return new_manager;
|
||||
};
|
||||
if (!free_managers.empty()) {
|
||||
return on_return();
|
||||
if (free_managers.empty()) {
|
||||
manager_pool.emplace_back();
|
||||
auto& last_pool = manager_pool.back();
|
||||
for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
|
||||
new (&last_pool[i]) Manager(0, *device_tracker);
|
||||
free_managers.push_back(&last_pool[i]);
|
||||
}
|
||||
}
|
||||
manager_pool.emplace_back();
|
||||
auto& last_pool = manager_pool.back();
|
||||
for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
|
||||
new (&last_pool[i]) Manager(0, *device_tracker, HIGHER_PAGE_SIZE);
|
||||
free_managers.push_back(&last_pool[i]);
|
||||
}
|
||||
return on_return();
|
||||
Manager* new_manager = free_managers.front();
|
||||
new_manager->cpu_addr = base_cpu_address;
|
||||
free_managers.pop_front();
|
||||
return new_manager;
|
||||
}
|
||||
|
||||
std::array<Manager*, NUM_HIGH_PAGES> top_tier{};
|
||||
std::deque<std::array<Manager, MANAGER_POOL_SIZE>> manager_pool;
|
||||
std::deque<Manager*> free_managers;
|
||||
|
||||
std::array<Manager*, NUM_HIGH_PAGES> top_tier{};
|
||||
|
||||
std::unordered_set<u32> cached_pages;
|
||||
|
||||
DeviceTracker* device_tracker = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
@@ -31,158 +31,26 @@ enum class Type {
|
||||
CachedCPU,
|
||||
Untracked,
|
||||
Preflushable,
|
||||
Max
|
||||
};
|
||||
|
||||
/// Vector tracking modified pages tightly packed with small vector optimization
|
||||
template <size_t stack_words = 1>
|
||||
struct WordsArray {
|
||||
/// Returns the pointer to the words state
|
||||
[[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
|
||||
return is_short ? stack.data() : heap;
|
||||
}
|
||||
template <class DeviceTracker, size_t stack_words, size_t size_bytes>
|
||||
struct WordManager {
|
||||
static constexpr size_t num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD);
|
||||
|
||||
/// Returns the pointer to the words state
|
||||
[[nodiscard]] u64* Pointer(bool is_short) noexcept {
|
||||
return is_short ? stack.data() : heap;
|
||||
}
|
||||
|
||||
std::array<u64, stack_words> stack{}; ///< Small buffers storage
|
||||
u64* heap; ///< Not-small buffers pointer to the storage
|
||||
};
|
||||
|
||||
template <size_t stack_words = 1>
|
||||
struct Words {
|
||||
explicit Words() = default;
|
||||
explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} {
|
||||
num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD);
|
||||
if (IsShort()) {
|
||||
cpu.stack.fill(~u64{0});
|
||||
gpu.stack.fill(0);
|
||||
cached_cpu.stack.fill(0);
|
||||
untracked.stack.fill(~u64{0});
|
||||
preflushable.stack.fill(0);
|
||||
} else {
|
||||
// Share allocation between CPU and GPU pages and set their default values
|
||||
u64* const alloc = new u64[num_words * 5];
|
||||
cpu.heap = alloc;
|
||||
gpu.heap = alloc + num_words;
|
||||
cached_cpu.heap = alloc + num_words * 2;
|
||||
untracked.heap = alloc + num_words * 3;
|
||||
preflushable.heap = alloc + num_words * 4;
|
||||
std::fill_n(cpu.heap, num_words, ~u64{0});
|
||||
std::fill_n(gpu.heap, num_words, 0);
|
||||
std::fill_n(cached_cpu.heap, num_words, 0);
|
||||
std::fill_n(untracked.heap, num_words, ~u64{0});
|
||||
std::fill_n(preflushable.heap, num_words, 0);
|
||||
}
|
||||
explicit WordManager(VAddr cpu_addr_, DeviceTracker& tracker_) : tracker{&tracker_}, cpu_addr{cpu_addr_} {
|
||||
std::fill_n(heap.data() + size_t(Type::CPU) * num_words, num_words, ~u64{0});
|
||||
std::fill_n(heap.data() + size_t(Type::Untracked) * num_words, num_words, ~u64{0});
|
||||
// Clean up tailing bits
|
||||
const u64 last_word_size = size_bytes % BYTES_PER_WORD;
|
||||
const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
|
||||
const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
|
||||
const u64 last_word = (~u64{0} << shift) >> shift;
|
||||
cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
|
||||
untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
|
||||
u64 const last_word_size = size_bytes % BYTES_PER_WORD;
|
||||
u64 const last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
|
||||
u64 const shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
|
||||
u64 const last_word = (~u64{0} << shift) >> shift;
|
||||
heap[num_words * size_t(Type::CPU) + num_words - 1] = last_word;
|
||||
heap[num_words * size_t(Type::Untracked) + num_words - 1] = last_word;
|
||||
}
|
||||
|
||||
~Words() {
|
||||
Release();
|
||||
}
|
||||
|
||||
Words& operator=(Words&& rhs) noexcept {
|
||||
Release();
|
||||
size_bytes = rhs.size_bytes;
|
||||
num_words = rhs.num_words;
|
||||
cpu = rhs.cpu;
|
||||
gpu = rhs.gpu;
|
||||
cached_cpu = rhs.cached_cpu;
|
||||
untracked = rhs.untracked;
|
||||
preflushable = rhs.preflushable;
|
||||
rhs.cpu.heap = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Words(Words&& rhs) noexcept
|
||||
: size_bytes{rhs.size_bytes}, num_words{rhs.num_words}, cpu{rhs.cpu}, gpu{rhs.gpu},
|
||||
cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked}, preflushable{rhs.preflushable} {
|
||||
rhs.cpu.heap = nullptr;
|
||||
}
|
||||
|
||||
Words& operator=(const Words&) = delete;
|
||||
Words(const Words&) = delete;
|
||||
|
||||
/// Returns true when the buffer fits in the small vector optimization
|
||||
[[nodiscard]] bool IsShort() const noexcept {
|
||||
return num_words <= stack_words;
|
||||
}
|
||||
|
||||
/// Returns the number of words of the buffer
|
||||
[[nodiscard]] size_t NumWords() const noexcept {
|
||||
return num_words;
|
||||
}
|
||||
|
||||
/// Release buffer resources
|
||||
void Release() {
|
||||
if (!IsShort()) {
|
||||
// CPU written words is the base for the heap allocation
|
||||
delete[] cpu.heap;
|
||||
}
|
||||
}
|
||||
|
||||
template <Type type>
|
||||
std::span<u64> Span() noexcept {
|
||||
if constexpr (type == Type::CPU) {
|
||||
return std::span<u64>(cpu.Pointer(IsShort()), num_words);
|
||||
} else if constexpr (type == Type::GPU) {
|
||||
return std::span<u64>(gpu.Pointer(IsShort()), num_words);
|
||||
} else if constexpr (type == Type::CachedCPU) {
|
||||
return std::span<u64>(cached_cpu.Pointer(IsShort()), num_words);
|
||||
} else if constexpr (type == Type::Untracked) {
|
||||
return std::span<u64>(untracked.Pointer(IsShort()), num_words);
|
||||
} else if constexpr (type == Type::Preflushable) {
|
||||
return std::span<u64>(preflushable.Pointer(IsShort()), num_words);
|
||||
}
|
||||
}
|
||||
|
||||
template <Type type>
|
||||
std::span<const u64> Span() const noexcept {
|
||||
if constexpr (type == Type::CPU) {
|
||||
return std::span<const u64>(cpu.Pointer(IsShort()), num_words);
|
||||
} else if constexpr (type == Type::GPU) {
|
||||
return std::span<const u64>(gpu.Pointer(IsShort()), num_words);
|
||||
} else if constexpr (type == Type::CachedCPU) {
|
||||
return std::span<const u64>(cached_cpu.Pointer(IsShort()), num_words);
|
||||
} else if constexpr (type == Type::Untracked) {
|
||||
return std::span<const u64>(untracked.Pointer(IsShort()), num_words);
|
||||
} else if constexpr (type == Type::Preflushable) {
|
||||
return std::span<const u64>(preflushable.Pointer(IsShort()), num_words);
|
||||
}
|
||||
}
|
||||
|
||||
u64 size_bytes = 0;
|
||||
size_t num_words = 0;
|
||||
WordsArray<stack_words> cpu;
|
||||
WordsArray<stack_words> gpu;
|
||||
WordsArray<stack_words> cached_cpu;
|
||||
WordsArray<stack_words> untracked;
|
||||
WordsArray<stack_words> preflushable;
|
||||
};
|
||||
|
||||
template <class DeviceTracker, size_t stack_words = 1>
|
||||
class WordManager {
|
||||
public:
|
||||
explicit WordManager(VAddr cpu_addr_, DeviceTracker& tracker_, u64 size_bytes)
|
||||
: cpu_addr{cpu_addr_}, tracker{&tracker_}, words{size_bytes} {}
|
||||
|
||||
explicit WordManager() = default;
|
||||
|
||||
void SetCpuAddress(VAddr new_cpu_addr) {
|
||||
cpu_addr = new_cpu_addr;
|
||||
}
|
||||
|
||||
VAddr GetCpuAddr() const {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
static u64 ExtractBits(u64 word, size_t page_start, size_t page_end) {
|
||||
constexpr size_t number_bits = sizeof(u64) * 8;
|
||||
const size_t limit_page_end = number_bits - (std::min)(page_end, number_bits);
|
||||
@@ -192,7 +60,7 @@ public:
|
||||
}
|
||||
|
||||
static std::pair<size_t, size_t> GetWordPage(VAddr address) {
|
||||
const size_t converted_address = static_cast<size_t>(address);
|
||||
const size_t converted_address = size_t(address);
|
||||
const size_t word_number = converted_address / BYTES_PER_WORD;
|
||||
const size_t amount_pages = converted_address % BYTES_PER_WORD;
|
||||
return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE);
|
||||
@@ -201,32 +69,28 @@ public:
|
||||
template <typename Func>
|
||||
void IterateWords(size_t offset, size_t size, Func&& func) const {
|
||||
using FuncReturn = std::invoke_result_t<Func, std::size_t, u64>;
|
||||
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
|
||||
const size_t start = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset), 0LL));
|
||||
const size_t end = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset + size), 0LL));
|
||||
if (start >= SizeBytes() || end <= start) {
|
||||
return;
|
||||
}
|
||||
auto [start_word, start_page] = GetWordPage(start);
|
||||
auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
|
||||
const size_t num_words = NumWords();
|
||||
start_word = (std::min)(start_word, num_words);
|
||||
end_word = (std::min)(end_word, num_words);
|
||||
const size_t diff = end_word - start_word;
|
||||
end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
|
||||
end_word = (std::min)(end_word, num_words);
|
||||
end_page += diff * PAGES_PER_WORD;
|
||||
constexpr u64 base_mask{~0ULL};
|
||||
for (size_t word_index = start_word; word_index < end_word; word_index++) {
|
||||
const u64 mask = ExtractBits(base_mask, start_page, end_page);
|
||||
start_page = 0;
|
||||
end_page -= PAGES_PER_WORD;
|
||||
if constexpr (BOOL_BREAK) {
|
||||
if (func(word_index, mask)) {
|
||||
return;
|
||||
const size_t start = size_t(std::max<s64>(s64(offset), 0LL));
|
||||
const size_t end = size_t(std::max<s64>(s64(offset + size), 0LL));
|
||||
if (!(start >= size_bytes || end <= start)) {
|
||||
auto [start_word, start_page] = GetWordPage(start);
|
||||
auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
|
||||
start_word = (std::min)(start_word, num_words);
|
||||
end_word = (std::min)(end_word, num_words);
|
||||
const size_t diff = end_word - start_word;
|
||||
end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
|
||||
end_word = (std::min)(end_word, num_words);
|
||||
end_page += diff * PAGES_PER_WORD;
|
||||
constexpr u64 base_mask{~0ULL};
|
||||
for (size_t word_index = start_word; word_index < end_word; word_index++) {
|
||||
const u64 mask = ExtractBits(base_mask, start_page, end_page);
|
||||
start_page = 0;
|
||||
end_page -= PAGES_PER_WORD;
|
||||
if constexpr (std::is_same_v<FuncReturn, bool>) { // bool return
|
||||
if (func(word_index, mask))
|
||||
return;
|
||||
} else {
|
||||
func(word_index, mask);
|
||||
}
|
||||
} else {
|
||||
func(word_index, mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -246,39 +110,32 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the state of a range of pages
|
||||
*
|
||||
* @param dirty_addr Base address to mark or unmark as modified
|
||||
* @param size Size in bytes to mark or unmark as modified
|
||||
*/
|
||||
template <Type type, bool enable>
|
||||
void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
|
||||
std::span<u64> state_words = words.template Span<type>();
|
||||
[[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
|
||||
[[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
|
||||
/// @brief Change the state of a range of pages
|
||||
/// @param type Type of the page
|
||||
/// @param enable If enabling or disabling
|
||||
/// @param dirty_addr Base address to mark or unmark as modified
|
||||
/// @param size Size in bytes to mark or unmark as modified
|
||||
void ChangeRegionState(Type type, bool enable, u64 dirty_addr, u64 size) noexcept {
|
||||
std::span<u64> state_words = Span(type);
|
||||
[[maybe_unused]] std::span<u64> untracked_words = Span(Type::Untracked);
|
||||
[[maybe_unused]] std::span<u64> cached_words = Span(Type::CachedCPU);
|
||||
std::vector<std::pair<VAddr, u64>> ranges;
|
||||
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
|
||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
||||
CollectChangedRanges<(!enable)>(index, untracked_words[index], mask, ranges);
|
||||
if (type == Type::CPU || type == Type::CachedCPU) {
|
||||
CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges);
|
||||
}
|
||||
if constexpr (enable) {
|
||||
if (enable) {
|
||||
state_words[index] |= mask;
|
||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
||||
if (type == Type::CPU || type == Type::CachedCPU)
|
||||
untracked_words[index] |= mask;
|
||||
}
|
||||
if constexpr (type == Type::CPU) {
|
||||
if (type == Type::CPU)
|
||||
cached_words[index] &= ~mask;
|
||||
}
|
||||
} else {
|
||||
if constexpr (type == Type::CPU) {
|
||||
const u64 word = state_words[index] & mask;
|
||||
cached_words[index] &= ~word;
|
||||
}
|
||||
if (type == Type::CPU)
|
||||
cached_words[index] &= ~(state_words[index] & mask);
|
||||
state_words[index] &= ~mask;
|
||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
||||
if (type == Type::CPU || type == Type::CachedCPU)
|
||||
untracked_words[index] &= ~mask;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!ranges.empty()) {
|
||||
@@ -286,22 +143,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop over each page in the given range, turn off those bits and notify the tracker if
|
||||
* needed. Call the given function on each turned off range.
|
||||
*
|
||||
* @param query_cpu_range Base CPU address to loop over
|
||||
* @param size Size in bytes of the CPU range to loop over
|
||||
* @param func Function to call for each turned off region
|
||||
*/
|
||||
template <Type type, bool clear, typename Func>
|
||||
void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) {
|
||||
static_assert(type != Type::Untracked);
|
||||
|
||||
std::span<u64> state_words = words.template Span<type>();
|
||||
[[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
|
||||
[[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
|
||||
const size_t offset = query_cpu_range - cpu_addr;
|
||||
/// @brief Loop over each page in the given range.
|
||||
/// Turn off those bits and notify the tracker if needed. Call the given function on each turned off range.
|
||||
/// @param type Type of the address
|
||||
/// @param clear Whetever to clear
|
||||
/// @param query_cpu_range Base CPU address to loop over
|
||||
/// @param size Size in bytes of the CPU range to loop over
|
||||
/// @param func Function to call for each turned off region
|
||||
template <typename Func>
|
||||
void ForEachModifiedRange(Type type, bool clear, VAddr query_cpu_range, s64 size, Func&& func) {
|
||||
//static_assert(type != Type::Untracked);
|
||||
std::span<u64> state_words = Span(type);
|
||||
std::span<u64> untracked_words = Span(Type::Untracked);
|
||||
std::span<u64> cached_words = Span(Type::CachedCPU);
|
||||
size_t const offset = query_cpu_range - cpu_addr;
|
||||
bool pending = false;
|
||||
size_t pending_offset{};
|
||||
size_t pending_pointer{};
|
||||
@@ -311,39 +166,32 @@ public:
|
||||
};
|
||||
std::vector<std::pair<VAddr, u64>> ranges;
|
||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
||||
if constexpr (type == Type::GPU) {
|
||||
if (type == Type::GPU)
|
||||
mask &= ~untracked_words[index];
|
||||
}
|
||||
const u64 word = state_words[index] & mask;
|
||||
if constexpr (clear) {
|
||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
||||
CollectChangedRanges<true>(index, untracked_words[index], mask, ranges);
|
||||
if (clear) {
|
||||
if (type == Type::CPU || type == Type::CachedCPU) {
|
||||
CollectChangedRanges(true, index, untracked_words[index], mask, ranges);
|
||||
}
|
||||
state_words[index] &= ~mask;
|
||||
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
|
||||
if (type == Type::CPU || type == Type::CachedCPU)
|
||||
untracked_words[index] &= ~mask;
|
||||
}
|
||||
if constexpr (type == Type::CPU) {
|
||||
if (type == Type::CPU)
|
||||
cached_words[index] &= ~word;
|
||||
}
|
||||
}
|
||||
const size_t base_offset = index * PAGES_PER_WORD;
|
||||
IteratePages(word, [&](size_t pages_offset, size_t pages_size) {
|
||||
const auto reset = [&]() {
|
||||
if (!pending) {
|
||||
pending_offset = base_offset + pages_offset;
|
||||
pending_pointer = base_offset + pages_offset + pages_size;
|
||||
};
|
||||
if (!pending) {
|
||||
reset();
|
||||
pending = true;
|
||||
return;
|
||||
}
|
||||
if (pending_pointer == base_offset + pages_offset) {
|
||||
} else if (pending_pointer == base_offset + pages_offset) {
|
||||
pending_pointer += pages_size;
|
||||
return;
|
||||
} else {
|
||||
func(cpu_addr + pending_offset * BYTES_PER_PAGE, (pending_pointer - pending_offset) * BYTES_PER_PAGE);
|
||||
pending_offset = base_offset + pages_offset;
|
||||
pending_pointer = base_offset + pages_offset + pages_size;
|
||||
}
|
||||
release();
|
||||
reset();
|
||||
});
|
||||
});
|
||||
if (pending) {
|
||||
@@ -354,90 +202,55 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when a region has been modified
|
||||
*
|
||||
* @param offset Offset in bytes from the start of the buffer
|
||||
* @param size Size in bytes of the region to query for modifications
|
||||
*/
|
||||
template <Type type>
|
||||
[[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
|
||||
static_assert(type != Type::Untracked);
|
||||
|
||||
const std::span<const u64> state_words = words.template Span<type>();
|
||||
[[maybe_unused]] const std::span<const u64> untracked_words =
|
||||
words.template Span<Type::Untracked>();
|
||||
/// @brief Returns true when a region has been modified
|
||||
/// @param type Type of region
|
||||
/// @param offset Offset in bytes from the start of the buffer
|
||||
/// @param size Size in bytes of the region to query for modifications
|
||||
[[nodiscard]] bool IsRegionModified(Type type, u64 offset, u64 size) const noexcept {
|
||||
//static_assert(type != Type::Untracked);
|
||||
const std::span<const u64> state_words = Span(type);
|
||||
const std::span<const u64> untracked_words = Span(Type::Untracked);
|
||||
bool result = false;
|
||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
||||
if constexpr (type == Type::GPU) {
|
||||
if (type == Type::GPU)
|
||||
mask &= ~untracked_words[index];
|
||||
}
|
||||
const u64 word = state_words[index] & mask;
|
||||
if (word != 0) {
|
||||
result = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return (state_words[index] & mask) != 0 ? (result = true) : false;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a begin end pair with the inclusive modified region
|
||||
*
|
||||
* @param offset Offset in bytes from the start of the buffer
|
||||
* @param size Size in bytes of the region to query for modifications
|
||||
*/
|
||||
template <Type type>
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
|
||||
static_assert(type != Type::Untracked);
|
||||
const std::span<const u64> state_words = words.template Span<type>();
|
||||
[[maybe_unused]] const std::span<const u64> untracked_words =
|
||||
words.template Span<Type::Untracked>();
|
||||
u64 begin = (std::numeric_limits<u64>::max)();
|
||||
u64 end = 0;
|
||||
/// @brief Returns a begin end pair with the inclusive modified region
|
||||
/// @param offset Offset in bytes from the start of the buffer
|
||||
/// @param size Size in bytes of the region to query for modifications
|
||||
[[nodiscard]] std::pair<u64, u64> ModifiedRegion(Type type, u64 offset, u64 size) const noexcept {
|
||||
//static_assert(type != Type::Untracked);
|
||||
const std::span<const u64> state_words = Span(type);
|
||||
const std::span<const u64> untracked_words = Span(Type::Untracked);
|
||||
u64 begin = (std::numeric_limits<u64>::max)(), end = 0;
|
||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
||||
if constexpr (type == Type::GPU) {
|
||||
if (type == Type::GPU)
|
||||
mask &= ~untracked_words[index];
|
||||
}
|
||||
const u64 word = state_words[index] & mask;
|
||||
if (word == 0) {
|
||||
return;
|
||||
if (word != 0) {
|
||||
const u64 local_page_begin = std::countr_zero(word);
|
||||
const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
|
||||
const u64 page_index = index * PAGES_PER_WORD;
|
||||
begin = (std::min)(begin, page_index + local_page_begin);
|
||||
end = page_index + local_page_end;
|
||||
}
|
||||
const u64 local_page_begin = std::countr_zero(word);
|
||||
const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
|
||||
const u64 page_index = index * PAGES_PER_WORD;
|
||||
begin = (std::min)(begin, page_index + local_page_begin);
|
||||
end = page_index + local_page_end;
|
||||
});
|
||||
static constexpr std::pair<u64, u64> EMPTY{0, 0};
|
||||
return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
|
||||
}
|
||||
|
||||
/// Returns the number of words of the manager
|
||||
[[nodiscard]] size_t NumWords() const noexcept {
|
||||
return words.NumWords();
|
||||
}
|
||||
|
||||
/// Returns the size in bytes of the manager
|
||||
[[nodiscard]] u64 SizeBytes() const noexcept {
|
||||
return words.size_bytes;
|
||||
}
|
||||
|
||||
/// Returns true when the buffer fits in the small vector optimization
|
||||
[[nodiscard]] bool IsShort() const noexcept {
|
||||
return words.IsShort();
|
||||
return begin < end ? std::make_pair<u64, u64>(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE)
|
||||
: std::make_pair<u64, u64>(0, 0);
|
||||
}
|
||||
|
||||
void FlushCachedWrites() noexcept {
|
||||
const u64 num_words = NumWords();
|
||||
u64* const cached_words = Array<Type::CachedCPU>();
|
||||
u64* const untracked_words = Array<Type::Untracked>();
|
||||
u64* const cpu_words = Array<Type::CPU>();
|
||||
auto const cached_words = Span(Type::CachedCPU);
|
||||
auto const untracked_words = Span(Type::Untracked);
|
||||
auto const cpu_words = Span(Type::CPU);
|
||||
std::vector<std::pair<VAddr, u64>> ranges;
|
||||
for (u64 word_index = 0; word_index < num_words; ++word_index) {
|
||||
const u64 cached_bits = cached_words[word_index];
|
||||
CollectChangedRanges<false>(word_index, untracked_words[word_index], cached_bits, ranges);
|
||||
CollectChangedRanges(false, word_index, untracked_words[word_index], cached_bits, ranges);
|
||||
untracked_words[word_index] |= cached_bits;
|
||||
cpu_words[word_index] |= cached_bits;
|
||||
cached_words[word_index] = 0;
|
||||
@@ -447,45 +260,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <Type type>
|
||||
u64* Array() noexcept {
|
||||
if constexpr (type == Type::CPU) {
|
||||
return words.cpu.Pointer(IsShort());
|
||||
} else if constexpr (type == Type::GPU) {
|
||||
return words.gpu.Pointer(IsShort());
|
||||
} else if constexpr (type == Type::CachedCPU) {
|
||||
return words.cached_cpu.Pointer(IsShort());
|
||||
} else if constexpr (type == Type::Untracked) {
|
||||
return words.untracked.Pointer(IsShort());
|
||||
}
|
||||
}
|
||||
|
||||
template <Type type>
|
||||
const u64* Array() const noexcept {
|
||||
if constexpr (type == Type::CPU) {
|
||||
return words.cpu.Pointer(IsShort());
|
||||
} else if constexpr (type == Type::GPU) {
|
||||
return words.gpu.Pointer(IsShort());
|
||||
} else if constexpr (type == Type::CachedCPU) {
|
||||
return words.cached_cpu.Pointer(IsShort());
|
||||
} else if constexpr (type == Type::Untracked) {
|
||||
return words.untracked.Pointer(IsShort());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify tracker about changes in the CPU tracking state of a word in the buffer
|
||||
*
|
||||
* @param word_index Index to the word to notify to the tracker
|
||||
* @param current_bits Current state of the word
|
||||
* @param new_bits New state of the word
|
||||
*
|
||||
* @tparam add_to_tracker True when the tracker should start tracking the new pages
|
||||
*/
|
||||
template <bool add_to_tracker>
|
||||
void CollectChangedRanges(u64 word_index, u64 current_bits, u64 new_bits,
|
||||
std::vector<std::pair<VAddr, u64>>& out_ranges) const {
|
||||
/// @brief Notify tracker about changes in the CPU tracking state of a word in the buffer
|
||||
/// @param add_to_tracker If add to tracker (selects changed bits)
|
||||
/// @param word_index Index to the word to notify to the tracker
|
||||
/// @param current_bits Current state of the word
|
||||
/// @param new_bits New state of the word
|
||||
/// @tparam add_to_tracker True when the tracker should start tracking the new pages
|
||||
void CollectChangedRanges(bool add_to_tracker, u64 word_index, u64 current_bits, u64 new_bits, std::vector<std::pair<VAddr, u64>>& out_ranges) const {
|
||||
u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits;
|
||||
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
|
||||
IteratePages(changed_bits, [&](size_t offset, size_t size) {
|
||||
@@ -494,9 +275,9 @@ private:
|
||||
}
|
||||
|
||||
void ApplyCollectedRanges(std::vector<std::pair<VAddr, u64>>& ranges, int delta) const {
|
||||
if (ranges.empty()) return;
|
||||
std::sort(ranges.begin(), ranges.end(),
|
||||
[](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
if (ranges.empty())
|
||||
return;
|
||||
std::sort(ranges.begin(), ranges.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
|
||||
// Coalesce adjacent/contiguous ranges
|
||||
std::vector<std::pair<VAddr, size_t>> coalesced;
|
||||
coalesced.reserve(ranges.size());
|
||||
@@ -517,19 +298,30 @@ private:
|
||||
ranges.clear();
|
||||
}
|
||||
|
||||
template <bool add_to_tracker>
|
||||
void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
|
||||
/// @brief Notify tracker about changes in the CPU tracking state of a word in the buffer
|
||||
/// @param add_to_tracker True when the tracker should start tracking the new pages
|
||||
/// @param word_index Index to the word to notify to the tracker
|
||||
/// @param current_bits Current state of the word
|
||||
/// @param new_bits New state of the word
|
||||
void NotifyRasterizer(bool add_to_tracker, u64 word_index, u64 current_bits, u64 new_bits) const {
|
||||
u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits;
|
||||
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
|
||||
IteratePages(changed_bits, [&](size_t offset, size_t size) {
|
||||
tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE,
|
||||
add_to_tracker ? 1 : -1);
|
||||
tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE, add_to_tracker ? 1 : -1);
|
||||
});
|
||||
}
|
||||
|
||||
VAddr cpu_addr = 0;
|
||||
std::span<u64> Span(Type type) noexcept {
|
||||
return std::span<u64>(heap.data() + num_words * size_t(type), num_words);
|
||||
}
|
||||
|
||||
std::span<const u64> Span(Type type) const noexcept {
|
||||
return std::span<const u64>(heap.data() + num_words * size_t(type), num_words);
|
||||
}
|
||||
|
||||
std::array<u64, size_t(Type::Max) * num_words> heap = {};
|
||||
DeviceTracker* tracker = nullptr;
|
||||
Words<stack_words> words;
|
||||
VAddr cpu_addr = 0;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
@@ -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
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/engines/sw_blitter/blitter.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/surface.h"
|
||||
@@ -20,6 +21,7 @@ namespace Tegra::Engines {
|
||||
using namespace Texture;
|
||||
|
||||
Fermi2D::Fermi2D(MemoryManager& memory_manager_) : memory_manager{memory_manager_} {
|
||||
sw_blitter = std::make_unique<Blitter::SoftwareBlitEngine>(memory_manager);
|
||||
// Nvidia's OpenGL driver seems to assume these values
|
||||
regs.src.depth = 1;
|
||||
regs.dst.depth = 1;
|
||||
@@ -58,19 +60,21 @@ void Fermi2D::ConsumeSinkImpl() {
|
||||
}
|
||||
|
||||
void Fermi2D::Blit() {
|
||||
LOG_DEBUG(HW_GPU, "called. source address=0x{:x}, destination address=0x{:x}", regs.src.Address(), regs.dst.Address());
|
||||
|
||||
const auto& args = regs.pixels_from_memory;
|
||||
//constexpr s64 null_derivative = 1ULL << 32;
|
||||
Surface src = regs.src;
|
||||
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
|
||||
LOG_DEBUG(HW_GPU, "called. source address=0x{:x}, destination address=0x{:x}",
|
||||
regs.src.Address(), regs.dst.Address());
|
||||
|
||||
UNIMPLEMENTED_IF_MSG(regs.operation != Operation::SrcCopy, "Operation is not copy");
|
||||
UNIMPLEMENTED_IF_MSG(regs.src.layer != 0, "Source layer is not zero");
|
||||
UNIMPLEMENTED_IF_MSG(regs.dst.layer != 0, "Destination layer is not zero");
|
||||
UNIMPLEMENTED_IF_MSG(regs.src.depth != 1, "Source depth is not one");
|
||||
UNIMPLEMENTED_IF_MSG(regs.clip_enable != 0, "Clipped blit enabled");
|
||||
// UNIMPLEMENTED_IF_MSG(args.du_dx == null_derivative && args.dv_dy == null_derivative, "du/dx & dv/dy null derivative");
|
||||
|
||||
const auto& args = regs.pixels_from_memory;
|
||||
constexpr s64 null_derivative = 1ULL << 32;
|
||||
Surface src = regs.src;
|
||||
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
|
||||
const bool delegate_to_gpu = src.width > 512 && src.height > 512 && bytes_per_pixel <= 8 &&
|
||||
src.format != regs.dst.format;
|
||||
|
||||
auto srcX = args.src_x0;
|
||||
auto srcY = args.src_y0;
|
||||
@@ -82,7 +86,8 @@ void Fermi2D::Blit() {
|
||||
Config config{
|
||||
.operation = regs.operation,
|
||||
.filter = args.sample_mode.filter,
|
||||
.must_accelerate = true,
|
||||
.must_accelerate =
|
||||
args.du_dx != null_derivative || args.dv_dy != null_derivative || delegate_to_gpu,
|
||||
.dst_x0 = args.dst_x0,
|
||||
.dst_y0 = args.dst_y0,
|
||||
.dst_x1 = args.dst_x0 + args.dst_width,
|
||||
@@ -107,9 +112,9 @@ void Fermi2D::Blit() {
|
||||
}
|
||||
|
||||
memory_manager.FlushCaching();
|
||||
|
||||
bool was_accelerated = rasterizer->AccelerateSurfaceCopy(src, regs.dst, config);
|
||||
ASSERT(was_accelerated && "Backend must always accelerate copies/blits");
|
||||
if (!rasterizer->AccelerateSurfaceCopy(src, regs.dst, config)) {
|
||||
sw_blitter->Blit(src, regs.dst, config);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Tegra::Engines
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -307,6 +304,7 @@ public:
|
||||
|
||||
private:
|
||||
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
||||
std::unique_ptr<Blitter::SoftwareBlitEngine> sw_blitter;
|
||||
MemoryManager& memory_manager;
|
||||
|
||||
/// Performs the copy from the source surface to the destination surface as configured in the
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "common/scratch_buffer.h"
|
||||
#include "video_core/engines/sw_blitter/blitter.h"
|
||||
#include "video_core/engines/sw_blitter/converter.h"
|
||||
#include "video_core/guest_memory.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
using VideoCore::Surface::BytesPerBlock;
|
||||
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
||||
|
||||
namespace Tegra::Engines::Blitter {
|
||||
|
||||
using namespace Texture;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t ir_components = 4;
|
||||
|
||||
void NearestNeighbor(std::span<const u8> input, std::span<u8> output, u32 src_width, u32 src_height,
|
||||
u32 dst_width, u32 dst_height, size_t bpp) {
|
||||
const size_t dx_du = std::llround((static_cast<f64>(src_width) / dst_width) * (1ULL << 32));
|
||||
const size_t dy_dv = std::llround((static_cast<f64>(src_height) / dst_height) * (1ULL << 32));
|
||||
size_t src_y = 0;
|
||||
for (u32 y = 0; y < dst_height; y++) {
|
||||
size_t src_x = 0;
|
||||
for (u32 x = 0; x < dst_width; x++) {
|
||||
const size_t read_from = ((src_y * src_width + src_x) >> 32) * bpp;
|
||||
const size_t write_to = (y * dst_width + x) * bpp;
|
||||
|
||||
std::memcpy(&output[write_to], &input[read_from], bpp);
|
||||
src_x += dx_du;
|
||||
}
|
||||
src_y += dy_dv;
|
||||
}
|
||||
}
|
||||
|
||||
void NearestNeighborFast(std::span<const f32> input, std::span<f32> output, u32 src_width,
|
||||
u32 src_height, u32 dst_width, u32 dst_height) {
|
||||
const size_t dx_du = std::llround((static_cast<f64>(src_width) / dst_width) * (1ULL << 32));
|
||||
const size_t dy_dv = std::llround((static_cast<f64>(src_height) / dst_height) * (1ULL << 32));
|
||||
size_t src_y = 0;
|
||||
for (u32 y = 0; y < dst_height; y++) {
|
||||
size_t src_x = 0;
|
||||
for (u32 x = 0; x < dst_width; x++) {
|
||||
const size_t read_from = ((src_y * src_width + src_x) >> 32) * ir_components;
|
||||
const size_t write_to = (y * dst_width + x) * ir_components;
|
||||
|
||||
std::memcpy(&output[write_to], &input[read_from], sizeof(f32) * ir_components);
|
||||
src_x += dx_du;
|
||||
}
|
||||
src_y += dy_dv;
|
||||
}
|
||||
}
|
||||
|
||||
void Bilinear(std::span<const f32> input, std::span<f32> output, size_t src_width,
|
||||
size_t src_height, size_t dst_width, size_t dst_height) {
|
||||
const auto bilinear_sample = [](std::span<const f32> x0_y0, std::span<const f32> x1_y0,
|
||||
std::span<const f32> x0_y1, std::span<const f32> x1_y1,
|
||||
f32 weight_x, f32 weight_y) {
|
||||
std::array<f32, ir_components> result{};
|
||||
for (size_t i = 0; i < ir_components; i++) {
|
||||
const f32 a = std::lerp(x0_y0[i], x1_y0[i], weight_x);
|
||||
const f32 b = std::lerp(x0_y1[i], x1_y1[i], weight_x);
|
||||
result[i] = std::lerp(a, b, weight_y);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const f32 dx_du =
|
||||
dst_width > 1 ? static_cast<f32>(src_width - 1) / static_cast<f32>(dst_width - 1) : 0.f;
|
||||
const f32 dy_dv =
|
||||
dst_height > 1 ? static_cast<f32>(src_height - 1) / static_cast<f32>(dst_height - 1) : 0.f;
|
||||
for (u32 y = 0; y < dst_height; y++) {
|
||||
for (u32 x = 0; x < dst_width; x++) {
|
||||
const f32 x_low = std::floor(static_cast<f32>(x) * dx_du);
|
||||
const f32 y_low = std::floor(static_cast<f32>(y) * dy_dv);
|
||||
const f32 x_high = std::ceil(static_cast<f32>(x) * dx_du);
|
||||
const f32 y_high = std::ceil(static_cast<f32>(y) * dy_dv);
|
||||
const f32 weight_x = (static_cast<f32>(x) * dx_du) - x_low;
|
||||
const f32 weight_y = (static_cast<f32>(y) * dy_dv) - y_low;
|
||||
|
||||
const auto read_src = [&](f32 in_x, f32 in_y) {
|
||||
const size_t read_from =
|
||||
((static_cast<size_t>(in_x) * src_width + static_cast<size_t>(in_y)) >> 32) *
|
||||
ir_components;
|
||||
return std::span<const f32>(&input[read_from], ir_components);
|
||||
};
|
||||
|
||||
auto x0_y0 = read_src(x_low, y_low);
|
||||
auto x1_y0 = read_src(x_high, y_low);
|
||||
auto x0_y1 = read_src(x_low, y_high);
|
||||
auto x1_y1 = read_src(x_high, y_high);
|
||||
|
||||
const auto result = bilinear_sample(x0_y0, x1_y0, x0_y1, x1_y1, weight_x, weight_y);
|
||||
|
||||
const size_t write_to = (y * dst_width + x) * ir_components;
|
||||
|
||||
std::memcpy(&output[write_to], &result, sizeof(f32) * ir_components);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool unpack>
|
||||
void ProcessPitchLinear(std::span<const u8> input, std::span<u8> output, size_t extent_x,
|
||||
size_t extent_y, u32 pitch, u32 x0, u32 y0, size_t bpp) {
|
||||
const size_t base_offset = x0 * bpp;
|
||||
const size_t copy_size = extent_x * bpp;
|
||||
for (size_t y = 0; y < extent_y; y++) {
|
||||
const size_t first_offset = (y + y0) * pitch + base_offset;
|
||||
const size_t second_offset = y * extent_x * bpp;
|
||||
u8* write_to = unpack ? &output[first_offset] : &output[second_offset];
|
||||
const u8* read_from = unpack ? &input[second_offset] : &input[first_offset];
|
||||
std::memcpy(write_to, read_from, copy_size);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct SoftwareBlitEngine::BlitEngineImpl {
|
||||
Common::ScratchBuffer<u8> tmp_buffer;
|
||||
Common::ScratchBuffer<u8> src_buffer;
|
||||
Common::ScratchBuffer<u8> dst_buffer;
|
||||
Common::ScratchBuffer<f32> intermediate_src;
|
||||
Common::ScratchBuffer<f32> intermediate_dst;
|
||||
ConverterFactory converter_factory;
|
||||
};
|
||||
|
||||
SoftwareBlitEngine::SoftwareBlitEngine(MemoryManager& memory_manager_)
|
||||
: memory_manager{memory_manager_} {
|
||||
impl = std::make_unique<BlitEngineImpl>();
|
||||
}
|
||||
|
||||
SoftwareBlitEngine::~SoftwareBlitEngine() = default;
|
||||
|
||||
bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
|
||||
Fermi2D::Config& config) {
|
||||
const auto get_surface_size = [](Fermi2D::Surface& surface, u32 bytes_per_pixel) {
|
||||
if (surface.linear == Fermi2D::MemoryLayout::BlockLinear) {
|
||||
return CalculateSize(true, bytes_per_pixel, surface.width, surface.height,
|
||||
surface.depth, surface.block_height, surface.block_depth);
|
||||
}
|
||||
return static_cast<size_t>(surface.pitch * surface.height);
|
||||
};
|
||||
|
||||
const u32 src_extent_x = config.src_x1 - config.src_x0;
|
||||
const u32 src_extent_y = config.src_y1 - config.src_y0;
|
||||
|
||||
const u32 dst_extent_x = config.dst_x1 - config.dst_x0;
|
||||
const u32 dst_extent_y = config.dst_y1 - config.dst_y0;
|
||||
const auto src_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
|
||||
const auto dst_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(dst.format));
|
||||
const size_t src_size = get_surface_size(src, src_bytes_per_pixel);
|
||||
|
||||
Tegra::Memory::GpuGuestMemory<u8, Tegra::Memory::GuestMemoryFlags::SafeRead> tmp_buffer(
|
||||
memory_manager, src.Address(), src_size, &impl->tmp_buffer);
|
||||
|
||||
const size_t src_copy_size = src_extent_x * src_extent_y * src_bytes_per_pixel;
|
||||
const size_t dst_copy_size = dst_extent_x * dst_extent_y * dst_bytes_per_pixel;
|
||||
|
||||
impl->src_buffer.resize_destructive(src_copy_size);
|
||||
|
||||
const bool no_passthrough =
|
||||
src.format != dst.format || src_extent_x != dst_extent_x || src_extent_y != dst_extent_y;
|
||||
|
||||
const auto conversion_phase_same_format = [&]() {
|
||||
NearestNeighbor(impl->src_buffer, impl->dst_buffer, src_extent_x, src_extent_y,
|
||||
dst_extent_x, dst_extent_y, dst_bytes_per_pixel);
|
||||
};
|
||||
|
||||
const auto conversion_phase_ir = [&]() {
|
||||
auto* input_converter = impl->converter_factory.GetFormatConverter(src.format);
|
||||
impl->intermediate_src.resize_destructive((src_copy_size / src_bytes_per_pixel) *
|
||||
ir_components);
|
||||
impl->intermediate_dst.resize_destructive((dst_copy_size / dst_bytes_per_pixel) *
|
||||
ir_components);
|
||||
input_converter->ConvertTo(impl->src_buffer, impl->intermediate_src);
|
||||
|
||||
if (config.filter != Fermi2D::Filter::Bilinear) {
|
||||
NearestNeighborFast(impl->intermediate_src, impl->intermediate_dst, src_extent_x,
|
||||
src_extent_y, dst_extent_x, dst_extent_y);
|
||||
} else {
|
||||
Bilinear(impl->intermediate_src, impl->intermediate_dst, src_extent_x, src_extent_y,
|
||||
dst_extent_x, dst_extent_y);
|
||||
}
|
||||
|
||||
auto* output_converter = impl->converter_factory.GetFormatConverter(dst.format);
|
||||
output_converter->ConvertFrom(impl->intermediate_dst, impl->dst_buffer);
|
||||
};
|
||||
|
||||
// Do actual Blit
|
||||
|
||||
impl->dst_buffer.resize_destructive(dst_copy_size);
|
||||
if (src.linear == Fermi2D::MemoryLayout::BlockLinear) {
|
||||
UnswizzleSubrect(impl->src_buffer, tmp_buffer, src_bytes_per_pixel, src.width, src.height,
|
||||
src.depth, config.src_x0, config.src_y0, src_extent_x, src_extent_y,
|
||||
src.block_height, src.block_depth, src_extent_x * src_bytes_per_pixel);
|
||||
} else {
|
||||
ProcessPitchLinear<false>(tmp_buffer, impl->src_buffer, src_extent_x, src_extent_y,
|
||||
src.pitch, config.src_x0, config.src_y0, src_bytes_per_pixel);
|
||||
}
|
||||
|
||||
// Conversion Phase
|
||||
if (no_passthrough) {
|
||||
if (src.format != dst.format || config.filter == Fermi2D::Filter::Bilinear) {
|
||||
conversion_phase_ir();
|
||||
} else {
|
||||
conversion_phase_same_format();
|
||||
}
|
||||
} else {
|
||||
impl->dst_buffer.swap(impl->src_buffer);
|
||||
}
|
||||
|
||||
const size_t dst_size = get_surface_size(dst, dst_bytes_per_pixel);
|
||||
Tegra::Memory::GpuGuestMemoryScoped<u8, Tegra::Memory::GuestMemoryFlags::SafeReadWrite>
|
||||
tmp_buffer2(memory_manager, dst.Address(), dst_size, &impl->tmp_buffer);
|
||||
|
||||
if (dst.linear == Fermi2D::MemoryLayout::BlockLinear) {
|
||||
SwizzleSubrect(tmp_buffer2, impl->dst_buffer, dst_bytes_per_pixel, dst.width, dst.height,
|
||||
dst.depth, config.dst_x0, config.dst_y0, dst_extent_x, dst_extent_y,
|
||||
dst.block_height, dst.block_depth, dst_extent_x * dst_bytes_per_pixel);
|
||||
} else {
|
||||
ProcessPitchLinear<true>(impl->dst_buffer, tmp_buffer2, dst_extent_x, dst_extent_y,
|
||||
dst.pitch, config.dst_x0, config.dst_y0,
|
||||
static_cast<size_t>(dst_bytes_per_pixel));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Tegra::Engines::Blitter
|
||||
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
namespace Tegra::Engines::Blitter {
|
||||
|
||||
class SoftwareBlitEngine {
|
||||
public:
|
||||
explicit SoftwareBlitEngine(MemoryManager& memory_manager_);
|
||||
~SoftwareBlitEngine();
|
||||
|
||||
bool Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst, Fermi2D::Config& copy_config);
|
||||
|
||||
private:
|
||||
MemoryManager& memory_manager;
|
||||
struct BlitEngineImpl;
|
||||
std::unique_ptr<BlitEngineImpl> impl;
|
||||
};
|
||||
|
||||
} // namespace Tegra::Engines::Blitter
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
namespace Tegra::Engines::Blitter {
|
||||
|
||||
class Converter {
|
||||
public:
|
||||
virtual void ConvertTo(std::span<const u8> input, std::span<f32> output) = 0;
|
||||
virtual void ConvertFrom(std::span<const f32> input, std::span<u8> output) = 0;
|
||||
virtual ~Converter() = default;
|
||||
};
|
||||
|
||||
class ConverterFactory {
|
||||
public:
|
||||
ConverterFactory();
|
||||
~ConverterFactory();
|
||||
|
||||
Converter* GetFormatConverter(RenderTargetFormat format);
|
||||
|
||||
private:
|
||||
Converter* BuildConverter(RenderTargetFormat format);
|
||||
|
||||
struct ConverterFactoryImpl;
|
||||
std::unique_ptr<ConverterFactoryImpl> impl;
|
||||
};
|
||||
|
||||
} // namespace Tegra::Engines::Blitter
|
||||
@@ -0,0 +1,25 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
add_library(gpu_logging STATIC
|
||||
gpu_logging.cpp
|
||||
gpu_logging.h
|
||||
gpu_state_capture.cpp
|
||||
gpu_state_capture.h
|
||||
qualcomm_debug.cpp
|
||||
qualcomm_debug.h
|
||||
)
|
||||
|
||||
if(ANDROID)
|
||||
target_sources(gpu_logging PRIVATE
|
||||
freedreno_debug.cpp
|
||||
freedreno_debug.h
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(gpu_logging PUBLIC common)
|
||||
|
||||
if(ANDROID)
|
||||
# Link with adrenotools when available for future Qualcomm integration
|
||||
# target_link_libraries(gpu_logging PUBLIC adrenotools)
|
||||
endif()
|
||||
@@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifdef ANDROID
|
||||
|
||||
#include "video_core/gpu_logging/freedreno_debug.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace GPU::Logging::Freedreno {
|
||||
|
||||
bool FreedrenoDebugger::is_initialized = false;
|
||||
|
||||
void FreedrenoDebugger::Initialize() {
|
||||
if (is_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_initialized = true;
|
||||
LOG_INFO(Render_Vulkan, "[Freedreno Debug] Initialized");
|
||||
}
|
||||
|
||||
void FreedrenoDebugger::SetTUDebugFlags(const std::string& flags) {
|
||||
if (flags.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set TU_DEBUG environment variable
|
||||
// Note: This should be set BEFORE Vulkan driver is loaded
|
||||
setenv("TU_DEBUG", flags.c_str(), 1);
|
||||
|
||||
LOG_INFO(Render_Vulkan, "[Freedreno Debug] TU_DEBUG set to: {}", flags);
|
||||
}
|
||||
|
||||
void FreedrenoDebugger::EnableCommandStreamDump(bool frames_only) {
|
||||
// Enable FD_RD_DUMP for command stream capture
|
||||
const char* dump_flags = frames_only ? "frames" : "all";
|
||||
setenv("FD_RD_DUMP", dump_flags, 1);
|
||||
|
||||
LOG_INFO(Render_Vulkan, "[Freedreno Debug] Command stream dump enabled: {}", dump_flags);
|
||||
}
|
||||
|
||||
std::string FreedrenoDebugger::GetBreadcrumbs() {
|
||||
// Breadcrumb reading requires driver-specific implementation
|
||||
// This is a stub for future implementation
|
||||
return "Breadcrumb capture not yet implemented";
|
||||
}
|
||||
|
||||
} // namespace GPU::Logging::Freedreno
|
||||
|
||||
#endif // ANDROID
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef ANDROID
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace GPU::Logging::Freedreno {
|
||||
|
||||
class FreedrenoDebugger {
|
||||
public:
|
||||
// Initialize Freedreno debugging
|
||||
static void Initialize();
|
||||
|
||||
// Set TU_DEBUG environment variable flags
|
||||
static void SetTUDebugFlags(const std::string& flags);
|
||||
|
||||
// Enable command stream dump
|
||||
static void EnableCommandStreamDump(bool frames_only = false);
|
||||
|
||||
// Get breadcrumb information (if available)
|
||||
static std::string GetBreadcrumbs();
|
||||
|
||||
private:
|
||||
static bool is_initialized;
|
||||
};
|
||||
|
||||
} // namespace GPU::Logging::Freedreno
|
||||
|
||||
#endif // ANDROID
|
||||
@@ -0,0 +1,734 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <thread>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/literals.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
namespace GPU::Logging {
|
||||
|
||||
// Static instance
|
||||
static GPULogger* g_instance = nullptr;
|
||||
|
||||
GPULogger& GPULogger::GetInstance() {
|
||||
if (!g_instance) {
|
||||
g_instance = new GPULogger();
|
||||
}
|
||||
return *g_instance;
|
||||
}
|
||||
|
||||
GPULogger::GPULogger() = default;
|
||||
|
||||
GPULogger::~GPULogger() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void GPULogger::Initialize(LogLevel level, DriverType driver) {
|
||||
if (initialized) {
|
||||
LOG_WARNING(Render_Vulkan, "[GPU Logging] Already initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
current_level = level;
|
||||
detected_driver = driver;
|
||||
|
||||
if (current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create log directory
|
||||
using namespace Common::FS;
|
||||
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
|
||||
[[maybe_unused]] const bool log_dir_created = CreateDir(log_dir);
|
||||
|
||||
// Create GPU crashes directory
|
||||
const auto crashes_dir = log_dir / "gpu_crashes";
|
||||
[[maybe_unused]] const bool crashes_dir_created = CreateDir(crashes_dir);
|
||||
|
||||
// Open GPU log file
|
||||
const auto gpu_log_path = log_dir / "eden_gpu.log";
|
||||
|
||||
// Rotate old log
|
||||
const auto old_log_path = log_dir / "eden_gpu.log.old.txt";
|
||||
RemoveFile(old_log_path);
|
||||
[[maybe_unused]] const bool log_renamed = RenameFile(gpu_log_path, old_log_path);
|
||||
|
||||
// Open new log file
|
||||
gpu_log_file = std::make_unique<Common::FS::IOFile>(
|
||||
gpu_log_path, Common::FS::FileAccessMode::Write, Common::FS::FileType::TextFile);
|
||||
|
||||
if (!gpu_log_file->IsOpen()) {
|
||||
LOG_ERROR(Render_Vulkan, "[GPU Logging] Failed to open GPU log file");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize ring buffer
|
||||
call_ring_buffer.resize(ring_buffer_size);
|
||||
|
||||
// Write header
|
||||
const char* driver_name = "Unknown";
|
||||
switch (detected_driver) {
|
||||
case DriverType::Turnip:
|
||||
driver_name = "Turnip (Mesa Freedreno)";
|
||||
break;
|
||||
case DriverType::Qualcomm:
|
||||
driver_name = "Qualcomm Proprietary";
|
||||
break;
|
||||
default:
|
||||
driver_name = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
const char* level_name = "Unknown";
|
||||
switch (current_level) {
|
||||
case LogLevel::Off:
|
||||
level_name = "Off";
|
||||
break;
|
||||
case LogLevel::Errors:
|
||||
level_name = "Errors";
|
||||
break;
|
||||
case LogLevel::Standard:
|
||||
level_name = "Standard";
|
||||
break;
|
||||
case LogLevel::Verbose:
|
||||
level_name = "Verbose";
|
||||
break;
|
||||
case LogLevel::All:
|
||||
level_name = "All";
|
||||
break;
|
||||
}
|
||||
|
||||
const auto header = fmt::format(
|
||||
"=== Eden GPU Logging Started ===\n"
|
||||
"Timestamp: {}\n"
|
||||
"Log Level: {}\n"
|
||||
"Driver: {}\n"
|
||||
"Ring Buffer Size: {}\n"
|
||||
"================================\n\n",
|
||||
FormatTimestamp(std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())),
|
||||
level_name, driver_name, ring_buffer_size);
|
||||
|
||||
WriteToLog(header);
|
||||
|
||||
// Note: Crash handler is initialized independently in EmulationSession::InitializeSystem()
|
||||
// to ensure it remains active even if Vulkan device initialization fails
|
||||
|
||||
initialized = true;
|
||||
LOG_INFO(Render_Vulkan, "[GPU Logging] Initialized with level: {}, driver: {}", level_name,
|
||||
driver_name);
|
||||
}
|
||||
|
||||
void GPULogger::Shutdown() {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Write statistics
|
||||
const auto stats = fmt::format(
|
||||
"\n=== GPU Logging Statistics ===\n"
|
||||
"Total Vulkan Calls: {}\n"
|
||||
"Total Memory Allocations: {}\n"
|
||||
"Total Memory Deallocations: {}\n"
|
||||
"Peak Memory Usage: {}\n"
|
||||
"Current Memory Usage: {}\n"
|
||||
"Log Size: {} bytes\n"
|
||||
"==============================\n",
|
||||
total_vulkan_calls, total_allocations, total_deallocations,
|
||||
FormatMemorySize(peak_allocated_bytes), FormatMemorySize(current_allocated_bytes),
|
||||
bytes_written);
|
||||
|
||||
WriteToLog(stats);
|
||||
|
||||
// Close file
|
||||
if (gpu_log_file) {
|
||||
gpu_log_file->Flush();
|
||||
gpu_log_file->Close();
|
||||
gpu_log_file.reset();
|
||||
}
|
||||
|
||||
// Note: Crash handler is NOT shut down here - it remains active throughout app lifetime
|
||||
// It will be shut down when EmulationSession is destroyed
|
||||
|
||||
initialized = false;
|
||||
LOG_INFO(Render_Vulkan, "[GPU Logging] Shutdown complete");
|
||||
}
|
||||
|
||||
void GPULogger::LogVulkanCall(const std::string& call_name, const std::string& params,
|
||||
int result) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!track_vulkan_calls) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only log all calls in Verbose or All mode
|
||||
if (current_level != LogLevel::Verbose && current_level != LogLevel::All) {
|
||||
// In Standard mode, only log important calls
|
||||
if (call_name.find("vkCmd") == std::string::npos &&
|
||||
call_name.find("vkCreate") == std::string::npos &&
|
||||
call_name.find("vkDestroy") == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
const auto thread_id = static_cast<u32>(std::hash<std::thread::id>{}(std::this_thread::get_id()));
|
||||
|
||||
// Add to ring buffer
|
||||
{
|
||||
std::lock_guard lock(ring_buffer_mutex);
|
||||
call_ring_buffer[ring_buffer_index] = {
|
||||
.timestamp = timestamp,
|
||||
.call_name = call_name,
|
||||
.parameters = params,
|
||||
.result = result,
|
||||
.thread_id = thread_id,
|
||||
};
|
||||
ring_buffer_index = (ring_buffer_index + 1) % ring_buffer_size;
|
||||
total_vulkan_calls++;
|
||||
}
|
||||
|
||||
// Log to file
|
||||
const auto log_entry =
|
||||
fmt::format("[{}] [Vulkan] [Thread:{}] {}({}) -> {}\n", FormatTimestamp(timestamp),
|
||||
thread_id, call_name, params, result);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
void GPULogger::LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!track_memory) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const bool is_device_local = (memory_flags & 0x1) != 0; // VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
|
||||
const bool is_host_visible = (memory_flags & 0x2) != 0; // VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||
|
||||
{
|
||||
std::lock_guard lock(memory_mutex);
|
||||
memory_allocations[memory] = {
|
||||
.memory_handle = memory,
|
||||
.size = size,
|
||||
.memory_flags = memory_flags,
|
||||
.timestamp = timestamp,
|
||||
.is_device_local = is_device_local,
|
||||
.is_host_visible = is_host_visible,
|
||||
};
|
||||
|
||||
total_allocations++;
|
||||
current_allocated_bytes += size;
|
||||
if (current_allocated_bytes > peak_allocated_bytes) {
|
||||
peak_allocated_bytes = current_allocated_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
const auto log_entry = fmt::format(
|
||||
"[{}] [Memory] Allocated {} at 0x{:x} (Device:{}, Host:{})\n", FormatTimestamp(timestamp),
|
||||
FormatMemorySize(size), memory, is_device_local ? "Yes" : "No",
|
||||
is_host_visible ? "Yes" : "No");
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
void GPULogger::LogMemoryDeallocation(uintptr_t memory) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!track_memory) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
u64 size = 0;
|
||||
{
|
||||
std::lock_guard lock(memory_mutex);
|
||||
auto it = memory_allocations.find(memory);
|
||||
if (it != memory_allocations.end()) {
|
||||
size = it->second.size;
|
||||
current_allocated_bytes -= size;
|
||||
memory_allocations.erase(it);
|
||||
total_deallocations++;
|
||||
}
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
const auto log_entry =
|
||||
fmt::format("[{}] [Memory] Deallocated {} at 0x{:x}\n", FormatTimestamp(timestamp),
|
||||
FormatMemorySize(size), memory);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
}
|
||||
|
||||
void GPULogger::LogShaderCompilation(const std::string& shader_name,
|
||||
const std::string& shader_info,
|
||||
std::span<const u32> spirv_code) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dump_shaders && current_level < LogLevel::Verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const auto log_entry = fmt::format("[{}] [Shader] Compiled: {} ({})\n",
|
||||
FormatTimestamp(timestamp), shader_name, shader_info);
|
||||
WriteToLog(log_entry);
|
||||
|
||||
// Dump SPIR-V binary if enabled and we have data
|
||||
if (dump_shaders && !spirv_code.empty()) {
|
||||
using namespace Common::FS;
|
||||
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
|
||||
const auto shaders_dir = log_dir / "shaders";
|
||||
|
||||
// Create directory on first dump
|
||||
if (!shader_dump_dir_created) {
|
||||
[[maybe_unused]] const bool created = CreateDir(shaders_dir);
|
||||
shader_dump_dir_created = true;
|
||||
}
|
||||
|
||||
// Write SPIR-V binary file
|
||||
const auto shader_path = shaders_dir / fmt::format("{}.spv", shader_name);
|
||||
auto shader_file = std::make_unique<Common::FS::IOFile>(
|
||||
shader_path, FileAccessMode::Write, FileType::BinaryFile);
|
||||
|
||||
if (shader_file->IsOpen()) {
|
||||
const size_t bytes_to_write = spirv_code.size() * sizeof(u32);
|
||||
static_cast<void>(shader_file->WriteSpan(spirv_code));
|
||||
shader_file->Close();
|
||||
|
||||
const auto dump_log = fmt::format("[{}] [Shader] Dumped SPIR-V: {} ({} bytes)\n",
|
||||
FormatTimestamp(timestamp), shader_path.string(), bytes_to_write);
|
||||
WriteToLog(dump_log);
|
||||
} else {
|
||||
LOG_WARNING(Render_Vulkan, "[GPU Logging] Failed to dump shader: {}", shader_path.string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GPULogger::LogPipelineStateChange(const std::string& state_info) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store pipeline state for crash dumps
|
||||
{
|
||||
std::lock_guard lock(state_mutex);
|
||||
stored_pipeline_state = state_info;
|
||||
}
|
||||
|
||||
if (current_level < LogLevel::Verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const auto log_entry =
|
||||
fmt::format("[{}] [Pipeline] State change: {}\n", FormatTimestamp(timestamp), state_info);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
void GPULogger::LogDriverDebugInfo(const std::string& debug_info) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store driver debug info for crash dumps
|
||||
{
|
||||
std::lock_guard lock(state_mutex);
|
||||
stored_driver_debug_info = debug_info;
|
||||
}
|
||||
|
||||
if (!capture_driver_debug) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const auto log_entry =
|
||||
fmt::format("[{}] [Driver] {}\n", FormatTimestamp(timestamp), debug_info);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
void GPULogger::LogExtensionUsage(const std::string& extension_name, const std::string& function_name) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
bool is_first_use = false;
|
||||
{
|
||||
std::lock_guard lock(extension_mutex);
|
||||
auto [iter, inserted] = used_extensions.insert(extension_name);
|
||||
is_first_use = inserted;
|
||||
}
|
||||
|
||||
if (is_first_use) {
|
||||
const auto log_entry = fmt::format("[{}] [Extension] First use of {} in {}\n",
|
||||
FormatTimestamp(timestamp), extension_name, function_name);
|
||||
WriteToLog(log_entry);
|
||||
LOG_INFO(Render_Vulkan, "[GPU Logging] First use of extension {} in {}",
|
||||
extension_name, function_name);
|
||||
} else if (current_level >= LogLevel::Verbose) {
|
||||
const auto log_entry = fmt::format("[{}] [Extension] {} used in {}\n",
|
||||
FormatTimestamp(timestamp), extension_name, function_name);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
}
|
||||
|
||||
void GPULogger::LogRenderPassBegin(const std::string& render_pass_info) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const auto log_entry = fmt::format("[{}] [RenderPass] Begin: {}\n",
|
||||
FormatTimestamp(timestamp), render_pass_info);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
void GPULogger::LogRenderPassEnd() {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const auto log_entry = fmt::format("[{}] [RenderPass] End\n", FormatTimestamp(timestamp));
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
void GPULogger::LogPipelineBind(bool is_compute, const std::string& pipeline_info) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const char* pipeline_type = is_compute ? "Compute" : "Graphics";
|
||||
const auto log_entry = fmt::format("[{}] [Pipeline] Bind {} pipeline: {}\n",
|
||||
FormatTimestamp(timestamp), pipeline_type, pipeline_info);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
void GPULogger::LogDescriptorSetBind(const std::string& descriptor_info) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_level < LogLevel::Verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const auto log_entry = fmt::format("[{}] [Descriptor] Bind: {}\n",
|
||||
FormatTimestamp(timestamp), descriptor_info);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
void GPULogger::LogPipelineBarrier(const std::string& barrier_info) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_level < LogLevel::Verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const auto log_entry = fmt::format("[{}] [Barrier] {}\n",
|
||||
FormatTimestamp(timestamp), barrier_info);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
void GPULogger::LogImageOperation(const std::string& operation, const std::string& image_info) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const auto log_entry = fmt::format("[{}] [Image] {}: {}\n",
|
||||
FormatTimestamp(timestamp), operation, image_info);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
void GPULogger::LogClearOperation(const std::string& clear_info) {
|
||||
if (!initialized || current_level == LogLevel::Off) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
|
||||
const auto log_entry = fmt::format("[{}] [Clear] {}\n",
|
||||
FormatTimestamp(timestamp), clear_info);
|
||||
WriteToLog(log_entry);
|
||||
}
|
||||
|
||||
GPUStateSnapshot GPULogger::GetCurrentSnapshot() {
|
||||
GPUStateSnapshot snapshot;
|
||||
snapshot.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch());
|
||||
snapshot.driver_type = detected_driver;
|
||||
|
||||
// Capture recent Vulkan calls
|
||||
{
|
||||
std::lock_guard lock(ring_buffer_mutex);
|
||||
snapshot.recent_calls.reserve(ring_buffer_size);
|
||||
|
||||
// Copy from current position to end
|
||||
for (size_t i = ring_buffer_index; i < ring_buffer_size; ++i) {
|
||||
if (!call_ring_buffer[i].call_name.empty()) {
|
||||
snapshot.recent_calls.push_back(call_ring_buffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy from beginning to current position
|
||||
for (size_t i = 0; i < ring_buffer_index; ++i) {
|
||||
if (!call_ring_buffer[i].call_name.empty()) {
|
||||
snapshot.recent_calls.push_back(call_ring_buffer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Capture memory status
|
||||
{
|
||||
std::lock_guard lock(memory_mutex);
|
||||
snapshot.memory_status = fmt::format(
|
||||
"Total Allocations: {}\n"
|
||||
"Current Usage: {}\n"
|
||||
"Peak Usage: {}\n"
|
||||
"Active Allocations: {}\n",
|
||||
total_allocations, FormatMemorySize(current_allocated_bytes),
|
||||
FormatMemorySize(peak_allocated_bytes), memory_allocations.size());
|
||||
}
|
||||
|
||||
// Capture stored pipeline and driver debug info
|
||||
{
|
||||
std::lock_guard lock(state_mutex);
|
||||
snapshot.pipeline_state = stored_pipeline_state.empty() ?
|
||||
"No pipeline state logged yet" : stored_pipeline_state;
|
||||
snapshot.driver_debug_info = stored_driver_debug_info.empty() ?
|
||||
"No driver debug info logged yet" : stored_driver_debug_info;
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void GPULogger::DumpStateToFile(const std::string& crash_reason) {
|
||||
using namespace Common::FS;
|
||||
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
|
||||
const auto crashes_dir = log_dir / "gpu_crashes";
|
||||
[[maybe_unused]] const bool crashes_dir_created = CreateDir(crashes_dir);
|
||||
|
||||
// Generate crash dump filename with timestamp
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
now.time_since_epoch()).count();
|
||||
const auto crash_dump_path = crashes_dir / fmt::format("crash_{}.gpu-dump", timestamp);
|
||||
|
||||
auto crash_file =
|
||||
std::make_unique<Common::FS::IOFile>(crash_dump_path, FileAccessMode::Write, FileType::TextFile);
|
||||
|
||||
if (!crash_file->IsOpen()) {
|
||||
LOG_ERROR(Render_Vulkan, "[GPU Logging] Failed to create crash dump file");
|
||||
return;
|
||||
}
|
||||
|
||||
auto snapshot = GetCurrentSnapshot();
|
||||
|
||||
const char* driver_name = "Unknown";
|
||||
switch (snapshot.driver_type) {
|
||||
case DriverType::Turnip:
|
||||
driver_name = "Turnip (Mesa Freedreno)";
|
||||
break;
|
||||
case DriverType::Qualcomm:
|
||||
driver_name = "Qualcomm Proprietary";
|
||||
break;
|
||||
default:
|
||||
driver_name = "Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
// Write crash dump header
|
||||
const auto header = fmt::format(
|
||||
"=== GPU CRASH DUMP ===\n"
|
||||
"Timestamp: {}\n"
|
||||
"Reason: {}\n"
|
||||
"Driver: {}\n"
|
||||
"\n",
|
||||
FormatTimestamp(snapshot.timestamp), crash_reason, driver_name);
|
||||
static_cast<void>(crash_file->WriteString(header));
|
||||
|
||||
// Write recent Vulkan calls
|
||||
static_cast<void>(crash_file->WriteString(fmt::format("=== RECENT VULKAN API CALLS (Last {}) ===\n",
|
||||
snapshot.recent_calls.size())));
|
||||
for (const auto& call : snapshot.recent_calls) {
|
||||
const auto call_str =
|
||||
fmt::format("[{}] [Thread:{}] {}({}) -> {}\n", FormatTimestamp(call.timestamp),
|
||||
call.thread_id, call.call_name, call.parameters, call.result);
|
||||
static_cast<void>(crash_file->WriteString(call_str));
|
||||
}
|
||||
static_cast<void>(crash_file->WriteString("\n"));
|
||||
|
||||
// Write memory status
|
||||
static_cast<void>(crash_file->WriteString("=== MEMORY STATUS ===\n"));
|
||||
static_cast<void>(crash_file->WriteString(snapshot.memory_status));
|
||||
static_cast<void>(crash_file->WriteString("\n"));
|
||||
|
||||
// Write pipeline state
|
||||
static_cast<void>(crash_file->WriteString("=== PIPELINE STATE ===\n"));
|
||||
static_cast<void>(crash_file->WriteString(snapshot.pipeline_state));
|
||||
static_cast<void>(crash_file->WriteString("\n"));
|
||||
|
||||
// Write driver debug info
|
||||
static_cast<void>(crash_file->WriteString("=== DRIVER DEBUG INFO ===\n"));
|
||||
static_cast<void>(crash_file->WriteString(snapshot.driver_debug_info));
|
||||
static_cast<void>(crash_file->WriteString("\n"));
|
||||
|
||||
crash_file->Flush();
|
||||
crash_file->Close();
|
||||
|
||||
LOG_CRITICAL(Render_Vulkan, "[GPU Logging] Crash dump written to: {}",
|
||||
crash_dump_path.string());
|
||||
}
|
||||
|
||||
void GPULogger::SetLogLevel(LogLevel level) {
|
||||
current_level = level;
|
||||
}
|
||||
|
||||
void GPULogger::EnableVulkanCallTracking(bool enabled) {
|
||||
track_vulkan_calls = enabled;
|
||||
}
|
||||
|
||||
void GPULogger::EnableShaderDumps(bool enabled) {
|
||||
dump_shaders = enabled;
|
||||
}
|
||||
|
||||
void GPULogger::EnableMemoryTracking(bool enabled) {
|
||||
track_memory = enabled;
|
||||
}
|
||||
|
||||
void GPULogger::EnableDriverDebugInfo(bool enabled) {
|
||||
capture_driver_debug = enabled;
|
||||
}
|
||||
|
||||
void GPULogger::SetRingBufferSize(size_t entries) {
|
||||
std::lock_guard lock(ring_buffer_mutex);
|
||||
ring_buffer_size = entries;
|
||||
call_ring_buffer.resize(entries);
|
||||
ring_buffer_index = 0;
|
||||
}
|
||||
|
||||
LogLevel GPULogger::GetLogLevel() const {
|
||||
return current_level;
|
||||
}
|
||||
|
||||
DriverType GPULogger::GetDriverType() const {
|
||||
return detected_driver;
|
||||
}
|
||||
|
||||
std::string GPULogger::GetStatistics() const {
|
||||
std::lock_guard lock(memory_mutex);
|
||||
return fmt::format(
|
||||
"Vulkan Calls: {}, Allocations: {}, Deallocations: {}, "
|
||||
"Current Memory: {}, Peak Memory: {}",
|
||||
total_vulkan_calls, total_allocations, total_deallocations,
|
||||
FormatMemorySize(current_allocated_bytes), FormatMemorySize(peak_allocated_bytes));
|
||||
}
|
||||
|
||||
bool GPULogger::IsInitialized() const {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
void GPULogger::WriteToLog(const std::string& message) {
|
||||
if (!gpu_log_file || !gpu_log_file->IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(file_mutex);
|
||||
bytes_written += gpu_log_file->WriteString(message);
|
||||
|
||||
// Flush on errors or if we've written a lot
|
||||
using namespace Common::Literals;
|
||||
if (bytes_written % (1_MiB) == 0) {
|
||||
gpu_log_file->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
std::string GPULogger::FormatTimestamp(std::chrono::microseconds timestamp) const {
|
||||
const auto seconds = timestamp.count() / 1000000;
|
||||
const auto microseconds = timestamp.count() % 1000000;
|
||||
return fmt::format("{:4d}.{:06d}", seconds, microseconds);
|
||||
}
|
||||
|
||||
std::string GPULogger::FormatMemorySize(u64 bytes) const {
|
||||
using namespace Common::Literals;
|
||||
if (bytes >= 1_GiB) {
|
||||
return fmt::format("{:.2f} GiB", static_cast<double>(bytes) / (1_GiB));
|
||||
} else if (bytes >= 1_MiB) {
|
||||
return fmt::format("{:.2f} MiB", static_cast<double>(bytes) / (1_MiB));
|
||||
} else if (bytes >= 1_KiB) {
|
||||
return fmt::format("{:.2f} KiB", static_cast<double>(bytes) / (1_KiB));
|
||||
} else {
|
||||
return fmt::format("{} B", bytes);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace GPU::Logging
|
||||
@@ -0,0 +1,199 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
// Forward declarations
|
||||
namespace Common::FS {
|
||||
class IOFile;
|
||||
}
|
||||
|
||||
namespace Vulkan {
|
||||
class Device;
|
||||
}
|
||||
|
||||
namespace GPU::Logging {
|
||||
|
||||
enum class LogLevel : u8 {
|
||||
Off = 0,
|
||||
Errors = 1,
|
||||
Standard = 2,
|
||||
Verbose = 3,
|
||||
All = 4,
|
||||
};
|
||||
|
||||
enum class DriverType : u8 {
|
||||
Unknown,
|
||||
Turnip, // Mesa Turnip driver
|
||||
Qualcomm, // Qualcomm proprietary driver
|
||||
};
|
||||
|
||||
// Ring buffer entry for tracking Vulkan API calls
|
||||
struct VulkanCallEntry {
|
||||
std::chrono::microseconds timestamp;
|
||||
std::string call_name; // e.g., "vkCmdDraw", "vkBeginRenderPass"
|
||||
std::string parameters; // Serialized parameters
|
||||
int result; // VkResult return code
|
||||
u32 thread_id;
|
||||
};
|
||||
|
||||
// GPU memory allocation entry
|
||||
struct MemoryAllocationEntry {
|
||||
uintptr_t memory_handle;
|
||||
u64 size;
|
||||
u32 memory_flags;
|
||||
std::chrono::microseconds timestamp;
|
||||
bool is_device_local;
|
||||
bool is_host_visible;
|
||||
};
|
||||
|
||||
// GPU state snapshot for crash dumps
|
||||
struct GPUStateSnapshot {
|
||||
std::vector<VulkanCallEntry> recent_calls; // Last N API calls
|
||||
std::vector<std::string> active_shaders; // Currently bound shaders
|
||||
std::string pipeline_state; // Current pipeline state
|
||||
std::string memory_status; // Current memory allocations
|
||||
std::string driver_debug_info; // Driver-specific debug data
|
||||
std::chrono::microseconds timestamp;
|
||||
DriverType driver_type;
|
||||
};
|
||||
|
||||
/// Main GPU logging system singleton
|
||||
class GPULogger {
|
||||
public:
|
||||
static GPULogger& GetInstance();
|
||||
|
||||
// Prevent copying
|
||||
GPULogger(const GPULogger&) = delete;
|
||||
GPULogger& operator=(const GPULogger&) = delete;
|
||||
|
||||
// Initialization and control
|
||||
void Initialize(LogLevel level, DriverType detected_driver = DriverType::Unknown);
|
||||
void Shutdown();
|
||||
|
||||
// Logging API
|
||||
void LogVulkanCall(const std::string& call_name, const std::string& params, int result);
|
||||
void LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags);
|
||||
void LogMemoryDeallocation(uintptr_t memory);
|
||||
void LogShaderCompilation(const std::string& shader_name, const std::string& shader_info,
|
||||
std::span<const u32> spirv_code = {});
|
||||
void LogPipelineStateChange(const std::string& state_info);
|
||||
void LogDriverDebugInfo(const std::string& debug_info);
|
||||
|
||||
// Extension usage tracking
|
||||
void LogExtensionUsage(const std::string& extension_name, const std::string& function_name);
|
||||
|
||||
// Render pass logging
|
||||
void LogRenderPassBegin(const std::string& render_pass_info);
|
||||
void LogRenderPassEnd();
|
||||
|
||||
// Pipeline binding logging
|
||||
void LogPipelineBind(bool is_compute, const std::string& pipeline_info);
|
||||
|
||||
// Descriptor set binding logging
|
||||
void LogDescriptorSetBind(const std::string& descriptor_info);
|
||||
|
||||
// Pipeline barrier logging
|
||||
void LogPipelineBarrier(const std::string& barrier_info);
|
||||
|
||||
// Image operation logging
|
||||
void LogImageOperation(const std::string& operation, const std::string& image_info);
|
||||
|
||||
// Clear operation logging
|
||||
void LogClearOperation(const std::string& clear_info);
|
||||
|
||||
// Crash handling
|
||||
GPUStateSnapshot GetCurrentSnapshot();
|
||||
void DumpStateToFile(const std::string& crash_reason);
|
||||
|
||||
// Settings
|
||||
void SetLogLevel(LogLevel level);
|
||||
void EnableVulkanCallTracking(bool enabled);
|
||||
void EnableShaderDumps(bool enabled);
|
||||
void EnableMemoryTracking(bool enabled);
|
||||
void EnableDriverDebugInfo(bool enabled);
|
||||
void SetRingBufferSize(size_t entries);
|
||||
|
||||
// Query
|
||||
LogLevel GetLogLevel() const;
|
||||
DriverType GetDriverType() const;
|
||||
std::string GetStatistics() const;
|
||||
bool IsInitialized() const;
|
||||
|
||||
private:
|
||||
GPULogger();
|
||||
~GPULogger();
|
||||
|
||||
// Helper functions
|
||||
void WriteToLog(const std::string& message);
|
||||
void RotateLogFile();
|
||||
std::string FormatTimestamp(std::chrono::microseconds timestamp) const;
|
||||
std::string FormatMemorySize(u64 bytes) const;
|
||||
|
||||
// State
|
||||
bool initialized = false;
|
||||
LogLevel current_level = LogLevel::Off;
|
||||
DriverType detected_driver = DriverType::Unknown;
|
||||
|
||||
// Ring buffer for API calls
|
||||
std::vector<VulkanCallEntry> call_ring_buffer;
|
||||
size_t ring_buffer_index = 0;
|
||||
size_t ring_buffer_size = 512;
|
||||
mutable std::mutex ring_buffer_mutex;
|
||||
|
||||
// Memory tracking
|
||||
std::unordered_map<uintptr_t, MemoryAllocationEntry> memory_allocations;
|
||||
mutable std::mutex memory_mutex;
|
||||
|
||||
// Statistics
|
||||
u64 total_vulkan_calls = 0;
|
||||
u64 total_allocations = 0;
|
||||
u64 total_deallocations = 0;
|
||||
u64 current_allocated_bytes = 0;
|
||||
u64 peak_allocated_bytes = 0;
|
||||
|
||||
// File backend for GPU logs
|
||||
std::unique_ptr<Common::FS::IOFile> gpu_log_file;
|
||||
mutable std::mutex file_mutex;
|
||||
u64 bytes_written = 0;
|
||||
|
||||
// Feature flags
|
||||
bool track_vulkan_calls = true;
|
||||
bool dump_shaders = false;
|
||||
bool track_memory = false;
|
||||
bool capture_driver_debug = false;
|
||||
|
||||
// Extension usage tracking
|
||||
std::set<std::string> used_extensions;
|
||||
mutable std::mutex extension_mutex;
|
||||
|
||||
// Shader dump directory (created on demand)
|
||||
bool shader_dump_dir_created = false;
|
||||
|
||||
// Stored state for crash dumps
|
||||
std::string stored_driver_debug_info;
|
||||
std::string stored_pipeline_state;
|
||||
mutable std::mutex state_mutex;
|
||||
};
|
||||
|
||||
// Helper to get stage name from index
|
||||
inline const char* GetShaderStageName(size_t stage_index) {
|
||||
static constexpr std::array<const char*, 5> stage_names{
|
||||
"vertex", "tess_control", "tess_eval", "geometry", "fragment"
|
||||
};
|
||||
return stage_index < stage_names.size() ? stage_names[stage_index] : "unknown";
|
||||
}
|
||||
|
||||
} // namespace GPU::Logging
|
||||
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "video_core/gpu_logging/gpu_state_capture.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace GPU::Logging {
|
||||
|
||||
GPUStateSnapshot GPUStateCapture::CaptureState() {
|
||||
return GPULogger::GetInstance().GetCurrentSnapshot();
|
||||
}
|
||||
|
||||
std::string GPUStateCapture::SerializeState(const GPUStateSnapshot& snapshot) {
|
||||
std::string result;
|
||||
|
||||
result += "=== GPU STATE SNAPSHOT ===\n\n";
|
||||
|
||||
result += fmt::format("Driver: {}\n", static_cast<int>(snapshot.driver_type));
|
||||
result += fmt::format("Recent Calls: {}\n\n", snapshot.recent_calls.size());
|
||||
|
||||
result += "=== RECENT VULKAN CALLS ===\n";
|
||||
for (const auto& call : snapshot.recent_calls) {
|
||||
result += fmt::format("{}: {}({}) -> {}\n", call.timestamp.count(), call.call_name,
|
||||
call.parameters, call.result);
|
||||
}
|
||||
|
||||
result += "\n=== MEMORY STATUS ===\n";
|
||||
result += snapshot.memory_status;
|
||||
|
||||
result += "\n=== PIPELINE STATE ===\n";
|
||||
result += snapshot.pipeline_state;
|
||||
|
||||
result += "\n=== DRIVER DEBUG INFO ===\n";
|
||||
result += snapshot.driver_debug_info;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void GPUStateCapture::WriteCrashDump(const std::string& crash_reason) {
|
||||
GPULogger::GetInstance().DumpStateToFile(crash_reason);
|
||||
}
|
||||
|
||||
} // namespace GPU::Logging
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
namespace GPU::Logging {
|
||||
|
||||
class GPUStateCapture {
|
||||
public:
|
||||
// Capture current GPU state from logging system
|
||||
static GPUStateSnapshot CaptureState();
|
||||
|
||||
// Serialize state to human-readable format
|
||||
static std::string SerializeState(const GPUStateSnapshot& snapshot);
|
||||
|
||||
// Write detailed crash dump (implemented in GPULogger)
|
||||
static void WriteCrashDump(const std::string& crash_reason);
|
||||
};
|
||||
|
||||
} // namespace GPU::Logging
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "video_core/gpu_logging/qualcomm_debug.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace GPU::Logging::Qualcomm {
|
||||
|
||||
bool QualcommDebugger::is_initialized = false;
|
||||
|
||||
void QualcommDebugger::Initialize() {
|
||||
if (is_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_initialized = true;
|
||||
LOG_INFO(Render_Vulkan, "[Qualcomm Debug] Initialized (stub)");
|
||||
}
|
||||
|
||||
std::string QualcommDebugger::GetDebugInfo() {
|
||||
// Stub for future Qualcomm proprietary driver debug extension support
|
||||
// This requires libadrenotools integration and Qualcomm-specific APIs
|
||||
return "Qualcomm debug info not yet implemented";
|
||||
}
|
||||
|
||||
} // namespace GPU::Logging::Qualcomm
|
||||
@@ -0,0 +1,22 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace GPU::Logging::Qualcomm {
|
||||
|
||||
class QualcommDebugger {
|
||||
public:
|
||||
// Initialize Qualcomm debugging (stub for future implementation)
|
||||
static void Initialize();
|
||||
|
||||
// Get debug information from Qualcomm driver
|
||||
static std::string GetDebugInfo();
|
||||
|
||||
private:
|
||||
static bool is_initialized;
|
||||
};
|
||||
|
||||
} // namespace GPU::Logging::Qualcomm
|
||||
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr)
|
||||
@@ -19,7 +19,6 @@ set(SHADER_FILES
|
||||
block_linear_unswizzle_2d.comp
|
||||
block_linear_unswizzle_3d.comp
|
||||
block_linear_unswizzle_3d_bcn.comp
|
||||
convert_abgr8_srgb_to_d24s8.frag
|
||||
convert_abgr8_to_d24s8.frag
|
||||
convert_abgr8_to_d32f.frag
|
||||
convert_d32f_to_abgr8.frag
|
||||
@@ -77,14 +76,6 @@ set(SHADER_FILES
|
||||
vulkan_quad_indexed.comp
|
||||
vulkan_turbo_mode.comp
|
||||
vulkan_uint8.comp
|
||||
convert_rgba8_to_bgra8.frag
|
||||
convert_yuv420_to_rgb.comp
|
||||
convert_rgb_to_yuv420.comp
|
||||
convert_bc7_to_rgba8.comp
|
||||
convert_astc_hdr_to_rgba16f.comp
|
||||
convert_rgba16f_to_rgba8.frag
|
||||
dither_temporal.frag
|
||||
dynamic_resolution_scale.comp
|
||||
)
|
||||
|
||||
if (PLATFORM_HAIKU)
|
||||
|
||||
@@ -83,6 +83,12 @@ int result_index = 0;
|
||||
uint result_vector_max_index;
|
||||
bool result_limit_reached = false;
|
||||
|
||||
// avoid intermediate result_vector storage during color decode phase
|
||||
bool write_color_values = false;
|
||||
uint color_values_direct[32];
|
||||
uint color_out_index = 0;
|
||||
uint color_num_values = 0;
|
||||
|
||||
// EncodingData helpers
|
||||
uint Encoding(EncodingData val) {
|
||||
return bitfieldExtract(val.data, 0, 8);
|
||||
@@ -114,9 +120,110 @@ EncodingData CreateEncodingData(uint encoding, uint num_bits, uint bit_val, uint
|
||||
return EncodingData(((encoding) << 0u) | ((num_bits) << 8u) |
|
||||
((bit_val) << 16u) | ((quint_trit_val) << 24u));
|
||||
}
|
||||
uint ReplicateBitTo9(uint bit);
|
||||
uint FastReplicateTo8(uint value, uint num_bits);
|
||||
|
||||
void EmitColorValue(EncodingData val) {
|
||||
// write directly to color_values_direct[]
|
||||
const uint encoding = Encoding(val);
|
||||
const uint bitlen = NumBits(val);
|
||||
const uint bitval = BitValue(val);
|
||||
|
||||
if (encoding == JUST_BITS) {
|
||||
color_values_direct[++color_out_index] = FastReplicateTo8(bitval, bitlen);
|
||||
return;
|
||||
}
|
||||
|
||||
uint A = ReplicateBitTo9((bitval & 1));
|
||||
uint B = 0, C = 0, D = QuintTritValue(val);
|
||||
|
||||
if (encoding == TRIT) {
|
||||
switch (bitlen) {
|
||||
case 1:
|
||||
C = 204;
|
||||
break;
|
||||
case 2: {
|
||||
C = 93;
|
||||
const uint b = (bitval >> 1) & 1;
|
||||
B = (b << 8) | (b << 4) | (b << 2) | (b << 1);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
C = 44;
|
||||
const uint cb = (bitval >> 1) & 3;
|
||||
B = (cb << 7) | (cb << 2) | cb;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
C = 22;
|
||||
const uint dcb = (bitval >> 1) & 7;
|
||||
B = (dcb << 6) | dcb;
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
C = 11;
|
||||
const uint edcb = (bitval >> 1) & 0xF;
|
||||
B = (edcb << 5) | (edcb >> 2);
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
C = 5;
|
||||
const uint fedcb = (bitval >> 1) & 0x1F;
|
||||
B = (fedcb << 4) | (fedcb >> 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else { // QUINT
|
||||
switch (bitlen) {
|
||||
case 1:
|
||||
C = 113;
|
||||
break;
|
||||
case 2: {
|
||||
C = 54;
|
||||
const uint b = (bitval >> 1) & 1;
|
||||
B = (b << 8) | (b << 3) | (b << 2);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
C = 26;
|
||||
const uint cb = (bitval >> 1) & 3;
|
||||
B = (cb << 7) | (cb << 1) | (cb >> 1);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
C = 13;
|
||||
const uint dcb = (bitval >> 1) & 7;
|
||||
B = (dcb << 6) | (dcb >> 1);
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
C = 6;
|
||||
const uint edcb = (bitval >> 1) & 0xF;
|
||||
B = (edcb << 5) | (edcb >> 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint T = (D * C) + B;
|
||||
T ^= A;
|
||||
T = (A & 0x80) | (T >> 2);
|
||||
color_values_direct[++color_out_index] = T;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ResultEmplaceBack(EncodingData val) {
|
||||
if (write_color_values) {
|
||||
if (color_out_index >= color_num_values) {
|
||||
// avoid decoding more than needed by this phase
|
||||
result_limit_reached = true;
|
||||
return;
|
||||
}
|
||||
EmitColorValue(val);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result_index >= result_vector_max_index) {
|
||||
// Alert callers to avoid decoding more than needed by this phase
|
||||
result_limit_reached = true;
|
||||
@@ -196,33 +303,36 @@ uint Hash52(uint p) {
|
||||
p ^= p >> 17;
|
||||
return p;
|
||||
}
|
||||
struct PartitionTable {
|
||||
uint s1, s2, s3, s4, s5, s6, s7, s8;
|
||||
uint rnum;
|
||||
bool small_block;
|
||||
};
|
||||
|
||||
uint Select2DPartition(uint seed, uint x, uint y, uint partition_count) {
|
||||
if ((block_dims.y * block_dims.x) < 32) {
|
||||
x <<= 1;
|
||||
y <<= 1;
|
||||
}
|
||||
PartitionTable GetPartitionTable(uint seed, uint partition_count) {
|
||||
PartitionTable pt;
|
||||
pt.small_block = (block_dims.y * block_dims.x) < 32;
|
||||
|
||||
seed += (partition_count - 1) * 1024;
|
||||
uint rnum = Hash52(uint(seed));
|
||||
pt.rnum = rnum;
|
||||
|
||||
const uint rnum = Hash52(uint(seed));
|
||||
uint seed1 = uint(rnum & 0xF);
|
||||
uint seed2 = uint((rnum >> 4) & 0xF);
|
||||
uint seed3 = uint((rnum >> 8) & 0xF);
|
||||
uint seed4 = uint((rnum >> 12) & 0xF);
|
||||
uint seed5 = uint((rnum >> 16) & 0xF);
|
||||
uint seed6 = uint((rnum >> 20) & 0xF);
|
||||
uint seed7 = uint((rnum >> 24) & 0xF);
|
||||
uint seed8 = uint((rnum >> 28) & 0xF);
|
||||
|
||||
seed1 = (seed1 * seed1);
|
||||
seed2 = (seed2 * seed2);
|
||||
seed3 = (seed3 * seed3);
|
||||
seed4 = (seed4 * seed4);
|
||||
seed5 = (seed5 * seed5);
|
||||
seed6 = (seed6 * seed6);
|
||||
seed7 = (seed7 * seed7);
|
||||
seed8 = (seed8 * seed8);
|
||||
uint seed1 = (rnum & 0xF);
|
||||
seed1 *= seed1;
|
||||
uint seed2 = (rnum >> 4) & 0xF;
|
||||
seed2 *= seed2;
|
||||
uint seed3 = (rnum >> 8) & 0xF;
|
||||
seed3 *= seed3;
|
||||
uint seed4 = (rnum >> 12) & 0xF;
|
||||
seed4 *= seed4;
|
||||
uint seed5 = (rnum >> 16) & 0xF;
|
||||
seed5 *= seed5;
|
||||
uint seed6 = (rnum >> 20) & 0xF;
|
||||
seed6 *= seed6;
|
||||
uint seed7 = (rnum >> 24) & 0xF;
|
||||
seed7 *= seed7;
|
||||
uint seed8 = (rnum >> 28) & 0xF;
|
||||
seed8 *= seed8;
|
||||
|
||||
uint sh1, sh2;
|
||||
if ((seed & 1) > 0) {
|
||||
@@ -232,31 +342,37 @@ uint Select2DPartition(uint seed, uint x, uint y, uint partition_count) {
|
||||
sh1 = (partition_count == 3) ? 6 : 5;
|
||||
sh2 = (seed & 2) > 0 ? 4 : 5;
|
||||
}
|
||||
seed1 >>= sh1;
|
||||
seed2 >>= sh2;
|
||||
seed3 >>= sh1;
|
||||
seed4 >>= sh2;
|
||||
seed5 >>= sh1;
|
||||
seed6 >>= sh2;
|
||||
seed7 >>= sh1;
|
||||
seed8 >>= sh2;
|
||||
|
||||
uint a = seed1 * x + seed2 * y + (rnum >> 14);
|
||||
uint b = seed3 * x + seed4 * y + (rnum >> 10);
|
||||
uint c = seed5 * x + seed6 * y + (rnum >> 6);
|
||||
uint d = seed7 * x + seed8 * y + (rnum >> 2);
|
||||
pt.s1 = seed1 >> sh1;
|
||||
pt.s2 = seed2 >> sh2;
|
||||
pt.s3 = seed3 >> sh1;
|
||||
pt.s4 = seed4 >> sh2;
|
||||
pt.s5 = seed5 >> sh1;
|
||||
pt.s6 = seed6 >> sh2;
|
||||
pt.s7 = seed7 >> sh1;
|
||||
pt.s8 = seed8 >> sh2;
|
||||
|
||||
return pt;
|
||||
}
|
||||
|
||||
uint SelectPartition(PartitionTable pt, uint x, uint y, uint partition_count) {
|
||||
if (pt.small_block) {
|
||||
x <<= 1;
|
||||
y <<= 1;
|
||||
}
|
||||
|
||||
uint a = pt.s1 * x + pt.s2 * y + (pt.rnum >> 14);
|
||||
uint b = pt.s3 * x + pt.s4 * y + (pt.rnum >> 10);
|
||||
uint c = pt.s5 * x + pt.s6 * y + (pt.rnum >> 6);
|
||||
uint d = pt.s7 * x + pt.s8 * y + (pt.rnum >> 2);
|
||||
|
||||
a &= 0x3F;
|
||||
b &= 0x3F;
|
||||
c &= 0x3F;
|
||||
d &= 0x3F;
|
||||
|
||||
if (partition_count < 4) {
|
||||
d = 0;
|
||||
}
|
||||
if (partition_count < 3) {
|
||||
c = 0;
|
||||
}
|
||||
if (partition_count < 4) d = 0;
|
||||
if (partition_count < 3) c = 0;
|
||||
|
||||
if (a >= b && a >= c && a >= d) {
|
||||
return 0;
|
||||
@@ -457,7 +573,7 @@ void DecodeIntegerSequence(uint max_range, uint num_values) {
|
||||
}
|
||||
}
|
||||
|
||||
void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, out uint color_values[32]) {
|
||||
void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits) {
|
||||
uint num_values = 0;
|
||||
for (uint i = 0; i < num_partitions; i++) {
|
||||
num_values += ((modes[i] >> 2) + 1) << 1;
|
||||
@@ -471,104 +587,21 @@ void DecodeColorValues(uvec4 modes, uint num_partitions, uint color_data_bits, o
|
||||
break;
|
||||
}
|
||||
}
|
||||
DecodeIntegerSequence(range - 1, num_values);
|
||||
uint out_index = 0;
|
||||
for (int itr = 0; itr < result_index; ++itr) {
|
||||
if (out_index >= num_values) {
|
||||
break;
|
||||
}
|
||||
const EncodingData val = GetEncodingFromVector(itr);
|
||||
const uint encoding = Encoding(val);
|
||||
const uint bitlen = NumBits(val);
|
||||
const uint bitval = BitValue(val);
|
||||
uint A = 0, B = 0, C = 0, D = 0;
|
||||
A = ReplicateBitTo9((bitval & 1));
|
||||
switch (encoding) {
|
||||
case JUST_BITS:
|
||||
color_values[++out_index] = FastReplicateTo8(bitval, bitlen);
|
||||
break;
|
||||
case TRIT: {
|
||||
D = QuintTritValue(val);
|
||||
switch (bitlen) {
|
||||
case 1:
|
||||
C = 204;
|
||||
break;
|
||||
case 2: {
|
||||
C = 93;
|
||||
const uint b = (bitval >> 1) & 1;
|
||||
B = (b << 8) | (b << 4) | (b << 2) | (b << 1);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
C = 44;
|
||||
const uint cb = (bitval >> 1) & 3;
|
||||
B = (cb << 7) | (cb << 2) | cb;
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
C = 22;
|
||||
const uint dcb = (bitval >> 1) & 7;
|
||||
B = (dcb << 6) | dcb;
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
C = 11;
|
||||
const uint edcb = (bitval >> 1) & 0xF;
|
||||
B = (edcb << 5) | (edcb >> 2);
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
C = 5;
|
||||
const uint fedcb = (bitval >> 1) & 0x1F;
|
||||
B = (fedcb << 4) | (fedcb >> 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QUINT: {
|
||||
D = QuintTritValue(val);
|
||||
switch (bitlen) {
|
||||
case 1:
|
||||
C = 113;
|
||||
break;
|
||||
case 2: {
|
||||
C = 54;
|
||||
const uint b = (bitval >> 1) & 1;
|
||||
B = (b << 8) | (b << 3) | (b << 2);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
C = 26;
|
||||
const uint cb = (bitval >> 1) & 3;
|
||||
B = (cb << 7) | (cb << 1) | (cb >> 1);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
C = 13;
|
||||
const uint dcb = (bitval >> 1) & 7;
|
||||
B = (dcb << 6) | (dcb >> 1);
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
C = 6;
|
||||
const uint edcb = (bitval >> 1) & 0xF;
|
||||
B = (edcb << 5) | (edcb >> 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (encoding != JUST_BITS) {
|
||||
uint T = (D * C) + B;
|
||||
T ^= A;
|
||||
T = (A & 0x80) | (T >> 2);
|
||||
color_values[++out_index] = T;
|
||||
}
|
||||
// Decode directly into color_values_direct[]
|
||||
write_color_values = true;
|
||||
color_out_index = 0;
|
||||
color_num_values = num_values;
|
||||
for (uint i = 0; i < 32; ++i) {
|
||||
color_values_direct[i] = 0;
|
||||
}
|
||||
|
||||
DecodeIntegerSequence(range - 1, num_values);
|
||||
|
||||
write_color_values = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ivec2 BitTransferSigned(int a, int b) {
|
||||
ivec2 transferred;
|
||||
transferred.y = b >> 1;
|
||||
@@ -730,7 +763,7 @@ uint UnquantizeTexelWeight(EncodingData val) {
|
||||
uint encoding = Encoding(val), bitlen = NumBits(val), bitval = BitValue(val);
|
||||
if (encoding == JUST_BITS) {
|
||||
return (bitlen >= 1 && bitlen <= 5)
|
||||
? uint(floor(0.5f + float(bitval) * 64.0f / float((1 << bitlen) - 1)))
|
||||
? ((bitval * 64) + ((1 << bitlen) - 1) / 2) / ((1 << bitlen) - 1)
|
||||
: FastReplicateTo6(bitval, bitlen);
|
||||
} else if (encoding == TRIT || encoding == QUINT) {
|
||||
uint B = 0, C = 0, D = 0;
|
||||
@@ -1069,13 +1102,12 @@ void DecompressBlock(ivec3 coord) {
|
||||
uvec4 endpoints0[4];
|
||||
uvec4 endpoints1[4];
|
||||
{
|
||||
// This decode phase should at most push 32 elements into the vector
|
||||
result_vector_max_index = 32;
|
||||
uint color_values[32];
|
||||
// Decode directly into color_values_direct[] (no intermediate result_vector storage)
|
||||
result_limit_reached = false;
|
||||
uint colvals_index = 0;
|
||||
DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits, color_values);
|
||||
DecodeColorValues(color_endpoint_mode, num_partitions, color_data_bits);
|
||||
for (uint i = 0; i < num_partitions; i++) {
|
||||
ComputeEndpoints(endpoints0[i], endpoints1[i], color_endpoint_mode[i], color_values,
|
||||
ComputeEndpoints(endpoints0[i], endpoints1[i], color_endpoint_mode[i], color_values_direct,
|
||||
colvals_index);
|
||||
}
|
||||
}
|
||||
@@ -1106,11 +1138,15 @@ void DecompressBlock(ivec3 coord) {
|
||||
DecodeIntegerSequence(max_weight, GetNumWeightValues(size_params, dual_plane));
|
||||
|
||||
UnquantizeTexelWeights(size_params, dual_plane);
|
||||
PartitionTable pt;
|
||||
if (num_partitions > 1) {
|
||||
pt = GetPartitionTable(partition_index, num_partitions);
|
||||
}
|
||||
for (uint j = 0; j < block_dims.y; j++) {
|
||||
for (uint i = 0; i < block_dims.x; i++) {
|
||||
uint local_partition = 0;
|
||||
if (num_partitions > 1) {
|
||||
local_partition = Select2DPartition(partition_index, i, j, num_partitions);
|
||||
local_partition = SelectPartition(pt, i, j, num_partitions);
|
||||
}
|
||||
const uvec4 C0 = ReplicateByteTo16(endpoints0[local_partition]);
|
||||
const uvec4 C1 = ReplicateByteTo16(endpoints1[local_partition]);
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#version 450
|
||||
#extension GL_ARB_shader_stencil_export : require
|
||||
|
||||
layout(binding = 0) uniform sampler2D color_texture;
|
||||
|
||||
// Even more accurate sRGB to linear conversion
|
||||
// https://entropymine.com/imageworsener/srgbformula/
|
||||
float srgbToLinear(float srgb) {
|
||||
if (srgb <= 0.0404482362771082f) { //assumes it's >= 0
|
||||
return srgb / 12.92;
|
||||
} else {
|
||||
return pow((srgb + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
ivec2 coord = ivec2(gl_FragCoord.xy);
|
||||
vec4 srgbColor = texelFetch(color_texture, coord, 0);
|
||||
|
||||
// Convert sRGB to linear space with proper gamma correction
|
||||
vec3 linearColor = vec3(
|
||||
srgbToLinear(srgbColor.r),
|
||||
srgbToLinear(srgbColor.g),
|
||||
srgbToLinear(srgbColor.b)
|
||||
);
|
||||
|
||||
// Use standard luminance coefficients
|
||||
float luminance = dot(linearColor, vec3(0.2126, 0.7152, 0.0722));
|
||||
|
||||
// Ensure proper depth range
|
||||
luminance = clamp(luminance, 0.0, 1.0);
|
||||
|
||||
// Convert to 24-bit depth value
|
||||
uint depth_val = uint(luminance * float(0xFFFFFF));
|
||||
|
||||
// Extract 8-bit stencil from alpha
|
||||
uint stencil_val = uint(srgbColor.a * 255.0);
|
||||
|
||||
// Pack values efficiently
|
||||
uint depth_stencil = (stencil_val << 24) | (depth_val & 0x00FFFFFF);
|
||||
|
||||
gl_FragDepth = float(depth_val) / float(0xFFFFFF);
|
||||
gl_FragStencilRefARB = int(stencil_val);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(binding = 0) uniform samplerBuffer astc_data;
|
||||
layout(binding = 1, rgba16f) uniform writeonly image2D output_image;
|
||||
|
||||
// Note: This is a simplified version. Real ASTC HDR decompression is more complex
|
||||
void main() {
|
||||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 size = imageSize(output_image);
|
||||
|
||||
if (pos.x >= size.x || pos.y >= size.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate block and pixel within block
|
||||
ivec2 block = pos / 8; // Assuming 8x8 ASTC blocks
|
||||
ivec2 pixel = pos % 8;
|
||||
|
||||
// Each ASTC block is 16 bytes
|
||||
int block_index = block.y * (size.x / 8) + block.x;
|
||||
|
||||
// Simplified ASTC HDR decoding - you'll need to implement full ASTC decoding
|
||||
vec4 color = texelFetch(astc_data, block_index * 8 + pixel.y * 8 + pixel.x);
|
||||
|
||||
imageStore(output_image, pos, color);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#version 450
|
||||
#extension GL_ARB_shader_ballot : require
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(binding = 0) uniform samplerBuffer bc7_data;
|
||||
layout(binding = 1, rgba8) uniform writeonly image2D output_image;
|
||||
|
||||
// Note: This is a simplified version. Real BC7 decompression is more complex
|
||||
void main() {
|
||||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 size = imageSize(output_image);
|
||||
|
||||
if (pos.x >= size.x || pos.y >= size.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate block and pixel within block
|
||||
ivec2 block = pos / 4;
|
||||
ivec2 pixel = pos % 4;
|
||||
|
||||
// Each BC7 block is 16 bytes
|
||||
int block_index = block.y * (size.x / 4) + block.x;
|
||||
|
||||
// Simplified BC7 decoding - you'll need to implement full BC7 decoding
|
||||
vec4 color = texelFetch(bc7_data, block_index * 4 + pixel.y * 4 + pixel.x);
|
||||
|
||||
imageStore(output_image, pos, color);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
layout(binding = 1, r8) uniform writeonly image2D y_output;
|
||||
layout(binding = 2, r8) uniform writeonly image2D u_output;
|
||||
layout(binding = 3, r8) uniform writeonly image2D v_output;
|
||||
|
||||
void main() {
|
||||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 size = imageSize(y_output);
|
||||
|
||||
if (pos.x >= size.x || pos.y >= size.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 tex_coord = vec2(pos) / vec2(size);
|
||||
vec3 rgb = texture(input_texture, tex_coord).rgb;
|
||||
|
||||
// RGB to YUV conversion
|
||||
float y = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b;
|
||||
float u = -0.147 * rgb.r - 0.289 * rgb.g + 0.436 * rgb.b + 0.5;
|
||||
float v = 0.615 * rgb.r - 0.515 * rgb.g - 0.100 * rgb.b + 0.5;
|
||||
|
||||
imageStore(y_output, pos, vec4(y));
|
||||
imageStore(u_output, pos / 2, vec4(u));
|
||||
imageStore(v_output, pos / 2, vec4(v));
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 texcoord;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
float exposure;
|
||||
float gamma;
|
||||
} constants;
|
||||
|
||||
vec3 tonemap(vec3 hdr) {
|
||||
// Reinhard tonemapping
|
||||
return hdr / (hdr + vec3(1.0));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 hdr = texture(input_texture, texcoord);
|
||||
|
||||
// Apply exposure
|
||||
vec3 exposed = hdr.rgb * constants.exposure;
|
||||
|
||||
// Tonemap
|
||||
vec3 tonemapped = tonemap(exposed);
|
||||
|
||||
// Gamma correction
|
||||
vec3 gamma_corrected = pow(tonemapped, vec3(1.0 / constants.gamma));
|
||||
|
||||
color = vec4(gamma_corrected, hdr.a);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 texcoord;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
|
||||
void main() {
|
||||
vec4 rgba = texture(input_texture, texcoord);
|
||||
color = rgba.bgra; // Swap red and blue channels
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(binding = 0) uniform sampler2D y_texture;
|
||||
layout(binding = 1) uniform sampler2D u_texture;
|
||||
layout(binding = 2) uniform sampler2D v_texture;
|
||||
layout(binding = 3, rgba8) uniform writeonly image2D output_image;
|
||||
|
||||
void main() {
|
||||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 size = imageSize(output_image);
|
||||
|
||||
if (pos.x >= size.x || pos.y >= size.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 tex_coord = vec2(pos) / vec2(size);
|
||||
float y = texture(y_texture, tex_coord).r;
|
||||
float u = texture(u_texture, tex_coord).r - 0.5;
|
||||
float v = texture(v_texture, tex_coord).r - 0.5;
|
||||
|
||||
// YUV to RGB conversion
|
||||
vec3 rgb;
|
||||
rgb.r = y + 1.402 * v;
|
||||
rgb.g = y - 0.344 * u - 0.714 * v;
|
||||
rgb.b = y + 1.772 * u;
|
||||
|
||||
imageStore(output_image, pos, vec4(rgb, 1.0));
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(location = 0) in vec2 texcoord;
|
||||
layout(location = 0) out vec4 color;
|
||||
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
float frame_count;
|
||||
float dither_strength;
|
||||
} constants;
|
||||
|
||||
// Pseudo-random number generator
|
||||
float rand(vec2 co) {
|
||||
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec4 input_color = texture(input_texture, texcoord);
|
||||
|
||||
// Generate temporal noise based on frame count
|
||||
vec2 noise_coord = gl_FragCoord.xy + vec2(constants.frame_count);
|
||||
float noise = rand(noise_coord) * 2.0 - 1.0;
|
||||
|
||||
// Apply dithering
|
||||
vec3 dithered = input_color.rgb + noise * constants.dither_strength;
|
||||
|
||||
color = vec4(dithered, input_color.a);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
#version 450
|
||||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
layout(binding = 0) uniform sampler2D input_texture;
|
||||
layout(binding = 1, rgba8) uniform writeonly image2D output_image;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec2 scale_factor;
|
||||
vec2 input_size;
|
||||
} constants;
|
||||
|
||||
vec4 cubic(float v) {
|
||||
vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
|
||||
vec4 s = n * n * n;
|
||||
float x = s.x;
|
||||
float y = s.y - 4.0 * s.x;
|
||||
float z = s.z - 4.0 * s.y + 6.0 * s.x;
|
||||
float w = s.w - 4.0 * s.z + 6.0 * s.y - 4.0 * s.x;
|
||||
return vec4(x, y, z, w) * (1.0/6.0);
|
||||
}
|
||||
|
||||
vec4 bicubic_sample(sampler2D tex, vec2 tex_coord) {
|
||||
vec2 tex_size = constants.input_size;
|
||||
vec2 inv_tex_size = 1.0 / tex_size;
|
||||
|
||||
tex_coord = tex_coord * tex_size - 0.5;
|
||||
|
||||
vec2 fxy = fract(tex_coord);
|
||||
tex_coord -= fxy;
|
||||
|
||||
vec4 xcubic = cubic(fxy.x);
|
||||
vec4 ycubic = cubic(fxy.y);
|
||||
|
||||
vec4 c = tex_coord.xxyy + vec2(-0.5, +1.5).xyxy;
|
||||
vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
|
||||
vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s;
|
||||
|
||||
offset *= inv_tex_size.xxyy;
|
||||
|
||||
vec4 sample0 = texture(tex, offset.xz);
|
||||
vec4 sample1 = texture(tex, offset.yz);
|
||||
vec4 sample2 = texture(tex, offset.xw);
|
||||
vec4 sample3 = texture(tex, offset.yw);
|
||||
|
||||
float sx = s.x / (s.x + s.y);
|
||||
float sy = s.z / (s.z + s.w);
|
||||
|
||||
return mix(
|
||||
mix(sample3, sample2, sx),
|
||||
mix(sample1, sample0, sx),
|
||||
sy
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
||||
ivec2 size = imageSize(output_image);
|
||||
|
||||
if (pos.x >= size.x || pos.y >= size.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 tex_coord = vec2(pos) / vec2(size);
|
||||
vec4 color = bicubic_sample(input_texture, tex_coord);
|
||||
|
||||
imageStore(output_image, pos, color);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -31,15 +31,6 @@
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/host_shaders/convert_abgr8_srgb_to_d24s8_frag_spv.h"
|
||||
#include "video_core/host_shaders/convert_rgba8_to_bgra8_frag_spv.h"
|
||||
#include "video_core/host_shaders/convert_yuv420_to_rgb_comp_spv.h"
|
||||
#include "video_core/host_shaders/convert_rgb_to_yuv420_comp_spv.h"
|
||||
#include "video_core/host_shaders/convert_bc7_to_rgba8_comp_spv.h"
|
||||
#include "video_core/host_shaders/convert_astc_hdr_to_rgba16f_comp_spv.h"
|
||||
#include "video_core/host_shaders/convert_rgba16f_to_rgba8_frag_spv.h"
|
||||
#include "video_core/host_shaders/dither_temporal_frag_spv.h"
|
||||
#include "video_core/host_shaders/dynamic_resolution_scale_comp_spv.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
@@ -540,17 +531,6 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
|
||||
convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
|
||||
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
|
||||
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
|
||||
convert_abgr8_srgb_to_d24s8_frag(device.IsExtShaderStencilExportSupported()
|
||||
? BuildShader(device, CONVERT_ABGR8_SRGB_TO_D24S8_FRAG_SPV)
|
||||
: vk::ShaderModule{}),
|
||||
convert_rgba_to_bgra_frag(BuildShader(device, CONVERT_RGBA8_TO_BGRA8_FRAG_SPV)),
|
||||
convert_yuv420_to_rgb_comp(BuildShader(device, CONVERT_YUV420_TO_RGB_COMP_SPV)),
|
||||
convert_rgb_to_yuv420_comp(BuildShader(device, CONVERT_RGB_TO_YUV420_COMP_SPV)),
|
||||
convert_bc7_to_rgba8_comp(BuildShader(device, CONVERT_BC7_TO_RGBA8_COMP_SPV)),
|
||||
convert_astc_hdr_to_rgba16f_comp(BuildShader(device, CONVERT_ASTC_HDR_TO_RGBA16F_COMP_SPV)),
|
||||
convert_rgba16f_to_rgba8_frag(BuildShader(device, CONVERT_RGBA16F_TO_RGBA8_FRAG_SPV)),
|
||||
dither_temporal_frag(BuildShader(device, DITHER_TEMPORAL_FRAG_SPV)),
|
||||
dynamic_resolution_scale_comp(BuildShader(device, DYNAMIC_RESOLUTION_SCALE_COMP_SPV)),
|
||||
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
|
||||
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
|
||||
|
||||
@@ -711,19 +691,6 @@ void BlitImageHelper::ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer,
|
||||
ConvertDepthStencil(*convert_s8d24_to_abgr8_pipeline, dst_framebuffer, src_image_view);
|
||||
}
|
||||
|
||||
void BlitImageHelper::ConvertABGR8SRGBToD24S8(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
if (!device.IsExtShaderStencilExportSupported()) {
|
||||
// Shader requires VK_EXT_shader_stencil_export which is not available
|
||||
LOG_WARNING(Render_Vulkan, "ConvertABGR8SRGBToD24S8 requires shader_stencil_export, skipping");
|
||||
return;
|
||||
}
|
||||
ConvertPipelineDepthTargetEx(convert_abgr8_srgb_to_d24s8_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
convert_abgr8_srgb_to_d24s8_frag);
|
||||
Convert(*convert_abgr8_srgb_to_d24s8_pipeline, dst_framebuffer, src_image_view);
|
||||
}
|
||||
|
||||
void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask,
|
||||
const std::array<f32, 4>& clear_color,
|
||||
const Region2D& dst_region) {
|
||||
@@ -1192,68 +1159,4 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
|
||||
});
|
||||
}
|
||||
|
||||
void BlitImageHelper::ConvertRGBAtoGBRA(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
ConvertPipeline(convert_rgba_to_bgra_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
false);
|
||||
Convert(*convert_rgba_to_bgra_pipeline, dst_framebuffer, src_image_view);
|
||||
}
|
||||
|
||||
void BlitImageHelper::ConvertYUV420toRGB(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
ConvertPipeline(convert_yuv420_to_rgb_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
false);
|
||||
Convert(*convert_yuv420_to_rgb_pipeline, dst_framebuffer, src_image_view);
|
||||
}
|
||||
|
||||
void BlitImageHelper::ConvertRGBtoYUV420(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
ConvertPipeline(convert_rgb_to_yuv420_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
false);
|
||||
Convert(*convert_rgb_to_yuv420_pipeline, dst_framebuffer, src_image_view);
|
||||
}
|
||||
|
||||
void BlitImageHelper::ConvertBC7toRGBA8(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
ConvertPipeline(convert_bc7_to_rgba8_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
false);
|
||||
Convert(*convert_bc7_to_rgba8_pipeline, dst_framebuffer, src_image_view);
|
||||
}
|
||||
|
||||
void BlitImageHelper::ConvertASTCHDRtoRGBA16F(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
ConvertPipeline(convert_astc_hdr_to_rgba16f_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
false);
|
||||
Convert(*convert_astc_hdr_to_rgba16f_pipeline, dst_framebuffer, src_image_view);
|
||||
}
|
||||
|
||||
void BlitImageHelper::ConvertRGBA16FtoRGBA8(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
ConvertPipeline(convert_rgba16f_to_rgba8_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
false);
|
||||
Convert(*convert_rgba16f_to_rgba8_pipeline, dst_framebuffer, src_image_view);
|
||||
}
|
||||
|
||||
void BlitImageHelper::ApplyDitherTemporal(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
ConvertPipeline(dither_temporal_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
false);
|
||||
Convert(*dither_temporal_pipeline, dst_framebuffer, src_image_view);
|
||||
}
|
||||
|
||||
void BlitImageHelper::ApplyDynamicResolutionScale(const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view) {
|
||||
ConvertPipeline(dynamic_resolution_scale_pipeline,
|
||||
dst_framebuffer->RenderPass(),
|
||||
false);
|
||||
Convert(*dynamic_resolution_scale_pipeline, dst_framebuffer, src_image_view);
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -70,8 +70,6 @@ public:
|
||||
|
||||
void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
|
||||
void ConvertABGR8SRGBToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
|
||||
void ConvertABGR8ToD32F(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
|
||||
void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
|
||||
@@ -86,16 +84,6 @@ public:
|
||||
void ClearDepthStencil(const Framebuffer* dst_framebuffer, bool depth_clear, f32 clear_depth,
|
||||
u8 stencil_mask, u32 stencil_ref, u32 stencil_compare_mask,
|
||||
const Region2D& dst_region);
|
||||
|
||||
void ConvertRGBAtoGBRA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
void ConvertYUV420toRGB(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
void ConvertRGBtoYUV420(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
void ConvertBC7toRGBA8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
void ConvertASTCHDRtoRGBA16F(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
void ConvertRGBA16FtoRGBA8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
void ApplyDitherTemporal(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
void ApplyDynamicResolutionScale(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
|
||||
|
||||
private:
|
||||
void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
|
||||
const ImageView& src_image_view);
|
||||
@@ -150,15 +138,6 @@ private:
|
||||
vk::ShaderModule convert_d32f_to_abgr8_frag;
|
||||
vk::ShaderModule convert_d24s8_to_abgr8_frag;
|
||||
vk::ShaderModule convert_s8d24_to_abgr8_frag;
|
||||
vk::ShaderModule convert_abgr8_srgb_to_d24s8_frag;
|
||||
vk::ShaderModule convert_rgba_to_bgra_frag;
|
||||
vk::ShaderModule convert_yuv420_to_rgb_comp;
|
||||
vk::ShaderModule convert_rgb_to_yuv420_comp;
|
||||
vk::ShaderModule convert_bc7_to_rgba8_comp;
|
||||
vk::ShaderModule convert_astc_hdr_to_rgba16f_comp;
|
||||
vk::ShaderModule convert_rgba16f_to_rgba8_frag;
|
||||
vk::ShaderModule dither_temporal_frag;
|
||||
vk::ShaderModule dynamic_resolution_scale_comp;
|
||||
vk::Sampler linear_sampler;
|
||||
vk::Sampler nearest_sampler;
|
||||
|
||||
@@ -179,15 +158,6 @@ private:
|
||||
vk::Pipeline convert_d32f_to_abgr8_pipeline;
|
||||
vk::Pipeline convert_d24s8_to_abgr8_pipeline;
|
||||
vk::Pipeline convert_s8d24_to_abgr8_pipeline;
|
||||
vk::Pipeline convert_abgr8_srgb_to_d24s8_pipeline;
|
||||
vk::Pipeline convert_rgba_to_bgra_pipeline;
|
||||
vk::Pipeline convert_yuv420_to_rgb_pipeline;
|
||||
vk::Pipeline convert_rgb_to_yuv420_pipeline;
|
||||
vk::Pipeline convert_bc7_to_rgba8_pipeline;
|
||||
vk::Pipeline convert_astc_hdr_to_rgba16f_pipeline;
|
||||
vk::Pipeline convert_rgba16f_to_rgba8_pipeline;
|
||||
vk::Pipeline dither_temporal_pipeline;
|
||||
vk::Pipeline dynamic_resolution_scale_pipeline;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "video_core/renderer_vulkan/pipeline_helper.h"
|
||||
#include "video_core/renderer_vulkan/pipeline_statistics.h"
|
||||
@@ -20,6 +21,8 @@
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
@@ -82,6 +85,13 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
|
||||
},
|
||||
*pipeline_cache);
|
||||
|
||||
// Log compute pipeline creation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogPipelineStateChange(
|
||||
"ComputePipeline created"
|
||||
);
|
||||
}
|
||||
|
||||
if (pipeline_statistics) {
|
||||
pipeline_statistics->Collect(*pipeline);
|
||||
}
|
||||
@@ -207,6 +217,13 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
build_condvar.wait(lock, [this] { return is_built.load(std::memory_order::relaxed); });
|
||||
});
|
||||
}
|
||||
|
||||
// Log compute pipeline binding
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogPipelineBind(true, "compute pipeline");
|
||||
}
|
||||
|
||||
const void* const descriptor_data{guest_descriptor_queue.UpdateData()};
|
||||
const bool is_rescaling = !info.texture_descriptors.empty() || !info.image_descriptors.empty();
|
||||
scheduler.Record([this, descriptor_data, is_rescaling,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "video_core/renderer_vulkan/pipeline_helper.h"
|
||||
|
||||
@@ -25,6 +26,8 @@
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#if defined(_MSC_VER) && defined(NDEBUG)
|
||||
#define LAMBDA_FORCEINLINE [[msvc::forceinline]]
|
||||
@@ -513,6 +516,14 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
const bool is_rescaling{texture_cache.IsRescaling()};
|
||||
const bool update_rescaling{scheduler.UpdateRescaling(is_rescaling)};
|
||||
const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)};
|
||||
|
||||
// Log graphics pipeline binding
|
||||
if (bind_pipeline && Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
const std::string pipeline_info = fmt::format("hash=0x{:016x}", key.Hash());
|
||||
GPU::Logging::GPULogger::GetInstance().LogPipelineBind(false, pipeline_info);
|
||||
}
|
||||
|
||||
const void* const descriptor_data{guest_descriptor_queue.UpdateData()};
|
||||
scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(),
|
||||
is_rescaling, update_rescaling,
|
||||
@@ -954,6 +965,16 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.basePipelineIndex = 0,
|
||||
},
|
||||
*pipeline_cache);
|
||||
|
||||
// Log graphics pipeline creation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
const std::string pipeline_info = fmt::format(
|
||||
"GraphicsPipeline created: stages={}, attachments={}",
|
||||
shader_stages.size(),
|
||||
color_blend_ci.attachmentCount
|
||||
);
|
||||
GPU::Logging::GPULogger::GetInstance().LogPipelineStateChange(pipeline_info);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsPipeline::Validate() {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <bit>
|
||||
@@ -42,6 +43,7 @@
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
@@ -724,6 +726,17 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
|
||||
const std::vector<u32> code{EmitSPIRV(profile, runtime_info, program, binding, this->optimize_spirv_output)};
|
||||
device.SaveShader(code);
|
||||
modules[stage_index] = BuildShader(device, code);
|
||||
|
||||
// Log shader compilation to GPU logger (with SPIR-V binary dump if enabled)
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
static constexpr std::array stage_names{"vertex", "tess_control", "tess_eval", "geometry", "fragment"};
|
||||
const std::string shader_name = fmt::format("shader_{:016x}_{}", key.unique_hashes[index], stage_names[stage_index]);
|
||||
const std::string shader_info = fmt::format("SPIR-V size: {} bytes, hash: {:016x}",
|
||||
code.size() * sizeof(u32), key.unique_hashes[index]);
|
||||
GPU::Logging::GPULogger::GetInstance().LogShaderCompilation(shader_name, shader_info,
|
||||
std::span<const u32>(code.data(), code.size()));
|
||||
}
|
||||
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
const std::string name{fmt::format("Shader {:016x}", key.unique_hashes[index])};
|
||||
modules[stage_index].SetObjectNameEXT(name.c_str());
|
||||
@@ -831,6 +844,16 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
const std::vector<u32> code{EmitSPIRV(profile, program, this->optimize_spirv_output)};
|
||||
device.SaveShader(code);
|
||||
vk::ShaderModule spv_module{BuildShader(device, code)};
|
||||
|
||||
// Log compute shader compilation to GPU logger (with SPIR-V binary dump if enabled)
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
const std::string shader_name = fmt::format("shader_{:016x}_compute", key.unique_hash);
|
||||
const std::string shader_info = fmt::format("SPIR-V size: {} bytes, hash: {:016x}",
|
||||
code.size() * sizeof(u32), key.unique_hash);
|
||||
GPU::Logging::GPULogger::GetInstance().LogShaderCompilation(shader_name, shader_info,
|
||||
std::span<const u32>(code.data(), code.size()));
|
||||
}
|
||||
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
const auto name{fmt::format("Shader {:016x}", key.unique_hash)};
|
||||
spv_module.SetObjectNameEXT(name.c_str());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -16,6 +18,7 @@
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/buffer_cache/buffer_cache.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "video_core/control/channel_state.h"
|
||||
#include "video_core/engines/draw_manager.h"
|
||||
#include "video_core/engines/kepler_compute.h"
|
||||
@@ -277,6 +280,20 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Log draw call
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
const std::string params = is_indexed ?
|
||||
fmt::format("vertices={}, instances={}, firstIndex={}, baseVertex={}, baseInstance={}",
|
||||
draw_params.num_vertices, draw_params.num_instances,
|
||||
draw_params.first_index, draw_params.base_vertex, draw_params.base_instance) :
|
||||
fmt::format("vertices={}, instances={}, firstVertex={}, firstInstance={}",
|
||||
draw_params.num_vertices, draw_params.num_instances,
|
||||
draw_params.base_vertex, draw_params.base_instance);
|
||||
GPU::Logging::GPULogger::GetInstance().LogVulkanCall(
|
||||
is_indexed ? "vkCmdDrawIndexed" : "vkCmdDraw", params, VK_SUCCESS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -324,6 +341,16 @@ void RasterizerVulkan::DrawIndirect() {
|
||||
static_cast<u32>(params.stride));
|
||||
}
|
||||
});
|
||||
|
||||
// Log indirect draw call
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
const std::string log_params = fmt::format("drawCount={}, stride={}",
|
||||
params.max_draw_counts, params.stride);
|
||||
GPU::Logging::GPULogger::GetInstance().LogVulkanCall(
|
||||
params.is_indexed ? "vkCmdDrawIndexedIndirect" : "vkCmdDrawIndirect",
|
||||
log_params, VK_SUCCESS);
|
||||
}
|
||||
});
|
||||
buffer_cache.SetDrawIndirect(nullptr);
|
||||
}
|
||||
@@ -568,6 +595,15 @@ void RasterizerVulkan::DispatchCompute() {
|
||||
scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, READ_BARRIER); });
|
||||
scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
|
||||
|
||||
// Log compute dispatch
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
const std::string params = fmt::format("groupCountX={}, groupCountY={}, groupCountZ={}",
|
||||
dim[0], dim[1], dim[2]);
|
||||
GPU::Logging::GPULogger::GetInstance().LogVulkanCall(
|
||||
"vkCmdDispatch", params, VK_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::ResetCounter(VideoCommon::QueryType type) {
|
||||
@@ -1066,6 +1102,11 @@ void RasterizerVulkan::HandleTransformFeedback() {
|
||||
query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount,
|
||||
regs.transform_feedback_enabled);
|
||||
if (regs.transform_feedback_enabled != 0) {
|
||||
// Log extension usage for transform feedback
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogExtensionUsage(
|
||||
"VK_EXT_transform_feedback", "HandleTransformFeedback");
|
||||
}
|
||||
UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) ||
|
||||
regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -9,9 +9,13 @@
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_query_cache.h"
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "video_core/renderer_vulkan/vk_command_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||
@@ -114,6 +118,15 @@ void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) {
|
||||
state.framebuffer = framebuffer_handle;
|
||||
state.render_area = render_area;
|
||||
|
||||
// Log render pass begin
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
const std::string render_pass_info = fmt::format(
|
||||
"renderArea={}x{}, numImages={}",
|
||||
render_area.width, render_area.height, framebuffer->NumImages());
|
||||
GPU::Logging::GPULogger::GetInstance().LogRenderPassBegin(render_pass_info);
|
||||
}
|
||||
|
||||
Record([renderpass, framebuffer_handle, render_area](vk::CommandBuffer cmdbuf) {
|
||||
const VkRenderPassBeginInfo renderpass_bi{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
@@ -270,6 +283,12 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
|
||||
switch (const VkResult result = master_semaphore->SubmitQueue(
|
||||
cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, signal_value)) {
|
||||
case VK_SUCCESS:
|
||||
// Log successful queue submission
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogVulkanCall(
|
||||
"vkQueueSubmit", "", VK_SUCCESS);
|
||||
}
|
||||
break;
|
||||
case VK_ERROR_DEVICE_LOST:
|
||||
device.ReportLoss();
|
||||
@@ -305,6 +324,12 @@ void Scheduler::EndRenderPass()
|
||||
return;
|
||||
}
|
||||
|
||||
// Log render pass end
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogRenderPassEnd();
|
||||
}
|
||||
|
||||
query_cache->CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, false);
|
||||
query_cache->NotifySegment(false);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "common/settings.h"
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/renderer_vulkan/blit_image.h"
|
||||
@@ -1254,25 +1255,13 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
|
||||
|
||||
switch (dst_view.format) {
|
||||
case PixelFormat::D24_UNORM_S8_UINT:
|
||||
// Handle sRGB source formats
|
||||
if (src_view.format == PixelFormat::A8B8G8R8_SRGB ||
|
||||
src_view.format == PixelFormat::B8G8R8A8_SRGB) {
|
||||
// Verify format support before conversion
|
||||
if (device.IsFormatSupported(VK_FORMAT_D24_UNORM_S8_UINT,
|
||||
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT,
|
||||
FormatType::Optimal)) {
|
||||
return blit_image_helper.ConvertABGR8SRGBToD24S8(dst, src_view);
|
||||
} else {
|
||||
// Fallback to regular ABGR8 conversion if sRGB not supported
|
||||
return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view);
|
||||
}
|
||||
}
|
||||
if (src_view.format == PixelFormat::A8B8G8R8_UNORM ||
|
||||
src_view.format == PixelFormat::B8G8R8A8_UNORM) {
|
||||
if (src_view.format == PixelFormat::A8B8G8R8_UNORM
|
||||
|| src_view.format == PixelFormat::B8G8R8A8_UNORM
|
||||
|| src_view.format == PixelFormat::A8B8G8R8_SRGB
|
||||
|| src_view.format == PixelFormat::B8G8R8A8_SRGB) {
|
||||
return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view);
|
||||
}
|
||||
break;
|
||||
|
||||
case PixelFormat::A8B8G8R8_UNORM:
|
||||
case PixelFormat::A8B8G8R8_SNORM:
|
||||
case PixelFormat::A8B8G8R8_SINT:
|
||||
@@ -2316,6 +2305,11 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t
|
||||
const void* pnext = nullptr;
|
||||
if (has_custom_border_colors) {
|
||||
pnext = &border_ci;
|
||||
// Log extension usage for custom border color
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogExtensionUsage(
|
||||
"VK_EXT_custom_border_color", "Sampler::Sampler");
|
||||
}
|
||||
}
|
||||
const VkSamplerReductionModeCreateInfoEXT reduction_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT,
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_manager.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
TextureManager::TextureManager(const Device& device_, MemoryAllocator& memory_allocator_)
|
||||
: device(device_), memory_allocator(memory_allocator_) {
|
||||
|
||||
// Create a default texture for fallback in case of errors
|
||||
default_texture = CreateDefaultTexture();
|
||||
}
|
||||
|
||||
TextureManager::~TextureManager() {
|
||||
std::lock_guard<std::mutex> lock(texture_mutex);
|
||||
// Clear all cached textures
|
||||
texture_cache.clear();
|
||||
|
||||
// Default texture will be cleaned up automatically by vk::Image's destructor
|
||||
}
|
||||
|
||||
VkImage TextureManager::GetTexture(const std::string& texture_path) {
|
||||
std::lock_guard<std::mutex> lock(texture_mutex);
|
||||
|
||||
// Check if the texture is already in the cache
|
||||
auto it = texture_cache.find(texture_path);
|
||||
if (it != texture_cache.end()) {
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
// Load the texture and add it to the cache
|
||||
vk::Image new_texture = LoadTexture(texture_path);
|
||||
if (new_texture) {
|
||||
VkImage raw_handle = *new_texture;
|
||||
texture_cache.emplace(texture_path, std::move(new_texture));
|
||||
return raw_handle;
|
||||
}
|
||||
|
||||
// If loading fails, return the default texture if it exists
|
||||
LOG_WARNING(Render_Vulkan, "Failed to load texture: {}, using default", texture_path);
|
||||
if (default_texture.has_value()) {
|
||||
return *(*default_texture);
|
||||
}
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
void TextureManager::ReloadTexture(const std::string& texture_path) {
|
||||
std::lock_guard<std::mutex> lock(texture_mutex);
|
||||
|
||||
// Remove the texture from cache if it exists
|
||||
auto it = texture_cache.find(texture_path);
|
||||
if (it != texture_cache.end()) {
|
||||
LOG_INFO(Render_Vulkan, "Reloading texture: {}", texture_path);
|
||||
texture_cache.erase(it);
|
||||
}
|
||||
|
||||
// The texture will be reloaded on next GetTexture call
|
||||
}
|
||||
|
||||
bool TextureManager::IsTextureLoadedCorrectly(VkImage texture) {
|
||||
// Check if the texture handle is valid
|
||||
static const VkImage null_handle = VK_NULL_HANDLE;
|
||||
return texture != null_handle;
|
||||
}
|
||||
|
||||
void TextureManager::CleanupTextureCache() {
|
||||
std::lock_guard<std::mutex> lock(texture_mutex);
|
||||
|
||||
// TODO: track usage and remove unused textures [ZEP]
|
||||
|
||||
LOG_INFO(Render_Vulkan, "Handling texture cache cleanup, current size: {}", texture_cache.size());
|
||||
}
|
||||
|
||||
void TextureManager::HandleTextureRendering(const std::string& texture_path,
|
||||
std::function<void(VkImage)> render_callback) {
|
||||
VkImage texture = GetTexture(texture_path);
|
||||
|
||||
if (!IsTextureLoadedCorrectly(texture)) {
|
||||
LOG_ERROR(Render_Vulkan, "Texture failed to load correctly: {}, attempting reload", texture_path);
|
||||
ReloadTexture(texture_path);
|
||||
texture = GetTexture(texture_path);
|
||||
}
|
||||
|
||||
// Execute the rendering callback with the texture
|
||||
render_callback(texture);
|
||||
}
|
||||
|
||||
vk::Image TextureManager::LoadTexture(const std::string& texture_path) {
|
||||
// TODO: load image data from disk
|
||||
// and create a proper Vulkan texture [ZEP]
|
||||
|
||||
if (!std::filesystem::exists(texture_path)) {
|
||||
LOG_ERROR(Render_Vulkan, "Texture file not found: {}", texture_path);
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
LOG_INFO(Render_Vulkan, "Loaded texture: {}", texture_path);
|
||||
|
||||
// TODO: create an actual VkImage [ZEP]
|
||||
return CreateDefaultTexture();
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR(Render_Vulkan, "Error loading texture {}: {}", texture_path, e.what());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
vk::Image TextureManager::CreateDefaultTexture() {
|
||||
// Create a small default texture (1x1 pixel) to use as a fallback
|
||||
// const VkExtent2D extent{1, 1};
|
||||
|
||||
/* // Create image
|
||||
const VkImageCreateInfo image_ci{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = texture_format,
|
||||
.extent = {extent.width, extent.height, 1},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
}; */
|
||||
|
||||
// TODO: create an actual VkImage [ZEP]
|
||||
LOG_INFO(Render_Vulkan, "Created default fallback texture");
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
@@ -1,12 +1,26 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <string_view>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/vulkan_common/vulkan_debug_callback.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
namespace Vulkan {
|
||||
namespace {
|
||||
|
||||
// Helper to get message type as string for GPU logging
|
||||
const char* GetMessageTypeName(VkDebugUtilsMessageTypeFlagsEXT type) {
|
||||
if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) {
|
||||
return "Validation";
|
||||
} else if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) {
|
||||
return "Performance";
|
||||
} else {
|
||||
return "General";
|
||||
}
|
||||
}
|
||||
|
||||
VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT type,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* data,
|
||||
@@ -60,6 +74,28 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
} else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) {
|
||||
LOG_DEBUG(Render_Vulkan, "{}", message);
|
||||
}
|
||||
|
||||
// Route to GPU logger for tracking Vulkan validation messages
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
// Convert severity to result code for logging (negative = error)
|
||||
int result_code = 0;
|
||||
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
result_code = -1;
|
||||
} else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
|
||||
result_code = -2;
|
||||
}
|
||||
|
||||
// Get message ID name or use generic name
|
||||
const char* call_name = data->pMessageIdName ? data->pMessageIdName : "VulkanDebug";
|
||||
|
||||
GPU::Logging::GPULogger::GetInstance().LogVulkanCall(
|
||||
call_name,
|
||||
std::string(GetMessageTypeName(type)) + ": " + std::string(message),
|
||||
result_code
|
||||
);
|
||||
}
|
||||
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/literals.h"
|
||||
#include <ranges>
|
||||
@@ -22,6 +24,7 @@
|
||||
#include "video_core/vulkan_common/vma.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
#include <adrenotools/bcenabler.h>
|
||||
@@ -734,9 +737,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
};
|
||||
|
||||
vk::Check(vmaCreateAllocator(&allocator_info, &allocator));
|
||||
|
||||
// Initialize GPU logging if enabled
|
||||
InitializeGPULogging();
|
||||
}
|
||||
|
||||
Device::~Device() {
|
||||
ShutdownGPULogging();
|
||||
vmaDestroyAllocator(allocator);
|
||||
}
|
||||
|
||||
@@ -1622,4 +1629,106 @@ std::vector<VkDeviceQueueCreateInfo> Device::GetDeviceQueueCreateInfos() const {
|
||||
return queue_cis;
|
||||
}
|
||||
|
||||
void Device::InitializeGPULogging() {
|
||||
if (!Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect driver type
|
||||
const auto driver_id = GetDriverID();
|
||||
GPU::Logging::DriverType detected_driver = GPU::Logging::DriverType::Unknown;
|
||||
|
||||
if (driver_id == VK_DRIVER_ID_MESA_TURNIP) {
|
||||
detected_driver = GPU::Logging::DriverType::Turnip;
|
||||
} else if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
|
||||
detected_driver = GPU::Logging::DriverType::Qualcomm;
|
||||
}
|
||||
|
||||
// Get log level from settings
|
||||
const auto log_level = static_cast<GPU::Logging::LogLevel>(
|
||||
static_cast<u32>(Settings::values.gpu_log_level.GetValue()));
|
||||
|
||||
// Initialize GPU logger
|
||||
GPU::Logging::GPULogger::GetInstance().Initialize(log_level, detected_driver);
|
||||
|
||||
// Configure feature flags
|
||||
GPU::Logging::GPULogger::GetInstance().EnableVulkanCallTracking(
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue());
|
||||
GPU::Logging::GPULogger::GetInstance().EnableShaderDumps(
|
||||
Settings::values.gpu_log_shader_dumps.GetValue());
|
||||
GPU::Logging::GPULogger::GetInstance().EnableMemoryTracking(
|
||||
Settings::values.gpu_log_memory_tracking.GetValue());
|
||||
GPU::Logging::GPULogger::GetInstance().EnableDriverDebugInfo(
|
||||
Settings::values.gpu_log_driver_debug.GetValue());
|
||||
GPU::Logging::GPULogger::GetInstance().SetRingBufferSize(
|
||||
Settings::values.gpu_log_ring_buffer_size.GetValue());
|
||||
|
||||
// Log comprehensive driver and extension information
|
||||
if (Settings::values.gpu_log_driver_debug.GetValue()) {
|
||||
std::string driver_info;
|
||||
|
||||
// Device information
|
||||
const auto& props = properties.properties;
|
||||
driver_info += fmt::format("Device: {}\n", props.deviceName);
|
||||
driver_info += fmt::format("Driver Name: {}\n", properties.driver.driverName);
|
||||
driver_info += fmt::format("Driver Info: {}\n", properties.driver.driverInfo);
|
||||
|
||||
// Version information
|
||||
const u32 driver_version = props.driverVersion;
|
||||
const u32 api_version = props.apiVersion;
|
||||
driver_info += fmt::format("Driver Version: {}.{}.{}\n",
|
||||
VK_API_VERSION_MAJOR(driver_version),
|
||||
VK_API_VERSION_MINOR(driver_version),
|
||||
VK_API_VERSION_PATCH(driver_version));
|
||||
driver_info += fmt::format("Vulkan API Version: {}.{}.{}\n",
|
||||
VK_API_VERSION_MAJOR(api_version),
|
||||
VK_API_VERSION_MINOR(api_version),
|
||||
VK_API_VERSION_PATCH(api_version));
|
||||
driver_info += fmt::format("Driver ID: {}\n", static_cast<u32>(driver_id));
|
||||
|
||||
// Vendor and device IDs
|
||||
driver_info += fmt::format("Vendor ID: 0x{:04X}\n", props.vendorID);
|
||||
driver_info += fmt::format("Device ID: 0x{:04X}\n", props.deviceID);
|
||||
|
||||
// Extensions - separate QCOM extensions from others
|
||||
driver_info += "\n=== Loaded Vulkan Extensions ===\n";
|
||||
std::vector<std::string> qcom_exts;
|
||||
std::vector<std::string> other_exts;
|
||||
|
||||
for (const auto& ext : loaded_extensions) {
|
||||
if (ext.find("QCOM") != std::string::npos || ext.find("qcom") != std::string::npos) {
|
||||
qcom_exts.push_back(ext);
|
||||
} else {
|
||||
other_exts.push_back(ext);
|
||||
}
|
||||
}
|
||||
|
||||
// Log QCOM extensions first
|
||||
if (!qcom_exts.empty()) {
|
||||
driver_info += "\nQualcomm Proprietary Extensions:\n";
|
||||
for (const auto& ext : qcom_exts) {
|
||||
driver_info += fmt::format(" - {}\n", ext);
|
||||
}
|
||||
}
|
||||
|
||||
// Log other extensions
|
||||
if (!other_exts.empty()) {
|
||||
driver_info += "\nStandard Extensions:\n";
|
||||
for (const auto& ext : other_exts) {
|
||||
driver_info += fmt::format(" - {}\n", ext);
|
||||
}
|
||||
}
|
||||
|
||||
driver_info += fmt::format("\nTotal Extensions Loaded: {}\n", loaded_extensions.size());
|
||||
|
||||
GPU::Logging::GPULogger::GetInstance().LogDriverDebugInfo(driver_info);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::ShutdownGPULogging() {
|
||||
if (GPU::Logging::GPULogger::GetInstance().IsInitialized()) {
|
||||
GPU::Logging::GPULogger::GetInstance().Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -929,6 +929,10 @@ public:
|
||||
return nvidia_arch;
|
||||
}
|
||||
|
||||
/// GPU logging integration
|
||||
void InitializeGPULogging();
|
||||
void ShutdownGPULogging();
|
||||
|
||||
private:
|
||||
/// Checks if the physical device is suitable and configures the object state
|
||||
/// with all necessary info about its properties.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
namespace Vulkan {
|
||||
namespace {
|
||||
@@ -107,7 +109,17 @@ namespace Vulkan {
|
||||
MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a,
|
||||
const VmaAllocationInfo &info) noexcept
|
||||
: allocator{alloc}, allocation{a}, memory{info.deviceMemory},
|
||||
offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {}
|
||||
offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {
|
||||
// Log GPU memory allocation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_memory_tracking.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
|
||||
reinterpret_cast<uintptr_t>(memory),
|
||||
static_cast<u64>(size),
|
||||
0 // Memory property flags (not easily available from VMA)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryCommit::~MemoryCommit() { Release(); }
|
||||
|
||||
@@ -166,6 +178,15 @@ namespace Vulkan {
|
||||
|
||||
void MemoryCommit::Release() {
|
||||
if (allocation && allocator) {
|
||||
// Log GPU memory deallocation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_memory_tracking.GetValue() &&
|
||||
memory != VK_NULL_HANDLE) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation(
|
||||
reinterpret_cast<uintptr_t>(memory)
|
||||
);
|
||||
}
|
||||
|
||||
if (mapped_ptr) {
|
||||
vmaUnmapMemory(allocator, allocation);
|
||||
mapped_ptr = nullptr;
|
||||
@@ -218,7 +239,19 @@ namespace Vulkan {
|
||||
|
||||
VkImage handle{};
|
||||
VmaAllocation allocation{};
|
||||
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
|
||||
VmaAllocationInfo alloc_info{};
|
||||
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
|
||||
|
||||
// Log GPU memory allocation for images
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_memory_tracking.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
|
||||
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
|
||||
static_cast<u64>(alloc_info.size),
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
|
||||
);
|
||||
}
|
||||
|
||||
return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation,
|
||||
device.GetDispatchLoader());
|
||||
}
|
||||
@@ -245,6 +278,16 @@ namespace Vulkan {
|
||||
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
|
||||
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
|
||||
|
||||
// Log GPU memory allocation for buffers
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_memory_tracking.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
|
||||
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
|
||||
static_cast<u64>(alloc_info.size),
|
||||
property_flags
|
||||
);
|
||||
}
|
||||
|
||||
u8 *data = reinterpret_cast<u8 *>(alloc_info.pMappedData);
|
||||
const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{};
|
||||
const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
@@ -193,6 +193,13 @@ public:
|
||||
}
|
||||
|
||||
void SwapBuffers() override {
|
||||
if (auto window = dynamic_cast<QWindow*>(surface)) {
|
||||
if (!window->isExposed()) {
|
||||
LOG_DEBUG(Frontend, "SwapBuffers ignored: window not exposed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context->swapBuffers(surface);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -497,7 +497,10 @@ void GameListWorker::run() {
|
||||
DirEntryReady(game_list_dir);
|
||||
AddTitlesToGameList(game_list_dir);
|
||||
} else {
|
||||
watch_list.append(QString::fromStdString(game_dir.path));
|
||||
const QString qpath = QString::fromStdString(game_dir.path);
|
||||
if (QDir(qpath).exists()) {
|
||||
watch_list.append(qpath);
|
||||
}
|
||||
auto* const game_list_dir = new GameListDir(game_dir);
|
||||
DirEntryReady(game_list_dir);
|
||||
ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Qt on macOS doesn't define VMA shit
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include "qt_common/qt_string_lookup.h"
|
||||
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
|
||||
#undef VMA_IMPLEMENTATION
|
||||
@@ -516,17 +517,8 @@ MainWindow::MainWindow(bool has_broken_vulkan)
|
||||
|
||||
#ifdef ENABLE_UPDATE_CHECKER
|
||||
if (UISettings::values.check_for_updates) {
|
||||
update_future = QtConcurrent::run([]() -> QString {
|
||||
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != NULL) ||
|
||||
(strstr(Common::g_build_version, "alpha") != NULL) ||
|
||||
(strstr(Common::g_build_version, "beta") != NULL) ||
|
||||
(strstr(Common::g_build_version, "rc") != NULL));
|
||||
const std::optional<std::string> latest_release_tag =
|
||||
UpdateChecker::GetLatestRelease(is_prerelease);
|
||||
if (latest_release_tag && latest_release_tag.value() != Common::g_build_version) {
|
||||
return QString::fromStdString(latest_release_tag.value());
|
||||
}
|
||||
return QString{};
|
||||
update_future = QtConcurrent::run([]() -> std::optional<UpdateChecker::Update> {
|
||||
return UpdateChecker::GetUpdate();
|
||||
});
|
||||
update_watcher.connect(&update_watcher, &QFutureWatcher<QString>::finished, this,
|
||||
&MainWindow::OnEmulatorUpdateAvailable);
|
||||
@@ -4020,8 +4012,8 @@ void MainWindow::OnCaptureScreenshot() {
|
||||
|
||||
#ifdef ENABLE_UPDATE_CHECKER
|
||||
void MainWindow::OnEmulatorUpdateAvailable() {
|
||||
QString version_string = update_future.result();
|
||||
if (version_string.isEmpty())
|
||||
std::optional<UpdateChecker::Update> version = update_future.result();
|
||||
if (!version)
|
||||
return;
|
||||
|
||||
QMessageBox update_prompt(this);
|
||||
@@ -4030,14 +4022,14 @@ void MainWindow::OnEmulatorUpdateAvailable() {
|
||||
update_prompt.addButton(QMessageBox::Yes);
|
||||
update_prompt.addButton(QMessageBox::Ignore);
|
||||
update_prompt.setText(
|
||||
tr("Download the %1 update?").arg(version_string));
|
||||
tr("Download %1?").arg(QString::fromStdString(version->name)));
|
||||
update_prompt.exec();
|
||||
if (update_prompt.button(QMessageBox::Yes) == update_prompt.clickedButton()) {
|
||||
auto const full_url = fmt::format("{}/{}/releases/tag/",
|
||||
std::string{Common::g_build_auto_update_website},
|
||||
std::string{Common::g_build_auto_update_repo}
|
||||
);
|
||||
QDesktopServices::openUrl(QUrl(QString::fromStdString(full_url) + version_string));
|
||||
QDesktopServices::openUrl(QUrl(QString::fromStdString(full_url + version->tag)));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
#include "frontend_common/update_checker.h"
|
||||
#include "input_common/drivers/tas_input.h"
|
||||
#include "qt_common/config/qt_config.h"
|
||||
#include "qt_common/util/game.h"
|
||||
@@ -471,8 +472,8 @@ private:
|
||||
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
|
||||
|
||||
#ifdef ENABLE_UPDATE_CHECKER
|
||||
QFuture<QString> update_future;
|
||||
QFutureWatcher<QString> update_watcher;
|
||||
QFuture<std::optional<UpdateChecker::Update>> update_future;
|
||||
QFutureWatcher<std::optional<UpdateChecker::Update>> update_watcher;
|
||||
#endif
|
||||
|
||||
MultiplayerState* multiplayer_state = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user