Compare commits

..

6 Commits

Author SHA1 Message Date
lizzie a60c5ddde2 slug you 2026-01-30 21:07:02 +01:00
lizzie ab24a002f2 actually derivates are fine? 2026-01-30 21:07:02 +01:00
lizzie 817582463b handle null du/dx, dv/dy 2026-01-30 21:07:02 +01:00
lizzie 2ad9160d59 make silksongy worky againy 2026-01-30 21:07:02 +01:00
lizzie 1f35996959 license 2026-01-30 21:07:02 +01:00
lizzie d3ed681bc1 [maxwell/fermi_2d] gutter entire Software blitter
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-01-30 21:07:02 +01:00
133 changed files with 1737 additions and 4090 deletions
+2 -18
View File
@@ -1,6 +1,6 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2025 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,7 +29,6 @@ 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.
@@ -62,7 +61,6 @@ 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
@@ -103,20 +101,7 @@ cd src/android
chmod +x ./gradlew
set -- "$@" -DUSE_CCACHE="${CCACHE}"
nightly() {
[ "$NIGHTLY" = "true" ]
}
if nightly || [ "$DEVEL" != "true" ]; then
set -- "$@" -DENABLE_UPDATE_CHECKER=ON
fi
if nightly; then
NIGHTLY=true
else
NIGHTLY=false
fi
[ "$DEVEL" != "true" ] && set -- "$@" -DENABLE_UPDATE_CHECKER=ON
echo "-- building..."
@@ -125,7 +110,6 @@ echo "-- building..."
-Dorg.gradle.parallel="${CCACHE}" \
-Dorg.gradle.workers.max="${NUM_JOBS}" \
-PYUZU_ANDROID_ARGS="$*" \
-Pnightly="$NIGHTLY" \
--info
if [ -n "${ANDROID_KEYSTORE_B64}" ]; then
-2
View File
@@ -227,8 +227,6 @@ 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)
+9 -16
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project
@@ -8,8 +8,8 @@
include(GetSCMRev)
function(get_timestamp _var)
string(TIMESTAMP timestamp UTC)
set(${_var} "${timestamp}" PARENT_SCOPE)
string(TIMESTAMP timestamp UTC)
set(${_var} "${timestamp}" PARENT_SCOPE)
endfunction()
get_timestamp(BUILD_DATE)
@@ -28,21 +28,14 @@ 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
# 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")
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(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")
configure_file(scm_rev.cpp.in scm_rev.cpp @ONLY)
-4
View File
@@ -20,10 +20,6 @@ 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)
+2 -28
View File
@@ -5,7 +5,6 @@
// 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
@@ -38,9 +37,6 @@ android {
compileSdkVersion = "android-36"
ndkVersion = "28.2.13676358"
val isNightly =
providers.gradleProperty("nightly").orNull?.toBooleanStrictOrNull() ?: false
buildFeatures {
viewBinding = true
}
@@ -75,7 +71,6 @@ android {
val extraCMakeArgs =
(project.findProperty("YUZU_ANDROID_ARGS") as String?)?.split("\\s+".toRegex())
?: emptyList()
arguments.addAll(
listOf(
"-DENABLE_QT=0", // Don't use QT
@@ -94,13 +89,6 @@ android {
)
)
if (isNightly) {
arguments.addAll(listOf(
"-DENABLE_UPDATE_CHECKER=ON",
"-DNIGHTLY_BUILD=ON",
))
}
abiFilters("arm64-v8a")
}
}
@@ -137,12 +125,7 @@ android {
signingConfigs.getByName("default")
}
if (isNightly) {
applicationIdSuffix = ".nightly"
manifestPlaceholders += mapOf("appNameSuffix" to " Nightly")
} else {
manifestPlaceholders += mapOf("appNameSuffix" to "")
}
manifestPlaceholders += mapOf("appNameSuffix" to "")
isMinifyEnabled = true
isDebuggable = false
@@ -256,15 +239,6 @@ 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 {
@@ -284,7 +258,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 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -218,7 +218,7 @@ object NativeLibrary {
/**
* Checks for available updates.
*/
external fun checkForUpdate(): Array<String>?
external fun checkForUpdate(): String?
/**
* Return the URL to the release page
@@ -228,18 +228,13 @@ object NativeLibrary {
/**
* Return the URL to download the APK for the given version
*/
external fun getUpdateApkUrl(tag: String, artifact: String, packageId: String): String
external fun getUpdateApkUrl(version: 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).
*/
@@ -29,6 +29,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_DEBUG("debug"),
RENDERER_PATCH_OLD_QCOM_DRIVERS("patch_old_qcom_drivers"),
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
RENDERER_PROVOKING_VERTEX("provoking_vertex"),
RENDERER_DESCRIPTOR_INDEXING("descriptor_indexing"),
RENDERER_SAMPLE_SHADING("sample_shading"),
PICTURE_IN_PICTURE("picture_in_picture"),
USE_CUSTOM_RTC("custom_rtc_enabled"),
@@ -70,14 +72,7 @@ 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"),
// 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");
ENABLE_OVERLAY("enable_overlay");
// external fun isFrameSkippingEnabled(): Boolean
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -9,8 +9,7 @@ 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"),
GPU_LOG_LEVEL("gpu_log_level");
AUDIO_VOLUME("volume"),;
override fun getByte(needsGlobal: Boolean): Byte = NativeConfig.getByte(key, needsGlobal)
@@ -67,8 +67,7 @@ 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"),
GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size")
DEBUG_KNOBS("debug_knobs")
;
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
@@ -143,12 +143,25 @@ abstract class SettingsItem(
)
put(
SwitchSetting(
BooleanSetting.RENDERER_PROVOKING_VERTEX,
titleId = R.string.provoking_vertex,
descriptionId = R.string.provoking_vertex_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE,
titleId = R.string.vertex_input_dynamic_state,
descriptionId = R.string.vertex_input_dynamic_state_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_DESCRIPTOR_INDEXING,
titleId = R.string.descriptor_indexing,
descriptionId = R.string.descriptor_indexing_description
)
)
put(
SliderSetting(
IntSetting.RENDERER_SAMPLE_SHADING,
@@ -823,62 +836,6 @@ 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() &&
@@ -290,6 +290,8 @@ class SettingsFragmentPresenter(
add(IntSetting.RENDERER_DYNA_STATE.key)
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
add(BooleanSetting.RENDERER_PROVOKING_VERTEX.key)
add(BooleanSetting.RENDERER_DESCRIPTOR_INDEXING.key)
add(IntSetting.RENDERER_SAMPLE_SHADING.key)
add(HeaderSetting(R.string.display))
@@ -1218,15 +1220,6 @@ 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 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -222,14 +222,6 @@ 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,
@@ -416,40 +408,6 @@ 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 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.ui.main
@@ -183,26 +183,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val latestVersion = NativeLibrary.checkForUpdate()
if (latestVersion != null) {
runOnUiThread {
val tag: String = latestVersion[0]
val name: String = latestVersion[1]
showUpdateDialog(tag, name)
showUpdateDialog(latestVersion)
}
}
}.start()
}
private fun showUpdateDialog(tag: String, name: String) {
private fun showUpdateDialog(version: String) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.update_available)
.setMessage(getString(R.string.update_available_description, name))
.setMessage(getString(R.string.update_available_description, version))
.setPositiveButton(android.R.string.ok) { _, _ ->
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)
downloadAndInstallUpdate(version)
}
.setNeutralButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
@@ -215,11 +207,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
.show()
}
private fun downloadAndInstallUpdate(version: String, artifact: String) {
private fun downloadAndInstallUpdate(version: String) {
CoroutineScope(Dispatchers.IO).launch {
val packageId = applicationContext.packageName
val apkUrl = NativeLibrary.getUpdateApkUrl(version, artifact, packageId)
val apkFile = File(cacheDir, "update-$artifact.apk")
val apkUrl = NativeLibrary.getUpdateApkUrl(version, packageId)
val apkFile = File(cacheDir, "update-$version.apk")
withContext(Dispatchers.Main) {
showDownloadProgressDialog()
+10 -40
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -1595,6 +1595,7 @@ 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) {
@@ -1605,47 +1606,22 @@ 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 jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
JNIEXPORT jstring 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<UpdateChecker::Update> release =
const std::optional<std::string> latest_release_tag =
UpdateChecker::GetLatestRelease(is_prerelease);
if (!release || release->tag == Common::g_build_version) {
return nullptr;
if (latest_release_tag && latest_release_tag.value() != Common::g_build_version) {
return env->NewStringUTF(latest_release_tag.value().c_str());
}
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;
return nullptr;
}
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
@@ -1664,11 +1640,9 @@ 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 tag,
jstring artifact,
jstring version,
jstring packageId) {
const char* version_str = env->GetStringUTFChars(tag, nullptr);
const char* artifact_str = env->GetStringUTFChars(artifact, nullptr);
const char* version_str = env->GetStringUTFChars(version, nullptr);
const char* package_id_str = env->GetStringUTFChars(packageId, nullptr);
std::string variant;
@@ -1679,11 +1653,7 @@ 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);
@@ -1693,7 +1663,7 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
version_str,
apk_filename);
env->ReleaseStringUTFChars(tag, version_str);
env->ReleaseStringUTFChars(version, version_str);
env->ReleaseStringUTFChars(packageId, package_id_str);
return env->NewStringUTF(url.c_str());
}
@@ -511,7 +511,10 @@
<string name="disabled">معطل</string>
<string name="vertex_input_dynamic_state">حالة ديناميكية لإدخال الرأس</string>
<string name="vertex_input_dynamic_state_description">يتيح ميزة الحالة الديناميكية لإدخال الرأس لتحسين الجودة والأداء.</string>
<string name="provoking_vertex">الرأس المثير</string>
<string name="provoking_vertex_description">يحسن الإضاءة ومعالجة الرؤوس في بعض الألعاب. مدعوم فقط على وحدات معالجة الرسومات Vulkan 1.0+.</string>
<string name="descriptor_indexing">فهرسة الوصف</string>
<string name="descriptor_indexing_description">يحسن معالجة النسيج والمخزن المؤقت، بالإضافة إلى طبقة الترجمة Maxwell. مدعوم من بعض وحدات معالجة الرسومات Vulkan 1.1 وجميع وحدات معالجة الرسومات Vulkan 1.2+.</string>
<string name="sample_shading_fraction">تظليل العينة</string>
<string name="sample_shading_fraction_description">يسمح هذا الخيار بتنفيذ مُظلل الأجزاء لكل عينة في جزء متعدد العينات بدلاً من تنفيذه مرة واحدة لكل جزء. يُحسّن هذا من جودة الرسومات على حساب بعض الأداء.</string>
@@ -368,7 +368,10 @@
<string name="renderer_asynchronous_shaders_description">کۆمپایلکردنی شادەرەکان بەشێوەی ناڕێک. ئەمە ڕەنگە کەمکردنەوەی ستەتەر بکات بەڵام لەوانەیە هەڵەش دروست بکات.</string>
<string name="dyna_state">الحالة الديناميكية الممتدة</string>
<string name="disabled">ناچالاک</string>
<string name="provoking_vertex">الرأس المثير</string>
<string name="provoking_vertex_description">يحسن الإضاءة ومعالجة الرؤوس في بعض الألعاب. مدعوم فقط على وحدات معالجة الرسومات التي تدعم فولكان 1.0+.</string>
<string name="descriptor_indexing">فهرسة الواصفات</string>
<string name="descriptor_indexing_description">يحسن معالجة القوام والمخازن المؤقتة، بالإضافة إلى طبقة ترجمة ماكسويل. مدعوم من قبل بعض وحدات معالجة الرسومات التي تدعم فولكان 1.1 وجميع وحدات معالجة الرسومات التي تدعم فولكان 1.2+.</string>
<string name="display">پیشاندان</string>
<string name="renderer_screen_layout">ڕێکخستنی شاشە</string>
@@ -493,7 +493,10 @@
<string name="disabled">Vypnuto</string>
<string name="vertex_input_dynamic_state">Dynamický stav vstupu vrcholů (Vertex Input)</string>
<string name="vertex_input_dynamic_state_description">Aktivuje funkci dynamického stavu vstupu vrcholů (Vertex Input Dynamic State) pro lepší kvalitu a výkon.</string>
<string name="provoking_vertex">Určující vrchol</string>
<string name="provoking_vertex_description">Zlepšuje osvětlení a zpracování vrcholů v některých hrách. Podporováno pouze na GPU s API Vulkan 1.0+.</string>
<string name="descriptor_indexing">Indexování deskriptorů</string>
<string name="descriptor_indexing_description">Zlepšuje zpracování textur a bufferů, stejně jako překladovou vrstvu architektury Maxwell. Podporováno některými GPU s API Vulkan 1.1 a všemi GPU s API Vulkan 1.2+.</string>
<string name="display">Zobrazení</string>
<string name="renderer_screen_layout">Orientace</string>
@@ -491,7 +491,10 @@ Wird der Handheld-Modus verwendet, verringert es die Auflösung und erhöht die
<string name="disabled">Deaktiviert</string>
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
<string name="vertex_input_dynamic_state_description">Aktiviert die Funktion \"Vertex Input Dynamic State\" für bessere Qualität und Leistung.</string>
<string name="provoking_vertex">Provokanter Vertex</string>
<string name="provoking_vertex_description">Verbessert die Beleuchtung und die Vertex-Verarbeitung in einigen Spielen. Wird nur von GPUs mit Vulkan 1.0+ unterstützt.</string>
<string name="descriptor_indexing">Deskriptor-Indizierung</string>
<string name="descriptor_indexing_description">Verbessert die Textur- und Puffer-Verarbeitung sowie die Maxwell-Übersetzungsschicht. Wird von einigen Vulkan 1.1-GPUs und allen Vulkan 1.2+-GPUs unterstützt.</string>
<string name="sample_shading_fraction">Sample Shading</string>
<string name="sample_shading_fraction_description">Ermöglicht es dem Fragment-Shader, in einem Multisample-Fragment pro Sample anstatt einmal pro Fragment ausgeführt zu werden. Verbessert die Grafikqualität auf Kosten der Leistung.</string>
@@ -425,7 +425,10 @@
<string name="renderer_asynchronous_shaders_description">Compila los shaders de forma asíncrona. Esto puede reducir los tirones, pero también puede introducir errores gráficos.</string>
<string name="dyna_state">Estado dinámico extendido</string>
<string name="disabled">Desactivado</string>
<string name="provoking_vertex">Vértice provocante</string>
<string name="provoking_vertex_description">Mejora la iluminación y el manejo de vértices en ciertos juegos. Solo es compatible con las GPU Vulkan 1.0+.</string>
<string name="descriptor_indexing">Indexación del descriptor</string>
<string name="descriptor_indexing_description">Mejora la textura y el manejo del búfer, así como la capa de traducción Maxwell. Compatible con algunas GPU Vulkan 1.1 y todas las GPU Vulkan 1.2+.</string>
<string name="display">Pantalla</string>
<string name="renderer_screen_layout">Orientación</string>
@@ -73,7 +73,10 @@
<string name="uninstall_firmware_description">حذف فرمور آن را از دستگاه پاک می‌کند و ممکن است بر سازگاری بازی‌ها تأثیر بگذارد</string>
<string name="firmware_uninstalling">در حال حذف فرمور</string>
<string name="firmware_uninstalled_success">فرمور با موفقیت حذف شد</string>
<string name="provoking_vertex">رأس تحریک‌کننده</string>
<string name="provoking_vertex_description">نورپردازی و مدیریت رأس را در برخی بازی‌ها بهبود می‌بخشد. فقط در کارت‌های گرافیک با پشتیبانی از ولکان 1.0+ قابل استفاده است</string>
<string name="descriptor_indexing">نمایه‌سازی توصیفگر</string>
<string name="descriptor_indexing_description">مدیریت بافت‌ها و بافرها و همچنین لایه ترجمه ماکسول را بهبود می‌بخشد. توسط برخی کارت‌های گرافیک ولکان 1.1 و تمام کارت‌های گرافیک ولکان 1.2+ پشتیبانی می‌شود</string>
<string name="fast_gpu_time">فاکتور اورکلاک GPU</string>
<string name="fast_gpu_time_description">برای حداکثر عملکرد از 128 و برای حداکثر وفاداری گرافیکی از 512 استفاده کنید.</string>
<string name="fast_gpu_medium">متوسط (256)</string>
@@ -441,7 +441,10 @@
<string name="disabled">Désactivé</string>
<string name="vertex_input_dynamic_state">État dynamique d\'entrée de sommet</string>
<string name="vertex_input_dynamic_state_description">Active la fonctionnalité d\'état dynamique des entrées de sommets pour une meilleure qualité et de meilleures performances.</string>
<string name="provoking_vertex">Provoque des Vertex</string>
<string name="provoking_vertex_description">Améliore l`éclairage et la gestion des vertex dans certains jeux. Pris en charge uniquement par les GPU Vulkan 1.0+.</string>
<string name="descriptor_indexing">Indexation des descripteurs</string>
<string name="descriptor_indexing_description">Améliore la gestion des textures et des tampons, ainsi que la couche de traduction Maxwell. Pris en charge par certains GPU Vulkan 1.1 et tous les GPU Vulkan 1.2+.</string>
<string name="display">Affichage</string>
<string name="renderer_screen_layout">Orientation</string>
@@ -399,7 +399,10 @@
<string name="renderer_asynchronous_shaders_description">מקמפל שיידרים באופן אסינכרוני. זה עשוי להפחית קפיצות אך גם עלול לגרום לתקלות גרפיות.</string>
<string name="dyna_state">מצב דינמי מורחב</string>
<string name="disabled">מושבת</string>
<string name="provoking_vertex">קודקוד מעורר</string>
<string name="provoking_vertex_description">משפר תאורה וטיפול בקודקודים במשחקים מסוימים. נתמך רק בכרטיסי מסך עם Vulkan 1.0+.</string>
<string name="descriptor_indexing">אינדוקס תיאורים</string>
<string name="descriptor_indexing_description">משפר טיפול במרקמים ומאגרים, כמו גם בשכבת התרגום של Maxwell. נתמך בחלק מכרטיסי ה-Vulkan 1.1 ובכל כרטיסי ה-Vulkan 1.2+.</string>
<string name="display">תצוגה</string>
<string name="renderer_screen_layout">כיוון</string>
@@ -387,8 +387,10 @@
<string name="renderer_asynchronous_shaders_description">A shaderek aszinkron fordítása. Csökkentheti a belassulásokat, de hibákat is okozhat.</string>
<string name="dyna_state">Kiterjesztett Dinamikus Állapot</string>
<string name="disabled">Letiltva</string>
<string name="provoking_vertex">Provokáló csúcs</string>
<string name="provoking_vertex_description">Javítja a világítást és a csúcskezelést bizonyos játékokban. Csak Vulkan 1.0+ GPU-kon támogatott.</string>
<string name="descriptor_indexing">Deskriptor Indexelés</string>
<string name="descriptor_indexing_description">Javítja a textúrák és pufferek kezelését, valamint a Maxwell fordítási réteget. Néhány Vulkan 1.1 GPU és minden Vulkan 1.2+ GPU támogatja.</string>
<string name="display">Kijelző</string>
<string name="renderer_screen_layout">Orientáció</string>
@@ -419,7 +419,10 @@
<string name="renderer_asynchronous_shaders_description">Kompilasi shader secara asinkron. Ini dapat mengurangi lag tetapi juga dapat menyebabkan glitch.</string>
<string name="dyna_state">Status Dinamis Ekstensi</string>
<string name="disabled">Dinonaktifkan</string>
<string name="provoking_vertex">Vertex Provokasi</string>
<string name="provoking_vertex_description">Meningkatkan pencahayaan dan penanganan vertex di beberapa game. Hanya didukung di GPU Vulkan 1.0+.</string>
<string name="descriptor_indexing">Pengindeks Deskriptor</string>
<string name="descriptor_indexing_description">Meningkatkan penanganan tekstur dan buffer, serta lapisan terjemahan Maxwell. Didukung oleh beberapa GPU Vulkan 1.1 dan semua GPU Vulkan 1.2+.</string>
<string name="display">Tampilan</string>
<string name="renderer_screen_layout">Orientasi</string>
@@ -426,7 +426,10 @@
<string name="renderer_asynchronous_shaders_description">Compila gli shader in modo asincrono. Può ridurre gli scatti, ma può anche causare glitch grafici.</string>
<string name="dyna_state">Stato dinamico esteso</string>
<string name="disabled">Disabilitato</string>
<string name="provoking_vertex">Vertice provocante</string>
<string name="provoking_vertex_description">Migliora illuminazione e gestione dei vertici in alcuni giochi. Supportato solo su GPU Vulkan 1.0+.</string>
<string name="descriptor_indexing">Indicizzazione descrittori</string>
<string name="descriptor_indexing_description">Migliora la gestione di texture e buffer, nonché il livello di traduzione Maxwell. Supportato da alcune GPU Vulkan 1.1 e tutte le GPU Vulkan 1.2+.</string>
<string name="display">Schermo</string>
<string name="renderer_screen_layout">Orientamento</string>
@@ -385,7 +385,10 @@
<string name="renderer_asynchronous_shaders_description">シェーダーを非同期でコンパイルします。これによりカクつきが減る可能性がありますが、グラフィックの不具合が発生する場合もあります。</string>
<string name="dyna_state">拡張ダイナミックステート</string>
<string name="disabled">無効</string>
<string name="provoking_vertex">プロボーキング頂点</string>
<string name="provoking_vertex_description">特定のゲームで照明と頂点処理を改善します。Vulkan 1.0+ GPUでのみサポートされています。</string>
<string name="descriptor_indexing">ディスクリプタインデキシング</string>
<string name="descriptor_indexing_description">テクスチャとバッファの処理、およびMaxwell翻訳レイヤーを改善します。一部のVulkan 1.1 GPUとすべてのVulkan 1.2+ GPUでサポートされています。</string>
<string name="display">ディスプレイ</string>
<string name="renderer_screen_layout">画面の向き</string>
@@ -385,7 +385,10 @@
<string name="renderer_asynchronous_shaders_description">셰이더를 비동기적으로 컴파일합니다. 이로 인해 끊김 현상이 줄어들 수 있지만 그래픽 오류가 발생할 수도 있습니다.</string>
<string name="dyna_state">확장 동적 상태</string>
<string name="disabled">비활성화됨</string>
<string name="provoking_vertex">프로보킹 버텍스</string>
<string name="provoking_vertex_description">일부 게임에서 조명과 버텍스 처리를 개선합니다. Vulkan 1.0+ GPU에서만 지원됩니다.</string>
<string name="descriptor_indexing">디스크립터 인덱싱</string>
<string name="descriptor_indexing_description">텍스처 및 버퍼 처리와 Maxwell 변환 레이어를 개선합니다. 일부 Vulkan 1.1 GPU 및 모든 Vulkan 1.2+ GPU에서 지원됩니다.</string>
<string name="display">디스플레이</string>
<string name="renderer_screen_layout">화면 방향</string>
@@ -368,7 +368,10 @@
<string name="renderer_asynchronous_shaders_description">Kompilerer shadere asynkront. Dette kan redusere rykk, men kan også forårsake grafiske feil.</string>
<string name="dyna_state">Utvidet dynamisk tilstand</string>
<string name="disabled">Deaktivert</string>
<string name="provoking_vertex">Provoserende vertex</string>
<string name="provoking_vertex_description">Forbedrer belysning og vertexhåndtering i enkelte spill. Støttes kun på Vulkan 1.0+ GPU-er.</string>
<string name="descriptor_indexing">Beskrivelsesindeksering</string>
<string name="descriptor_indexing_description">Forbedrer tekstur- og bufferhåndtering, samt Maxwell-oversettelseslaget. Støttes av noen Vulkan 1.1 GPU-er og alle Vulkan 1.2+ GPU-er.</string>
<string name="display">Skjerm</string>
<string name="renderer_screen_layout">Orientering</string>
@@ -493,7 +493,10 @@
<string name="disabled">Wyłączone</string>
<string name="vertex_input_dynamic_state">Dynamiczny stan wejścia wierzchołków</string>
<string name="vertex_input_dynamic_state_description">Włącza funkcję dynamicznego stanu wejścia wierzchołków, poprawiając jakość i wydajność.</string>
<string name="provoking_vertex">Wierzchołek prowokujący</string>
<string name="provoking_vertex_description">Poprawia oświetlenie i obsługę wierzchołków w niektórych grach. Obsługiwane tylko przez GPU Vulkan 1.0+.</string>
<string name="descriptor_indexing">Indeksowanie deskryptorów</string>
<string name="descriptor_indexing_description">Poprawia obsługę tekstur i buforów oraz warstwę tłumaczenia Maxwell. Obsługiwane przez niektóre GPU Vulkan 1.1 i wszystkie GPU Vulkan 1.2+.</string>
<string name="sample_shading_fraction">Cieniowanie próbkowane</string>
<string name="sample_shading_fraction_description">Pozwala uruchamiać shader fragmentów dla każdej próbki w wielopróbkowanym fragmencie zamiast raz na fragment. Poprawia jakość grafiki kosztem części wydajności.</string>
@@ -476,7 +476,10 @@
<string name="disabled">Desativado</string>
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
<string name="vertex_input_dynamic_state_description">Ativa o recurso de vertex input dynamic state para melhor qualidade e desempenho.</string>
<string name="provoking_vertex">Provoking Vertex</string>
<string name="provoking_vertex_description">Vértice Provocante: Melhora a iluminação e o processamento de vértices em certos jogos. Suportado apenas em GPUs com Vulkan 1.0 ou superior.</string>
<string name="descriptor_indexing">Descriptor Indexing</string>
<string name="descriptor_indexing_description">Indexação de Descritores: Melhora o processamento de texturas e buffers, assim como a camada de tradução Maxwell. Suportado por algumas GPUs Vulkan 1.1 e todas as GPUs Vulkan 1.2 ou superiores.</string>
<string name="display">Tela</string>
<string name="renderer_screen_layout">Orientação</string>
@@ -391,7 +391,10 @@
<string name="renderer_asynchronous_shaders_description">Compila shaders de forma assíncrona. Isto pode reduzir engasgadelas, mas também pode introduzir falhas gráficas.</string>
<string name="dyna_state">Estado Dinâmico Estendido</string>
<string name="disabled">Desativado</string>
<string name="provoking_vertex">Vértice provocante</string>
<string name="provoking_vertex_description">Melhora a iluminação e o tratamento de vértices em certos jogos. Suportado apenas em GPUs Vulkan 1.0+.</string>
<string name="descriptor_indexing">Indexação de descritores</string>
<string name="descriptor_indexing_description">Melhora o tratamento de texturas e buffers, assim como a camada de tradução Maxwell. Suportado por algumas GPUs Vulkan 1.1 e todas Vulkan 1.2+.</string>
<string name="display">Ecrã</string>
<string name="renderer_screen_layout">Oriantação</string>
@@ -503,7 +503,10 @@
<string name="disabled">Отключено</string>
<string name="vertex_input_dynamic_state">Динамическое состояние ввода вершин</string>
<string name="vertex_input_dynamic_state_description">Включает функцию динамического состояния ввода вершин для повышения качества и производительности</string>
<string name="provoking_vertex">Определяющая вершина</string>
<string name="provoking_vertex_description">Улучшает освещение и обработку вершин в некоторых играх. Поддерживается только ГПУ с Vulkan 1.0+.</string>
<string name="descriptor_indexing">Индексирование дескрипторов</string>
<string name="descriptor_indexing_description">Улучшает обработку текстур и буферов, а также слой перевода Maxwell. Поддерживается некоторыми ГПУ Vulkan 1.1 и всеми ГПУ Vulkan 1.2+.</string>
<string name="sample_shading_fraction">Сэмпловое затенение</string>
<string name="sample_shading_fraction_description">Позволяет шейдеру фрагментов выполняться для каждого сэмпла в многосэмпловом фрагменте, а не один раз на фрагмент. Улучшает качество графики ценой некоторого падения производительности.</string>
@@ -390,7 +390,10 @@
<string name="renderer_asynchronous_shaders_description">Саставља схадера асинхроно. Ово може умањити затезаче, али могу такође да уведе пропусте.</string>
<string name="dyna_state">Проширена динамичка држава</string>
<string name="disabled">Искључено</string>
<string name="provoking_vertex">Провоцирајући врх</string>
<string name="provoking_vertex_description">Побољшава осветљење и вертификат руковања у одређеним играма. Подржан само на Вулкану 1.0+ ГПУ-у.</string>
<string name="descriptor_indexing">Индексирање дескриптора</string>
<string name="descriptor_indexing_description">Побољшава текстуру и руковање међуспремника, као и преводилачки слој Маквелл. Подржани од стране неких Вулкана 1.1 ГПУ-а и сви Вулкан 1.2+ ГПУ.</string>
<string name="display">Приказ</string>
<string name="renderer_screen_layout">Оријентација</string>
@@ -507,7 +507,10 @@
<string name="disabled">Вимкнено</string>
<string name="vertex_input_dynamic_state">Динамічний стан введення вершин</string>
<string name="vertex_input_dynamic_state_description">Вмикає можливість динамічного стану введення вершин для кращих якості й продуктивності.</string>
<string name="provoking_vertex">Провокативна вершина</string>
<string name="provoking_vertex_description">Покращує освітлення та взаємодію з вершинами у деяких іграх. Лише для ГП з підтримкою Vulkan 1.0+.</string>
<string name="descriptor_indexing">Індексація дескрипторів</string>
<string name="descriptor_indexing_description">Покращує обробку текстур та буферів, а також шар перекладу Maxwell. Підтримується деякими GPU Vulkan 1.1 та всіма GPU Vulkan 1.2+.</string>
<string name="sample_shading_fraction">Простий шейдинг</string>
<string name="sample_shading_fraction_description">Дозволяє виконувати фрагмент шейдера для кожного зразка в багатозразковому фрагменті замість одного разу для кожного фрагмента. Покращує якість графікі ціною втрати продуктивності.</string>
@@ -366,7 +366,10 @@
<string name="renderer_asynchronous_shaders_description">Biên dịch shader không đồng bộ. Điều này có thể giảm giật lag nhưng cũng có thể gây ra lỗi đồ họa.</string>
<string name="dyna_state">Trạng thái động mở rộng</string>
<string name="disabled">Đã tắt</string>
<string name="provoking_vertex">Đỉnh kích hoạt</string>
<string name="provoking_vertex_description">Cải thiện ánh sáng và xử lý đỉnh trong một số trò chơi. Chỉ được hỗ trợ trên GPU Vulkan 1.0+.</string>
<string name="descriptor_indexing">Lập chỉ mục bộ mô tả</string>
<string name="descriptor_indexing_description">Cải thiện xử lý kết cấu và bộ đệm, cũng như lớp dịch Maxwell. Được hỗ trợ bởi một số GPU Vulkan 1.1 và tất cả GPU Vulkan 1.2+.</string>
<string name="display">Hiển thị</string>
<string name="renderer_screen_layout">Hướng màn hình</string>
@@ -501,7 +501,10 @@
<string name="disabled">已禁用</string>
<string name="vertex_input_dynamic_state">顶点输入动态状态</string>
<string name="vertex_input_dynamic_state_description">开启顶点输入动态状态功能来获得更好的质量和性能。</string>
<string name="provoking_vertex">引发顶点</string>
<string name="provoking_vertex_description">改善某些游戏中的光照和顶点处理。仅支持Vulkan 1.0+ GPU。</string>
<string name="descriptor_indexing">描述符索引</string>
<string name="descriptor_indexing_description">改进纹理和缓冲区处理以及Maxwell转换层。部分Vulkan 1.1 GPU和所有Vulkan 1.2+ GPU支持。</string>
<string name="sample_shading_fraction">采样着色</string>
<string name="sample_shading_fraction_description">允许片段着色器在多采样片段中每个样本执行一次,而不是每个片段执行一次。以提高性能为代价改善图形质量。</string>
@@ -472,7 +472,10 @@
<string name="disabled">已停用</string>
<string name="vertex_input_dynamic_state">頂點輸入動態狀態</string>
<string name="vertex_input_dynamic_state_description">啟用頂點輸入動態狀態以取得更佳的品質及性能</string>
<string name="provoking_vertex">引發頂點</string>
<string name="provoking_vertex_description">改善某些遊戲中的光照和頂點處理。僅支援Vulkan 1.0+ GPU。</string>
<string name="descriptor_indexing">描述符索引</string>
<string name="descriptor_indexing_description">改進紋理和緩衝區處理以及Maxwell轉換層。部分Vulkan 1.1 GPU和所有Vulkan 1.2+ GPU支援。</string>
<string name="display">顯示</string>
<string name="renderer_screen_layout">方向</string>
+1 -18
View File
@@ -611,7 +611,7 @@
</integer-array>
<string-array name="dynaStateEntries">
<item>Core</item>
<item>@string/disabled</item>
<item>ExtendedDynamicState 1</item>
<item>ExtendedDynamicState 2</item>
<item>ExtendedDynamicState 3</item>
@@ -631,21 +631,4 @@
<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,9 +325,6 @@
<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>
@@ -520,6 +517,10 @@
<string name="disabled">Disabled</string>
<string name="vertex_input_dynamic_state">Vertex Input Dynamic State</string>
<string name="vertex_input_dynamic_state_description">Enables vertex input dynamic state feature for better quality and performance.</string>
<string name="provoking_vertex">Provoking Vertex</string>
<string name="provoking_vertex_description">Improves lighting and vertex handling in certain games. Only supported on Vulkan 1.0+ GPUs.</string>
<string name="descriptor_indexing">Descriptor Indexing</string>
<string name="descriptor_indexing_description">Improves texture and buffer handling, as well as the Maxwell translation layer. Supported by some Vulkan 1.1 GPUs and all Vulkan 1.2+ GPUs.</string>
<string name="sample_shading_fraction">Sample Shading</string>
<string name="sample_shading_fraction_description">Allows the fragment shader to execute per sample in a multi-sampled fragment instead once per fragment. Improves graphics quality at the cost of some performance.</string>
@@ -551,26 +552,6 @@
<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 -->
-1
View File
@@ -49,7 +49,6 @@ SWITCHABLE(CpuBackend, true);
SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(GpuLogLevel, true);
SWITCHABLE(Language, true);
SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false);
+4 -14
View File
@@ -535,9 +535,9 @@ struct Values {
#elif defined (__FreeBSD__)
ExtendedDynamicState::EDS3,
#elif defined (ANDROID)
ExtendedDynamicState::Core,
ExtendedDynamicState::Disabled,
#elif defined (__APPLE__)
ExtendedDynamicState::Core,
ExtendedDynamicState::Disabled,
#else
ExtendedDynamicState::EDS2,
#endif
@@ -559,6 +559,8 @@ struct Values {
true,
#endif
"vertex_input_dynamic_state", Category::RendererExtensions};
SwitchableSetting<bool> provoking_vertex{linkage, false, "provoking_vertex", Category::RendererExtensions};
SwitchableSetting<bool> descriptor_indexing{linkage, false, "descriptor_indexing", Category::RendererExtensions};
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
Setting<bool> renderer_shader_feedback{linkage, false, "shader_feedback",
@@ -736,18 +738,6 @@ 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,
+2 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project
@@ -153,8 +153,7 @@ ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
ENUM(TemperatureUnits, Celsius, Fahrenheit)
ENUM(ExtendedDynamicState, Core, EDS1, EDS2, EDS3);
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
template <typename Type>
inline std::string_view CanonicalizeEnum(Type id) {
+17 -38
View File
@@ -1,15 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 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>
@@ -20,8 +18,6 @@
#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>
@@ -32,7 +28,7 @@
#endif
#ifdef __FreeBSD__
# define cpu_set_t cpuset_t
#define cpu_set_t cpuset_t
#endif
namespace Common {
@@ -81,14 +77,22 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
#endif
}
void SetCurrentThreadName(const char* name) {
#ifdef _MSC_VER
// Sets the debugger-visible name of the current thread.
if (auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription"); pf)
// 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)
pf(GetCurrentThread(), UTF8ToUTF16W(name).data()); // Windows 10+
else
; // No-op
#elif defined(__APPLE__)
}
#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__
pthread_setname_np(name);
#elif defined(__HAIKU__)
rename_thread(find_thread(NULL), name);
@@ -108,38 +112,13 @@ void SetCurrentThreadName(const char* name) {
pthread_setname_np(pthread_self(), buf);
}
#elif defined(_WIN32)
// 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
// mingw stub
(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
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 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
+12 -11
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
@@ -7,7 +7,6 @@
#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"
@@ -26,8 +25,11 @@ 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() {
@@ -186,15 +188,14 @@ void CpuManager::ShutdownThread() {
void CpuManager::RunThread(std::stop_token token, std::size_t core) {
/// Initialization
system.RegisterCoreThread(core);
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
std::string name;
if (is_multicore) {
name = "CPUCore_" + std::to_string(core);
} else {
name = "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 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 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.instructions.begin(); iter != block.instructions.end(); ++iter) {
for (auto iter = block.begin(); iter != block.end(); ++iter) {
IR::Inst* inst = &*iter;
switch (inst->GetOpcode()) {
@@ -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
/* This file is part of the dynarmic project.
@@ -582,9 +582,11 @@ 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.instructions)
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC)
for (auto& inst : ctx.block) {
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC) {
return;
}
}
EmitSetUpperLocationDescriptor(code, ctx, ctx.block.EndLocation(), ctx.block.Location());
}
@@ -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
/* This file is part of the dynarmic project.
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -193,6 +193,7 @@ 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()) {
@@ -207,6 +208,7 @@ 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());
@@ -373,6 +375,7 @@ 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) {
@@ -397,6 +400,7 @@ 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);
@@ -460,6 +464,7 @@ 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 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -302,12 +302,17 @@ 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);
@@ -332,6 +337,7 @@ 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 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -15,24 +15,26 @@
namespace Dynarmic::Backend {
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>::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>::ClearCache() {
template<typename ProgramCounterType>
void BlockRangeInformation<ProgramCounterType>::ClearCache() {
block_ranges.clear();
}
template<typename P>
ankerl::unordered_dense::set<IR::LocationDescriptor> BlockRangeInformation<P>::InvalidateRanges(const boost::icl::interval_set<P>& ranges) {
template<typename ProgramCounterType>
ankerl::unordered_dense::set<IR::LocationDescriptor> BlockRangeInformation<ProgramCounterType>::InvalidateRanges(const boost::icl::interval_set<ProgramCounterType>& 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,6 +1,3 @@
// 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
@@ -18,13 +15,15 @@
namespace Dynarmic::Backend {
template<typename P>
template<typename ProgramCounterType>
class BlockRangeInformation {
public:
void AddRange(boost::icl::discrete_interval<P> range, IR::LocationDescriptor location);
void AddRange(boost::icl::discrete_interval<ProgramCounterType> range, IR::LocationDescriptor location);
void ClearCache();
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;
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;
};
} // namespace Dynarmic::Backend
@@ -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
/* 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.instructions.begin(); iter != block.instructions.end(); ++iter) {
for (auto iter = block.begin(); iter != block.end(); ++iter) {
IR::Inst* inst = &*iter;
switch (inst->GetOpcode()) {
@@ -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
/* 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.instructions.begin(); iter != block.instructions.end(); ++iter) [[likely]] {
for (auto iter = block.begin(); iter != block.end(); ++iter) [[likely]] {
auto* inst = &*iter;
// Call the relevant Emit* member function.
switch (inst->GetOpcode()) {
@@ -727,9 +727,11 @@ void A32EmitX64::EmitA32BXWritePC(A32EmitContext& ctx, IR::Inst* inst) {
}
void A32EmitX64::EmitA32UpdateUpperLocationDescriptor(A32EmitContext& ctx, IR::Inst*) {
for (auto& inst : ctx.block.instructions)
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC)
for (auto& inst : ctx.block) {
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC) {
return;
}
}
EmitSetUpperLocationDescriptor(ctx.EndLocation(), ctx.Location());
}
@@ -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
/* 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.instructions) {
for (auto& inst : block) {
auto const opcode = inst.GetOpcode();
// Call the relevant Emit* member function.
switch (opcode) {
@@ -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
/* This file is part of the dynarmic project.
@@ -38,6 +38,11 @@ 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 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -56,7 +56,11 @@ 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 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -34,7 +34,14 @@ namespace Dynarmic::Backend::X64 {
using namespace Xbyak::util;
#define ICODE(NAME) [&](auto... args) { if (esize == 32) code.NAME##d(args...); else code.NAME##q(args...); }
#define ICODE(NAME) \
[&code](auto... args) { \
if constexpr (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 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 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.instructions.begin(), ir.block.instructions.end(), [](const IR::Inst& inst) {
return std::all_of(ir.block.begin(), ir.block.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.instructions.empty()) {
if (!v.ir.block.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,6 +1,3 @@
// 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
@@ -129,7 +126,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.instructions.empty() && !options.wall_clock_cntpct) {
if (!ir.block.empty() && !options.wall_clock_cntpct) {
ir.block.CycleCount()--;
ir.SetTerm(IR::Term::LinkBlock{*ir.current_location});
return false;
+44 -22
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -100,39 +100,57 @@ 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 (auto const name = arg.GetInst()->GetName())
if (const unsigned name = arg.GetInst()->GetName()) {
return fmt::format("%{}", name);
return fmt::format("%<unnamed inst {:016x}>", u64(arg.GetInst()));
}
return fmt::format("%<unnamed inst {:016x}>", reinterpret_cast<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.instructions) {
for (const auto& inst : block) {
const Opcode op = inst.GetOpcode();
ret += fmt::format("[{:016x}] ", reinterpret_cast<u64>(&inst));
@@ -162,9 +180,13 @@ std::string DumpBlock(const IR::Block& block) noexcept {
}
}
ret += fmt::format(" (uses: {})", inst.UseCount()) + '\n';
ret += fmt::format(" (uses: {})", inst.UseCount());
ret += '\n';
}
ret += "terminal = " + TerminalToString(block.GetTerminal()) + '\n';
return ret;
}
+33 -7
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -23,6 +23,7 @@
#include "dynarmic/ir/microinstruction.h"
#include "dynarmic/ir/terminal.h"
#include "dynarmic/ir/value.h"
#include "dynarmic/ir/dense_list.h"
namespace Dynarmic::IR {
@@ -33,7 +34,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 alignas(4096) Block final {
class Block final {
public:
//using instruction_list_type = dense_list<Inst>;
using instruction_list_type = mcl::intrusive_list<Inst>;
@@ -50,12 +51,37 @@ 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 iterator AppendNewInst(const Opcode opcode, const std::initializer_list<IR::Value> args) noexcept {
return PrependNewInst(instructions.end(), opcode, args);
inline void AppendNewInst(const Opcode opcode, const std::initializer_list<IR::Value> args) noexcept {
PrependNewInst(end(), opcode, args);
}
iterator PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args) noexcept;
@@ -139,9 +165,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, 30> inlined_inst;
boost::container::static_vector<Inst, 14> inlined_inst;
/// List of instructions in this block.
instruction_list_type instructions;
/// "Long/far" memory pool
@@ -161,7 +187,7 @@ public:
/// Number of cycles this block takes to execute.
size_t cycle_count = 0;
};
static_assert(sizeof(Block) == 4096);
static_assert(sizeof(Block) == 2048);
/// Returns a string representation of the contents of block. Intended for debugging.
std::string DumpBlock(const IR::Block& block) noexcept;
+61
View File
@@ -0,0 +1,61 @@
// 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;
};
}
+8 -5
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -65,12 +65,15 @@ enum class MemOp {
PREFETCH,
};
/// @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.
/**
* 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.instructions.end()) {}
explicit IREmitter(Block& block)
: block(block), insertion_point(block.end()) {}
Block& block;
+22 -17
View File
@@ -33,7 +33,7 @@
namespace Dynarmic::Optimization {
static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
for (auto& inst : block.instructions) {
for (auto& inst : block) {
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.instructions.rbegin(); inst != block.instructions.rend(); ++inst) {
for (auto inst = block.rbegin(); inst != block.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.instructions.begin(); inst != block.instructions.end(); ++inst) {
for (auto inst = block.begin(); inst != block.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.instructions) {
for (auto& inst : block) {
if (inst.GetOpcode() != IR::Opcode::A64DataCacheOperationRaised) {
continue;
}
@@ -541,7 +541,7 @@ static void A64GetSetElimination(IR::Block& block) {
do_nothing();
};
for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) {
for (auto inst = block.begin(); inst != block.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.instructions) {
for (auto& inst : block) {
auto const opcode = inst.GetOpcode();
switch (opcode) {
case Op::LeastSignificantWord:
@@ -1221,20 +1221,25 @@ 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.instructions.rbegin(); it != block.instructions.rend(); ++it)
for (auto it = block.rbegin(); it != block.rend(); ++it)
if (!it->HasUses() && !MayHaveSideEffects(it->GetOpcode()))
it->Invalidate();
}
static void IdentityRemovalPass(IR::Block& block) {
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());
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());
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);
@@ -1248,7 +1253,7 @@ static void IdentityRemovalPass(IR::Block& block) {
static void NamingPass(IR::Block& block) {
u32 name = 1;
for (auto& inst : block.instructions)
for (auto& inst : block)
inst.SetName(name++);
}
@@ -1397,7 +1402,7 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
IR::IREmitter ir{block};
for (auto& inst : block.instructions) {
for (auto& inst : block) {
ir.SetInsertionPointBefore(&inst);
switch (inst.GetOpcode()) {
@@ -1453,7 +1458,7 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
}
static void VerificationPass(const IR::Block& block) {
for (auto const& inst : block.instructions) {
for (auto const& inst : block) {
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);
@@ -1461,7 +1466,7 @@ static void VerificationPass(const IR::Block& block) {
}
}
ankerl::unordered_dense::map<IR::Inst*, size_t> actual_uses;
for (auto const& inst : block.instructions) {
for (auto const& inst : block) {
for (size_t i = 0; i < inst.NumArgs(); i++)
if (IR::Value const arg = inst.GetArg(i); !arg.IsImmediate())
actual_uses[arg.GetInst()]++;
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* 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.instructions) {
for (const auto& ir_inst : block) {
switch (ir_inst.GetOpcode()) {
case IR::Opcode::A32ExceptionRaised:
case IR::Opcode::A32CallSupervisor:
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* 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.instructions) {
for (const auto& ir_inst : block) {
switch (ir_inst.GetOpcode()) {
case IR::Opcode::A64ExceptionRaised:
case IR::Opcode::A64CallSupervisor:
+3 -3
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* 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.instructions.cbegin(), block.instructions.cend(), [](auto const& e) {
return std::find_if(block.cbegin(), block.cend(), [](auto const& e) {
return e.GetOpcode() == IR::Opcode::A32ExceptionRaised && A32::Exception(e.GetArg(1).GetU64()) == A32::Exception::DecodeError;
}) != block.instructions.cend();
}) != block.cend();
};
for (auto iter = table.cbegin(); iter != table.cend(); ++iter) {
+2 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* 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.instructions) {
for (const auto& ir_inst : block) {
switch (ir_inst.GetOpcode()) {
// A32
case IR::Opcode::A32GetFpscr:
+6 -10
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright Citra Emulator Project / Azahar Emulator Project
@@ -45,7 +45,8 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
};
client->set_follow_location(true);
httplib::Result result = client->send(request);
httplib::Result result;
result = client->send(request);
if (!result) {
LOG_ERROR(Frontend, "GET to {}{} returned null", url, path);
@@ -77,7 +78,7 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
}
}
std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool include_prereleases) {
std::optional<std::string> 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});
@@ -95,9 +96,6 @@ std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool includ
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;
@@ -107,7 +105,7 @@ std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool includ
if (!latest_tag_has_release)
return {};
return Update{latest_tag, latest_name};
return latest_tag;
} 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);
@@ -116,9 +114,7 @@ std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool includ
return {};
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name");
const std::string latest_name = nlohmann::json::parse(response.value()).at("name");
return Update{latest_tag, latest_name};
return latest_tag;
}
} catch (nlohmann::detail::out_of_range &) {
+2 -8
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright Citra Emulator Project / Azahar Emulator Project
@@ -11,12 +11,6 @@
#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<Update> GetLatestRelease(bool include_prereleases);
std::optional<std::string> GetLatestRelease(bool include_prereleases);
} // namespace UpdateChecker
+13 -1
View File
@@ -345,6 +345,18 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
tr("Vertex Input Dynamic State"),
tr("Enables vertex input dynamic state feature for better quality and performance."));
INSERT(Settings,
provoking_vertex,
tr("Provoking Vertex"),
tr("Improves lighting and vertex handling in some games.\n"
"Only Vulkan 1.0+ devices support this extension."));
INSERT(Settings,
descriptor_indexing,
tr("Descriptor Indexing"),
tr("Improves texture & buffer handling and the Maxwell translation layer.\n"
"Some Vulkan 1.1+ and all 1.2+ devices support this extension."));
INSERT(Settings,
sample_shading,
tr("Sample Shading"),
@@ -744,7 +756,7 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
translations->insert({Settings::EnumMetadata<Settings::ExtendedDynamicState>::Index(),
{
PAIR(ExtendedDynamicState, Core, tr("Core")),
PAIR(ExtendedDynamicState, Disabled, tr("Disabled")),
PAIR(ExtendedDynamicState, EDS1, tr("ExtendedDynamicState 1")),
PAIR(ExtendedDynamicState, EDS2, tr("ExtendedDynamicState 2")),
PAIR(ExtendedDynamicState, EDS3, tr("ExtendedDynamicState 3")),
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -19,7 +16,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(instructions.end(), op, args);
PrependNewInst(end(), op, args);
}
Block::iterator Block::PrependNewInst(iterator insertion_point, const Inst& base_inst) {
+1 -6
View File
@@ -5,7 +5,6 @@
# 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
@@ -38,10 +37,6 @@ 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
@@ -314,7 +309,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 gpu_logging)
target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder)
if (YUZU_USE_EXTERNAL_FFMPEG)
add_dependencies(video_core ffmpeg-build)
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+104 -66
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
@@ -29,104 +29,124 @@ 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, HIGHER_PAGE_SIZE>;
using Manager = WordManager<DeviceTracker, WORDS_STACK_NEEDED>;
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->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->template 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->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->template 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->IsRegionModified(Type::CPU, offset, size);
});
return IteratePages<true>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template 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->IsRegionModified(Type::GPU, offset, size);
});
return IteratePages<false>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template 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->IsRegionModified(Type::Preflushable, offset, size);
});
return IteratePages<false>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template 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->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, 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);
});
}
/// 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->ChangeRegionState(Type::CPU, false, manager->cpu_addr + offset, 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);
});
}
/// 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->ChangeRegionState(Type::GPU, true, manager->cpu_addr + offset, size);
});
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::GPU, true>(
manager->GetCpuAddr() + 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->ChangeRegionState(Type::Preflushable, true, manager->cpu_addr + offset, size);
});
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::Preflushable, true>(
manager->GetCpuAddr() + 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->ChangeRegionState(Type::GPU, false, manager->cpu_addr + offset, size);
});
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::GPU, false>(
manager->GetCpuAddr() + 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->ChangeRegionState(Type::Preflushable, false, manager->cpu_addr + offset, size);
});
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::Preflushable, false>(
manager->GetCpuAddr() + 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->cpu_addr + offset;
manager->ChangeRegionState(Type::CachedCPU, true, cpu_address, size);
cached_pages.insert(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->GetCpuAddr() + offset;
manager->template ChangeRegionState<Type::CachedCPU, true>(cpu_address, size);
cached_pages.insert(static_cast<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 {
@@ -139,24 +159,35 @@ 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->ForEachModifiedRange(Type::CPU, true, manager->cpu_addr + offset, size, 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);
});
}
/// 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) {
manager->ForEachModifiedRange(Type::GPU, clear, manager->cpu_addr + offset, size, 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);
}
});
}
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->ForEachModifiedRange(Type::GPU, true, manager->cpu_addr + offset, size, 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);
});
}
private:
@@ -240,24 +271,31 @@ private:
}
Manager* GetNewManager(VAddr base_cpu_address) {
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]);
}
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();
}
Manager* new_manager = free_managers.front();
new_manager->cpu_addr = base_cpu_address;
free_managers.pop_front();
return new_manager;
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();
}
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;
};
+350 -142
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
@@ -31,26 +31,158 @@ enum class Type {
CachedCPU,
Untracked,
Preflushable,
Max
};
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);
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
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;
/// 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;
}
/// 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);
}
// 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;
}
~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);
@@ -60,7 +192,7 @@ struct WordManager {
}
static std::pair<size_t, size_t> GetWordPage(VAddr address) {
const size_t converted_address = size_t(address);
const size_t converted_address = static_cast<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);
@@ -69,28 +201,32 @@ struct WordManager {
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>;
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);
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;
}
} else {
func(word_index, mask);
}
}
}
@@ -110,32 +246,39 @@ struct WordManager {
}
}
/// @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);
/**
* 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>();
std::vector<std::pair<VAddr, u64>> ranges;
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
if (type == Type::CPU || type == Type::CachedCPU) {
CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges);
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
CollectChangedRanges<(!enable)>(index, untracked_words[index], mask, ranges);
}
if (enable) {
if constexpr (enable) {
state_words[index] |= mask;
if (type == Type::CPU || type == Type::CachedCPU)
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
untracked_words[index] |= mask;
if (type == Type::CPU)
}
if constexpr (type == Type::CPU) {
cached_words[index] &= ~mask;
}
} else {
if (type == Type::CPU)
cached_words[index] &= ~(state_words[index] & mask);
if constexpr (type == Type::CPU) {
const u64 word = state_words[index] & mask;
cached_words[index] &= ~word;
}
state_words[index] &= ~mask;
if (type == Type::CPU || type == Type::CachedCPU)
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
untracked_words[index] &= ~mask;
}
}
});
if (!ranges.empty()) {
@@ -143,20 +286,22 @@ struct WordManager {
}
}
/// @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;
/**
* 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;
bool pending = false;
size_t pending_offset{};
size_t pending_pointer{};
@@ -166,32 +311,39 @@ struct WordManager {
};
std::vector<std::pair<VAddr, u64>> ranges;
IterateWords(offset, size, [&](size_t index, u64 mask) {
if (type == Type::GPU)
if constexpr (type == Type::GPU) {
mask &= ~untracked_words[index];
}
const u64 word = state_words[index] & mask;
if (clear) {
if (type == Type::CPU || type == Type::CachedCPU) {
CollectChangedRanges(true, index, untracked_words[index], mask, ranges);
if constexpr (clear) {
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
CollectChangedRanges<true>(index, untracked_words[index], mask, ranges);
}
state_words[index] &= ~mask;
if (type == Type::CPU || type == Type::CachedCPU)
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
untracked_words[index] &= ~mask;
if (type == Type::CPU)
}
if constexpr (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 = [&]() {
pending_offset = base_offset + pages_offset;
pending_pointer = base_offset + pages_offset + pages_size;
};
if (!pending) {
pending_offset = base_offset + pages_offset;
pending_pointer = base_offset + pages_offset + pages_size;
reset();
pending = true;
} else if (pending_pointer == base_offset + pages_offset) {
pending_pointer += pages_size;
} 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;
return;
}
if (pending_pointer == base_offset + pages_offset) {
pending_pointer += pages_size;
return;
}
release();
reset();
});
});
if (pending) {
@@ -202,55 +354,90 @@ struct WordManager {
}
}
/// @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);
/**
* 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>();
bool result = false;
IterateWords(offset, size, [&](size_t index, u64 mask) {
if (type == Type::GPU)
if constexpr (type == Type::GPU) {
mask &= ~untracked_words[index];
return (state_words[index] & mask) != 0 ? (result = true) : false;
}
const u64 word = state_words[index] & mask;
if (word != 0) {
result = true;
return true;
}
return false;
});
return result;
}
/// @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;
/**
* 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;
IterateWords(offset, size, [&](size_t index, u64 mask) {
if (type == Type::GPU)
if constexpr (type == Type::GPU) {
mask &= ~untracked_words[index];
const u64 word = state_words[index] & mask;
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 word = state_words[index] & mask;
if (word == 0) {
return;
}
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;
});
return begin < end ? std::make_pair<u64, u64>(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE)
: std::make_pair<u64, u64>(0, 0);
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();
}
void FlushCachedWrites() noexcept {
auto const cached_words = Span(Type::CachedCPU);
auto const untracked_words = Span(Type::Untracked);
auto const cpu_words = Span(Type::CPU);
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>();
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;
@@ -260,13 +447,45 @@ struct WordManager {
}
}
/// @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 {
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 {
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) {
@@ -275,9 +494,9 @@ struct WordManager {
}
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());
@@ -298,30 +517,19 @@ struct WordManager {
ranges.clear();
}
/// @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 {
template <bool add_to_tracker>
void NotifyRasterizer(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);
});
}
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;
VAddr cpu_addr = 0;
DeviceTracker* tracker = nullptr;
Words<stack_words> words;
};
} // namespace VideoCommon
+12 -17
View File
@@ -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,7 +7,6 @@
#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"
@@ -21,7 +20,6 @@ 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;
@@ -60,21 +58,19 @@ 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());
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));
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");
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;
// UNIMPLEMENTED_IF_MSG(args.du_dx == null_derivative && args.dv_dy == null_derivative, "du/dx & dv/dy null derivative");
auto srcX = args.src_x0;
auto srcY = args.src_y0;
@@ -86,8 +82,7 @@ void Fermi2D::Blit() {
Config config{
.operation = regs.operation,
.filter = args.sample_mode.filter,
.must_accelerate =
args.du_dx != null_derivative || args.dv_dy != null_derivative || delegate_to_gpu,
.must_accelerate = true,
.dst_x0 = args.dst_x0,
.dst_y0 = args.dst_y0,
.dst_x1 = args.dst_x0 + args.dst_width,
@@ -112,9 +107,9 @@ void Fermi2D::Blit() {
}
memory_manager.FlushCaching();
if (!rasterizer->AccelerateSurfaceCopy(src, regs.dst, config)) {
sw_blitter->Blit(src, regs.dst, config);
}
bool was_accelerated = rasterizer->AccelerateSurfaceCopy(src, regs.dst, config);
ASSERT(was_accelerated && "Backend must always accelerate copies/blits");
}
} // namespace Tegra::Engines
+3 -1
View File
@@ -1,3 +1,6 @@
// 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
@@ -304,7 +307,6 @@ 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
@@ -1,240 +0,0 @@
// 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
@@ -1,27 +0,0 @@
// 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
@@ -1,36 +0,0 @@
// 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
-25
View File
@@ -1,25 +0,0 @@
# 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()
@@ -1,52 +0,0 @@
// 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
@@ -1,32 +0,0 @@
// 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
-734
View File
@@ -1,734 +0,0 @@
// 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
-199
View File
@@ -1,199 +0,0 @@
// 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
@@ -1,43 +0,0 @@
// 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
@@ -1,23 +0,0 @@
// 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
@@ -1,26 +0,0 @@
// 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
@@ -1,22 +0,0 @@
// 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 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+1 -4
View File
@@ -1,6 +1,3 @@
// SDPX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -92,7 +89,7 @@ struct CommandDataContainer {
/// Struct used to synchronize the GPU thread
struct SynchState final {
using CommandQueue = Common::SPSCQueue<CommandDataContainer>;
using CommandQueue = Common::MPSCQueue<CommandDataContainer>;
std::mutex write_lock;
CommandQueue queue;
u64 last_fence{};
+10 -1
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr)
@@ -19,6 +19,7 @@ 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
@@ -76,6 +77,14 @@ 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)
@@ -0,0 +1,46 @@
// 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);
}
@@ -0,0 +1,28 @@
#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);
}
@@ -0,0 +1,29 @@
#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);
}
@@ -0,0 +1,29 @@
#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));
}
@@ -0,0 +1,31 @@
#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);
}

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