Compare commits

...

75 Commits

Author SHA1 Message Date
CamilleLaVey fb8e0f25ab [vulkan] Re-ordering order for BindVertexBuffers2EXT command (again) 2026-02-02 22:35:07 -04:00
CamilleLaVey 2d1fe28198 [vulkan] small update for viewports 2026-02-02 22:18:14 -04:00
CamilleLaVey 9139dec954 Update license headers 2026-02-02 20:23:52 -04:00
CamilleLaVey 705090c8da [vulkan] Another take for BindingStride 2026-02-02 20:18:42 -04:00
CamilleLaVey 06a179fb2a Revert "[video_core] Replacing recursive mutex with simple mutex lock" 2026-02-02 20:02:40 -04:00
CamilleLaVey 3c260a2f4d [vk, qcom] Removing FP16 from QCOM drivers 2026-02-02 20:01:44 -04:00
CamilleLaVey 4657adc052 [video_core] Replacing recursive mutex with simple mutex lock 2026-02-02 19:27:11 -04:00
CamilleLaVey 1e59afb0aa crash fix 2026-02-02 19:20:55 -04:00
CamilleLaVey 2153cf397a Miscellaneous changes 2026-02-02 17:28:29 -04:00
CamilleLaVey 705ca3aef4 Revert "[vulkan, qcom] Removing faulty DepthTest from QCOM drivers" 2026-02-02 16:11:06 -04:00
CamilleLaVey 284018cd36 [vulkan] BindVertexBuffers2EXT set properly within pipeline chain 2026-02-02 11:52:51 -04:00
CamilleLaVey ebacfbf4ed [vulkan, qcom] Removing faulty DepthTest from QCOM drivers 2026-02-02 11:34:26 -04:00
CamilleLaVey 655377f585 [vulkan] Adding EDS3 resets per EDS level due to wrong activation from ProvokingVertexLast vs ProvokingVertex feature 2026-02-02 03:28:03 -04:00
CamilleLaVey 338fb71a6f updating headers 2026-02-02 03:21:24 -04:00
CamilleLaVey fe5c75e3ee [vulkan, android] removing remaining arrays from static mode 2026-02-02 03:10:42 -04:00
CamilleLaVey fdcf6149b4 another small change 2026-02-02 03:02:55 -04:00
CamilleLaVey 4b0bb3c436 small change 2026-02-02 02:17:24 -04:00
CamilleLaVey 7badde58b2 Yamero GIDOLY!!!!!! 2026-02-02 00:12:25 -04:00
CamilleLaVey 13903d6e8c The Mystic Gidoly 2026-02-02 00:03:02 -04:00
CamilleLaVey 202193708a [vulkan] Exchanging ProvokingVertexLast dynamic state mode. 2026-02-01 23:49:07 -04:00
CamilleLaVey 7802eaa22b fix build 2026-02-01 23:41:50 -04:00
CamilleLaVey c549531418 quick change 2026-02-01 23:29:59 -04:00
CamilleLaVey dbcbce57b0 [vulkan] Adjusting provoking vertex last. 2026-02-01 23:21:24 -04:00
CamilleLaVey 0d518e7303 [vulkan, mobile] Reworking per-mobile gpu configuration block 2026-02-01 23:12:21 -04:00
CamilleLaVey 26c4656473 fix build 2026-02-01 22:19:03 -04:00
CamilleLaVey ad71860407 [vulkan] Removing Static mode for pipeline compilations 2026-02-01 21:47:21 -04:00
CamilleLaVey d1251e2987 Fix build 2026-02-01 14:20:52 -04:00
CamilleLaVey 0cdd5d3362 [vulkan] Quick adjustments due to recent regressions 2026-02-01 14:02:07 -04:00
CamilleLaVey dd6c303ae1 meow 2026-02-01 03:05:44 -04:00
CamilleLaVey c327fd7265 fix build 2.0 2026-02-01 02:56:19 -04:00
CamilleLaVey 474c770316 [vulkan] Removing unused code 2026-02-01 02:47:08 -04:00
CamilleLaVey 762b9e0a11 fix build 2026-02-01 02:38:43 -04:00
CamilleLaVey 0d69555ac6 [vulkan] Adjusting Vk_EXT_custom_border_colors + removal duplicate code 2026-02-01 02:29:17 -04:00
CamilleLaVey 3a70dddf80 [vulkan] Adjusting DynamicState functionality + including Static Pipelines Mode 2026-02-01 02:07:47 -04:00
CamilleLaVey 55db1d862b [vulkan] Adjusting VK_EXT_provoking_vertex 2026-02-01 01:12:54 -04:00
CamilleLaVey 0bf5377323 [vulkan] Removing VK_EXT_descriptor_indexing 2026-02-01 00:56:27 -04:00
CamilleLaVey 4efdf24463 [vulkan] Removing VK_EXT_inline_uniform_block 2026-02-01 00:47:39 -04:00
CamilleLaVey dd13dbf9aa [vulkan] Removing VK_EXT_descriptor_buffer 2026-02-01 00:40:10 -04:00
CamilleLaVey 5100108f4e [vulkan] Removing VK_EXT_graphics_pipelines_library 2026-02-01 00:34:24 -04:00
CamilleLaVey 12be42d5ec [vulkan] Extending ASTC handling with specific feature on supported GPU's 2026-02-01 00:09:48 -04:00
CamilleLaVey 6e73a5e395 [vulkan] Adding VK_EXT_color_write_enable 2026-01-31 23:43:51 -04:00
CamilleLaVey a8718b7738 Revert "[vulkan] Initial VK_KHR_dynamic_rendering implementation" 2026-01-31 23:23:40 -04:00
CamilleLaVey ce65d51f9e Revert "[vulkan] Follow-up for the VK_KHR_dynamic_rendering implementation" 2026-01-31 23:23:14 -04:00
CamilleLaVey a4a139edf7 Revert "fix build" 2026-01-31 23:21:44 -04:00
CamilleLaVey c73232a299 Revert "[vulkan] Establishin pColorAttachments pass thorugh DynamicRendering commands" 2026-01-31 23:21:19 -04:00
CamilleLaVey 43af33830c Revert "[vulkan] Re-defining DynamicRendering scheduler/framebuffer and RenderAttachment with direct copy" 2026-01-31 23:20:19 -04:00
CamilleLaVey a176746184 [vulkan] Re-defining DynamicRendering scheduler/framebuffer and RenderAttachment with direct copy 2026-01-31 22:47:09 -04:00
CamilleLaVey c295c9c30c [vulkan] Establishin pColorAttachments pass thorugh DynamicRendering commands 2026-01-31 22:19:16 -04:00
CamilleLaVey 87d81ab554 fix build 2026-01-31 22:24:33 -03:00
CamilleLaVey 480104dc50 [vulkan] Follow-up for the VK_KHR_dynamic_rendering implementation 2026-01-31 22:24:31 -03:00
CamilleLaVey 8932211b70 Revert "turnip mhr base" 2026-01-31 22:18:15 -03:00
CamilleLaVey d69177fcba [vulkan] Initial VK_KHR_dynamic_rendering implementation 2026-01-31 22:18:15 -03:00
CamilleLaVey 13b37d2707 [vulkan, qcom] Removing the CUBE_BIT removal. 2026-01-31 22:18:15 -03:00
CamilleLaVey da77101b4d Revert "[vulkan, qcom] Re-ordering WMEL detection based for QCOM driver" 2026-01-31 22:18:15 -03:00
CamilleLaVey bb005df4a5 Revert "fix build" 2026-01-31 22:18:15 -03:00
PavelBARABANOV a49d142e69 turnip mhr base 2026-01-31 22:18:14 -03:00
CamilleLaVey 677d908edd fix build 2026-01-31 22:18:14 -03:00
CamilleLaVey d29cfd9f40 [vulkan, qcom] Re-ordering WMEL detection based for QCOM driver 2026-01-31 22:18:14 -03:00
CamilleLaVey a99bba00be [vulkan, qcom] Updating CUBE_BIT compatibility with QCOM drivers 2026-01-31 22:18:14 -03:00
CamilleLaVey 9fa43afd14 [vulkan, qcom] Increasing SPP for QCOM 2026-01-31 22:18:14 -03:00
CamilleLaVey 7fa788714f Fix build 1.0 2026-01-31 22:18:14 -03:00
CamilleLaVey dd1ab91da7 Fix android crashing 2026-01-31 22:18:13 -03:00
CamilleLaVey b64d5ca23f [vulkan, qcom] Re-ordering InlineUniformBlock +DescriptorBuffers and QCOM ban for older devices 2026-01-31 22:18:13 -03:00
CamilleLaVey 14537155a6 [vulkan, qcom] Removing TimelineSemaphore from some older models 2026-01-31 22:18:13 -03:00
CamilleLaVey 15760dad60 [vk] Added Descriptor Buffers 2026-01-31 22:18:13 -03:00
CamilleLaVey d271510263 [vk] Added Inline Uniform Block 2026-01-31 22:18:13 -03:00
CamilleLaVey 32fd70c363 [vk] Added graphics pipeline library 2026-01-31 22:18:13 -03:00
MrPurple666 6637810fe6 [android] gpu logs functions (#3389)
Since Android is a pain when it comes to checking GPU logs in more depth, this is a better way to see what's going on, especially for testers...

This should be expanded to Mali, Xclipse, and Tensor in the future. Since I don't own any of these devices, it's up to developers with similar capabilities to add support for this system.

~~The GPU log sharing button should also be added in the future... For now, they are available in the same location as the traditional logs.~~ Added on 572810e022

Co-authored-by: DraVee <dravee@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3389
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Co-authored-by: MrPurple666 <antoniosacramento666usa@gmail.com>
Co-committed-by: MrPurple666 <antoniosacramento666usa@gmail.com>
2026-02-01 02:02:23 +01:00
lizzie 8118557c17 [video_core/buffer_cache] Simplify word manager logic (#3221)
no significant performance gains expected
reduces memory (4096 word managers are active at any given time, this reduces total amount of memory used by a tiny margin)
also reduces code duplication due to templatery fuckery

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3221
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-01 01:55:47 +01:00
lizzie 1925726b96 [android, core] pin core threads to logical CPUs 0-3 (#3121)
this basically allows the threads to exist in these logical CPUs, undisturbed, and without trashing each other's cache
this could improve performance, very tricky thing to pull off correctly, but again, this is mostly an experiment
will mainly benefit: Linux, Android, FreeBSD, Windows (not ARM)
Additionally, this means no context trashing :)

Signed-off-by: lizzie <lizzie@eden-emu.dev>

Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3121
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-02-01 01:54:59 +01:00
lizzie acf7deea95 [vk] remove unused vk_texture_manager.cpp and unused shader conversions (#3432)
- Trivial dead code removal, also these shaders are useless

TODO: maybe in a future do YUV280 or whatever in shader for vic :)

Signed-off-by: lizzie <lizzie@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3432
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-01-31 20:33:35 +01:00
crueter 84fdbbaaa1 [frontend] Fix build issues with update checker (#3433)
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3433
2026-01-31 01:45:12 +01:00
crueter 638663b28e [cmake, frontend] Add nightly build modifier (#3431)
The `NIGHTLY_BUILD` option changes the app name to "Eden Nightly" and
changes the auto-update URL to use our new Nightly repository.

This needs added to Android, but I can't right now as I have to leave.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3431
2026-01-31 01:18:36 +01:00
lizzie df838a57fd [dynarmic] reduce CPU usage on Spooky Mansion by making blocks be page-sized and page-aligned; replace std::set<> with ankerl set in arm64 (#3253)
reduces CPU usage by about 10-20%, may be placebo
Signed-off-by: lizzie lizzie@eden-emu.dev

Co-authored-by: Caio Oliveira <caiooliveirafarias0@gmail.com>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3253
Reviewed-by: Maufeat <sahyno1996@gmail.com>
Reviewed-by: DraVee <dravee@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
2026-01-31 00:21:45 +01:00
MaranBr b2b73ecb62 [opengl] Fix SwapBuffers warning when window is not exposed (#3429)
This fixes an annoying warning when opening any game using OpenGL.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3429
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2026-01-30 23:06:22 +01:00
127 changed files with 2523 additions and 1717 deletions
+18 -2
View File
@@ -1,6 +1,6 @@
#!/bin/sh -e
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
NUM_JOBS=$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)
@@ -29,6 +29,7 @@ Options:
-b, --build-type <TYPE> Build type (variable: TYPE)
Valid values are: Release, RelWithDebInfo, Debug
Default: Debug
-n, --nightly Create a nightly build.
Extra arguments are passed to CMake (e.g. -DCMAKE_OPTION_NAME=VALUE)
Set the CCACHE variable to "true" to enable build caching.
@@ -61,6 +62,7 @@ while true; do
-r|--release) DEVEL=false ;;
-t|--target) target "$2"; shift ;;
-b|--build-type) type "$2"; shift ;;
-n|--nightly) NIGHTLY=true ;;
-h|--help) usage ;;
*) break ;;
esac
@@ -101,7 +103,20 @@ cd src/android
chmod +x ./gradlew
set -- "$@" -DUSE_CCACHE="${CCACHE}"
[ "$DEVEL" != "true" ] && set -- "$@" -DENABLE_UPDATE_CHECKER=ON
nightly() {
[ "$NIGHTLY" = "true" ]
}
if nightly || [ "$DEVEL" != "true" ]; then
set -- "$@" -DENABLE_UPDATE_CHECKER=ON
fi
if nightly; then
NIGHTLY=true
else
NIGHTLY=false
fi
echo "-- building..."
@@ -110,6 +125,7 @@ echo "-- building..."
-Dorg.gradle.parallel="${CCACHE}" \
-Dorg.gradle.workers.max="${NUM_JOBS}" \
-PYUZU_ANDROID_ARGS="$*" \
-Pnightly="$NIGHTLY" \
--info
if [ -n "${ANDROID_KEYSTORE_B64}" ]; then
+2
View File
@@ -227,6 +227,8 @@ option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android"
option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.g. Snapdragon 865) at the cost of performance" OFF)
option(NIGHTLY_BUILD "Use Nightly qualifiers in the update checker and build metadata" OFF)
cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF)
cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF)
+15 -8
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: 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,14 +28,21 @@ set(GIT_DESC ${BUILD_VERSION})
# Generate cpp with Git revision from template
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
set(REPO_NAME "Eden")
set(BUILD_ID ${GIT_REFSPEC})
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
set(CXX_COMPILER "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
# Auto-updater metadata! Must somewhat mirror GitHub API endpoint
set(BUILD_AUTO_UPDATE_WEBSITE "https://github.com")
set(BUILD_AUTO_UPDATE_API "http://api.github.com")
set(BUILD_AUTO_UPDATE_REPO "eden-emulator/Releases")
if (NIGHTLY_BUILD)
set(BUILD_AUTO_UPDATE_REPO "Eden-CI/Nightly")
set(REPO_NAME "Eden Nightly")
else()
set(BUILD_AUTO_UPDATE_REPO "eden-emulator/Releases")
set(REPO_NAME "Eden")
endif()
set(BUILD_ID ${GIT_REFSPEC})
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
set(CXX_COMPILER "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
configure_file(scm_rev.cpp.in scm_rev.cpp @ONLY)
+4
View File
@@ -20,6 +20,10 @@ if (YUZU_STATIC_BUILD)
add_compile_definitions(QT_STATICPLUGIN)
endif()
if (NIGHTLY_BUILD)
add_compile_definitions(NIGHTLY_BUILD)
endif()
# Set compilation flags
if (MSVC AND NOT CXX_CLANG)
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
+28 -2
View File
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// import android.annotation.SuppressLint
import com.android.build.gradle.api.ApplicationVariant
import kotlin.collections.setOf
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
import com.github.triplet.gradle.androidpublisher.ReleaseStatus
@@ -37,6 +38,9 @@ android {
compileSdkVersion = "android-36"
ndkVersion = "28.2.13676358"
val isNightly =
providers.gradleProperty("nightly").orNull?.toBooleanStrictOrNull() ?: false
buildFeatures {
viewBinding = true
}
@@ -71,6 +75,7 @@ android {
val extraCMakeArgs =
(project.findProperty("YUZU_ANDROID_ARGS") as String?)?.split("\\s+".toRegex())
?: emptyList()
arguments.addAll(
listOf(
"-DENABLE_QT=0", // Don't use QT
@@ -89,6 +94,13 @@ android {
)
)
if (isNightly) {
arguments.addAll(listOf(
"-DENABLE_UPDATE_CHECKER=ON",
"-DNIGHTLY_BUILD=ON",
))
}
abiFilters("arm64-v8a")
}
}
@@ -125,7 +137,12 @@ android {
signingConfigs.getByName("default")
}
manifestPlaceholders += mapOf("appNameSuffix" to "")
if (isNightly) {
applicationIdSuffix = ".nightly"
manifestPlaceholders += mapOf("appNameSuffix" to " Nightly")
} else {
manifestPlaceholders += mapOf("appNameSuffix" to "")
}
isMinifyEnabled = true
isDebuggable = false
@@ -239,6 +256,15 @@ android {
path = file("${edenDir}/CMakeLists.txt")
}
}
productFlavors.all {
val currentName = manifestPlaceholders["appNameBase"] as? String ?: "Eden"
val suffix = if (isNightly) " Nightly" else ""
// apply nightly suffix I/A
resValue("string", "app_name_suffixed", "$currentName$suffix")
resValue("string", "app_name", "Eden$suffix")
}
}
idea {
@@ -258,7 +284,7 @@ tasks.register<Delete>("ktlintReset", fun Delete.() {
val showFormatHelp = {
logger.lifecycle(
"If this check fails, please try running \"gradlew ktlintFormat\" for automatic " +
"codestyle fixes"
"codestyle fixes"
)
}
tasks.getByPath("ktlintKotlinScriptCheck").doFirst { showFormatHelp.invoke() }
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -218,7 +218,7 @@ object NativeLibrary {
/**
* Checks for available updates.
*/
external fun checkForUpdate(): String?
external fun checkForUpdate(): Array<String>?
/**
* Return the URL to the release page
@@ -228,13 +228,18 @@ object NativeLibrary {
/**
* Return the URL to download the APK for the given version
*/
external fun getUpdateApkUrl(version: String, packageId: String): String
external fun getUpdateApkUrl(tag: String, artifact: String, packageId: String): String
/**
* Returns whether the update checker is enabled through CMAKE options.
*/
external fun isUpdateCheckerEnabled(): Boolean
/**
* Returns whether or not this is a nightly build.
*/
external fun isNightlyBuild(): Boolean
/**
* Returns the build version generated by CMake (BUILD_VERSION).
*/
@@ -29,8 +29,6 @@ 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"),
@@ -72,7 +70,14 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
USE_LRU_CACHE("use_lru_cache"),
DONT_SHOW_DRIVER_SHADER_WARNING("dont_show_driver_shader_warning"),
ENABLE_OVERLAY("enable_overlay");
ENABLE_OVERLAY("enable_overlay"),
// GPU Logging
GPU_LOGGING_ENABLED("gpu_logging_enabled"),
GPU_LOG_VULKAN_CALLS("gpu_log_vulkan_calls"),
GPU_LOG_SHADER_DUMPS("gpu_log_shader_dumps"),
GPU_LOG_MEMORY_TRACKING("gpu_log_memory_tracking"),
GPU_LOG_DRIVER_DEBUG("gpu_log_driver_debug");
// external fun isFrameSkippingEnabled(): Boolean
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
@@ -9,7 +9,8 @@ package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ByteSetting(override val key: String) : AbstractByteSetting {
AUDIO_VOLUME("volume"),;
AUDIO_VOLUME("volume"),
GPU_LOG_LEVEL("gpu_log_level");
override fun getByte(needsGlobal: Boolean): Byte = NativeConfig.getByte(key, needsGlobal)
@@ -67,7 +67,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting {
MY_PAGE_APPLET("my_page_applet_mode"),
INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide"),
OVERLAY_GRID_SIZE("overlay_grid_size"),
DEBUG_KNOBS("debug_knobs")
DEBUG_KNOBS("debug_knobs"),
GPU_LOG_RING_BUFFER_SIZE("gpu_log_ring_buffer_size")
;
override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal)
@@ -143,25 +143,12 @@ 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,
@@ -836,6 +823,62 @@ abstract class SettingsItem(
)
)
// GPU Logging settings
put(
SwitchSetting(
BooleanSetting.GPU_LOGGING_ENABLED,
titleId = R.string.gpu_logging_enabled,
descriptionId = R.string.gpu_logging_enabled_description
)
)
put(
SingleChoiceSetting(
ByteSetting.GPU_LOG_LEVEL,
titleId = R.string.gpu_log_level,
descriptionId = R.string.gpu_log_level_description,
choicesId = R.array.gpuLogLevelEntries,
valuesId = R.array.gpuLogLevelValues
)
)
put(
SwitchSetting(
BooleanSetting.GPU_LOG_VULKAN_CALLS,
titleId = R.string.gpu_log_vulkan_calls,
descriptionId = R.string.gpu_log_vulkan_calls_description
)
)
put(
SwitchSetting(
BooleanSetting.GPU_LOG_SHADER_DUMPS,
titleId = R.string.gpu_log_shader_dumps,
descriptionId = R.string.gpu_log_shader_dumps_description
)
)
put(
SwitchSetting(
BooleanSetting.GPU_LOG_MEMORY_TRACKING,
titleId = R.string.gpu_log_memory_tracking,
descriptionId = R.string.gpu_log_memory_tracking_description
)
)
put(
SwitchSetting(
BooleanSetting.GPU_LOG_DRIVER_DEBUG,
titleId = R.string.gpu_log_driver_debug,
descriptionId = R.string.gpu_log_driver_debug_description
)
)
put(
SpinBoxSetting(
IntSetting.GPU_LOG_RING_BUFFER_SIZE,
titleId = R.string.gpu_log_ring_buffer_size,
descriptionId = R.string.gpu_log_ring_buffer_size_description,
valueHint = R.string.gpu_log_ring_buffer_size_hint,
min = 64,
max = 4096
)
)
val fastmem = object : AbstractBooleanSetting {
override fun getBoolean(needsGlobal: Boolean): Boolean =
BooleanSetting.FASTMEM.getBoolean() &&
@@ -290,8 +290,6 @@ 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))
@@ -1220,6 +1218,15 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.general))
add(IntSetting.DEBUG_KNOBS.key)
add(HeaderSetting(R.string.gpu_logging_header))
add(BooleanSetting.GPU_LOGGING_ENABLED.key)
add(ByteSetting.GPU_LOG_LEVEL.key)
add(BooleanSetting.GPU_LOG_VULKAN_CALLS.key)
add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key)
add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key)
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key)
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key)
}
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.fragments
@@ -222,6 +222,14 @@ class HomeSettingsFragment : Fragment() {
{ shareLog() }
)
)
add(
HomeSetting(
R.string.share_gpu_log,
R.string.share_gpu_log_description,
R.drawable.ic_log,
{ shareGpuLog() }
)
)
add(
HomeSetting(
R.string.open_user_folder,
@@ -408,6 +416,40 @@ class HomeSettingsFragment : Fragment() {
}
}
private fun shareGpuLog() {
val currentLog = DocumentFile.fromSingleUri(
mainActivity,
DocumentsContract.buildDocumentUri(
DocumentProvider.AUTHORITY,
"${DocumentProvider.ROOT_ID}/log/eden_gpu.log"
)
)!!
val oldLog = DocumentFile.fromSingleUri(
mainActivity,
DocumentsContract.buildDocumentUri(
DocumentProvider.AUTHORITY,
"${DocumentProvider.ROOT_ID}/log/eden_gpu.log.old.txt"
)
)!!
val intent = Intent(Intent.ACTION_SEND)
.setDataAndType(currentLog.uri, FileUtil.TEXT_PLAIN)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
if (!Log.gameLaunched && oldLog.exists()) {
intent.putExtra(Intent.EXTRA_STREAM, oldLog.uri)
startActivity(Intent.createChooser(intent, getText(R.string.share_gpu_log)))
} else if (currentLog.exists()) {
intent.putExtra(Intent.EXTRA_STREAM, currentLog.uri)
startActivity(Intent.createChooser(intent, getText(R.string.share_gpu_log)))
} else {
Toast.makeText(
requireContext(),
getText(R.string.share_gpu_log_missing),
Toast.LENGTH_SHORT
).show()
}
}
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.ui.main
@@ -183,18 +183,26 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val latestVersion = NativeLibrary.checkForUpdate()
if (latestVersion != null) {
runOnUiThread {
showUpdateDialog(latestVersion)
val tag: String = latestVersion[0]
val name: String = latestVersion[1]
showUpdateDialog(tag, name)
}
}
}.start()
}
private fun showUpdateDialog(version: String) {
private fun showUpdateDialog(tag: String, name: String) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.update_available)
.setMessage(getString(R.string.update_available_description, version))
.setMessage(getString(R.string.update_available_description, name))
.setPositiveButton(android.R.string.ok) { _, _ ->
downloadAndInstallUpdate(version)
var artifact = tag
// Nightly builds have a slightly different format
if (NativeLibrary.isNightlyBuild()) {
val splitTag = tag.split('.')
artifact = splitTag.subList(1, splitTag.size - 1).joinToString(".")
}
downloadAndInstallUpdate(tag, artifact)
}
.setNeutralButton(R.string.cancel) { dialog, _ ->
dialog.dismiss()
@@ -207,11 +215,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
.show()
}
private fun downloadAndInstallUpdate(version: String) {
private fun downloadAndInstallUpdate(version: String, artifact: String) {
CoroutineScope(Dispatchers.IO).launch {
val packageId = applicationContext.packageName
val apkUrl = NativeLibrary.getUpdateApkUrl(version, packageId)
val apkFile = File(cacheDir, "update-$version.apk")
val apkUrl = NativeLibrary.getUpdateApkUrl(version, artifact, packageId)
val apkFile = File(cacheDir, "update-$artifact.apk")
withContext(Dispatchers.Main) {
showDownloadProgressDialog()
+40 -10
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 2023 yuzu Emulator Project
@@ -1595,7 +1595,6 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_updatePowerState(
g_has_battery.store(hasBattery, std::memory_order_relaxed);
}
// return #ifdef ENABLE_UPDATE_CHECKER
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isUpdateCheckerEnabled(
JNIEnv* env,
jobject obj) {
@@ -1606,22 +1605,47 @@ JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isUpdateChecker
#endif
}
JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isNightlyBuild(
JNIEnv* env,
jobject obj) {
#ifdef NIGHTLY_BUILD
return JNI_TRUE;
#else
return JNI_FALSE;
#endif
}
#ifdef ENABLE_UPDATE_CHECKER
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
JNIEXPORT jobjectArray JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_checkForUpdate(
JNIEnv* env,
jobject obj) {
const bool is_prerelease = ((strstr(Common::g_build_version, "pre-alpha") != nullptr) ||
(strstr(Common::g_build_version, "alpha") != nullptr) ||
(strstr(Common::g_build_version, "beta") != nullptr) ||
(strstr(Common::g_build_version, "rc") != nullptr));
const std::optional<std::string> latest_release_tag =
const std::optional<UpdateChecker::Update> release =
UpdateChecker::GetLatestRelease(is_prerelease);
if (latest_release_tag && latest_release_tag.value() != Common::g_build_version) {
return env->NewStringUTF(latest_release_tag.value().c_str());
if (!release || release->tag == Common::g_build_version) {
return nullptr;
}
return nullptr;
const std::string tag = release->tag;
const std::string name = release->name;
jobjectArray result = env->NewObjectArray(2, env->FindClass("java/lang/String"), nullptr);
const jstring jtag = env->NewStringUTF(tag.c_str());
const jstring jname = env->NewStringUTF(name.c_str());
env->SetObjectArrayElement(result, 0, jtag);
env->SetObjectArrayElement(result, 1, jname);
env->DeleteLocalRef(jtag);
env->DeleteLocalRef(jname);
return result;
}
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
@@ -1640,9 +1664,11 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateUrl(
JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
JNIEnv* env,
jobject obj,
jstring version,
jstring tag,
jstring artifact,
jstring packageId) {
const char* version_str = env->GetStringUTFChars(version, nullptr);
const char* version_str = env->GetStringUTFChars(tag, nullptr);
const char* artifact_str = env->GetStringUTFChars(artifact, nullptr);
const char* package_id_str = env->GetStringUTFChars(packageId, nullptr);
std::string variant;
@@ -1653,7 +1679,11 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
} else if (package_id.find("com.miHoYo.Yuanshen") != std::string::npos) {
variant = "optimized";
} else {
#ifdef ARCHITECTURE_arm64
variant = "standard";
#else
variant = "chromeos";
#endif
}
const std::string apk_filename = fmt::format("Eden-Android-{}-{}.apk", version_str, variant);
@@ -1663,7 +1693,7 @@ JNIEXPORT jstring JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_getUpdateApkUrl(
version_str,
apk_filename);
env->ReleaseStringUTFChars(version, version_str);
env->ReleaseStringUTFChars(tag, version_str);
env->ReleaseStringUTFChars(packageId, package_id_str);
return env->NewStringUTF(url.c_str());
}
@@ -511,10 +511,7 @@
<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,10 +368,7 @@
<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,10 +493,7 @@
<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,10 +491,7 @@ 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,10 +425,7 @@
<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,10 +73,7 @@
<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,10 +441,7 @@
<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,10 +399,7 @@
<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,10 +387,8 @@
<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,10 +419,7 @@
<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,10 +426,7 @@
<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,10 +385,7 @@
<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,10 +385,7 @@
<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,10 +368,7 @@
<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,10 +493,7 @@
<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,10 +476,7 @@
<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,10 +391,7 @@
<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,10 +503,7 @@
<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,10 +390,7 @@
<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,10 +507,7 @@
<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,10 +366,7 @@
<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,10 +501,7 @@
<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,10 +472,7 @@
<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>
+18 -1
View File
@@ -611,7 +611,7 @@
</integer-array>
<string-array name="dynaStateEntries">
<item>@string/disabled</item>
<item>Core</item>
<item>ExtendedDynamicState 1</item>
<item>ExtendedDynamicState 2</item>
<item>ExtendedDynamicState 3</item>
@@ -631,4 +631,21 @@
<item>@string/error_keys_invalid_filename</item>
<item>@string/error_keys_failed_init</item>
</string-array>
<!-- GPU Logging Arrays -->
<string-array name="gpuLogLevelEntries">
<item>Off</item>
<item>Errors Only</item>
<item>Standard</item>
<item>Verbose</item>
<item>All</item>
</string-array>
<integer-array name="gpuLogLevelValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
</resources>
@@ -325,6 +325,9 @@
<string name="share_log">Share debug logs</string>
<string name="share_log_description">Share Eden\'s log file to debug issues</string>
<string name="share_log_missing">No log file found</string>
<string name="share_gpu_log">Share GPU logs</string>
<string name="share_gpu_log_description">Share Eden\'s GPU log file to debug graphics issues</string>
<string name="share_gpu_log_missing">No GPU log file found</string>
<string name="install_game_content">Install game content</string>
<string name="install_game_content_description">Install game updates or DLC</string>
<string name="installing_game_content">Installing content…</string>
@@ -517,10 +520,6 @@
<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>
@@ -552,6 +551,26 @@
<string name="flush_by_line">Flush debug logs by line</string>
<string name="flush_by_line_description">Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing.</string>
<!-- GPU Logging strings -->
<string name="gpu_logging">GPU Logging</string>
<string name="gpu_logging_header">GPU Logging</string>
<string name="gpu_logging_enabled">Enable GPU Logging</string>
<string name="gpu_logging_enabled_description">Log GPU operations to eden_gpu.log for debugging Adreno drivers</string>
<string name="gpu_log_level">Log Level</string>
<string name="gpu_log_level_description">Detail level for GPU logs (higher = more detail, more overhead)</string>
<string name="gpu_logging_features">Logging Features</string>
<string name="gpu_log_vulkan_calls">Log Vulkan API Calls</string>
<string name="gpu_log_vulkan_calls_description">Track all Vulkan API calls in ring buffer</string>
<string name="gpu_log_shader_dumps">Dump Shaders</string>
<string name="gpu_log_shader_dumps_description">Save compiled shader SPIR-V to files</string>
<string name="gpu_log_memory_tracking">Track GPU Memory</string>
<string name="gpu_log_memory_tracking_description">Monitor GPU memory allocations and deallocations</string>
<string name="gpu_log_driver_debug">Driver Debug Info</string>
<string name="gpu_log_driver_debug_description">Capture driver-specific debug information (Turnip breadcrumbs, etc.)</string>
<string name="gpu_log_ring_buffer_size">Ring Buffer Size</string>
<string name="gpu_log_ring_buffer_size_description">Number of recent Vulkan calls to track (default: 512)</string>
<string name="gpu_log_ring_buffer_size_hint">64 to 4096 entries</string>
<string name="general">General</string>
<!-- Audio settings strings -->
+1
View File
@@ -49,6 +49,7 @@ SWITCHABLE(CpuBackend, true);
SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(GpuLogLevel, true);
SWITCHABLE(Language, true);
SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false);
+14 -4
View File
@@ -535,9 +535,9 @@ struct Values {
#elif defined (__FreeBSD__)
ExtendedDynamicState::EDS3,
#elif defined (ANDROID)
ExtendedDynamicState::Disabled,
ExtendedDynamicState::Core,
#elif defined (__APPLE__)
ExtendedDynamicState::Disabled,
ExtendedDynamicState::Core,
#else
ExtendedDynamicState::EDS2,
#endif
@@ -559,8 +559,6 @@ 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",
@@ -738,6 +736,18 @@ struct Values {
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Debugging};
// GPU Logging
Setting<bool> gpu_logging_enabled{linkage, true, "gpu_logging_enabled", Category::Debugging};
SwitchableSetting<GpuLogLevel> gpu_log_level{linkage, GpuLogLevel::Standard, "gpu_log_level",
Category::Debugging};
Setting<bool> gpu_log_vulkan_calls{linkage, true, "gpu_log_vulkan_calls", Category::Debugging};
Setting<bool> gpu_log_shader_dumps{linkage, false, "gpu_log_shader_dumps", Category::Debugging};
Setting<bool> gpu_log_memory_tracking{linkage, true, "gpu_log_memory_tracking",
Category::Debugging};
Setting<bool> gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging};
Setting<s32> gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size",
Category::Debugging};
SwitchableSetting<u16, true> debug_knobs{linkage,
0,
0,
+3 -2
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 2024 Torzu Emulator Project
@@ -153,7 +153,8 @@ ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge)
ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
ENUM(TemperatureUnits, Celsius, Fahrenheit)
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
ENUM(ExtendedDynamicState, Core, EDS1, EDS2, EDS3);
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
template <typename Type>
inline std::string_view CanonicalizeEnum(Type id) {
+38 -17
View File
@@ -1,13 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include <thread>
#include "common/error.h"
#include "common/logging/log.h"
#include "common/assert.h"
#include "common/thread.h"
#ifdef __APPLE__
#include <mach/mach.h>
@@ -18,6 +20,8 @@
#include "common/string_util.h"
#else
#if defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
#include <sys/cpuset.h>
#include <sys/_cpuset.h>
#include <pthread_np.h>
#endif
#include <pthread.h>
@@ -28,7 +32,7 @@
#endif
#ifdef __FreeBSD__
#define cpu_set_t cpuset_t
# define cpu_set_t cpuset_t
#endif
namespace Common {
@@ -77,22 +81,14 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
#endif
}
void SetCurrentThreadName(const char* name) {
#ifdef _MSC_VER
// Sets the debugger-visible name of the current thread.
void SetCurrentThreadName(const char* name) {
static auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription");
if (pf)
// Sets the debugger-visible name of the current thread.
if (auto pf = (decltype(&SetThreadDescription))(void*)GetProcAddress(GetModuleHandle(TEXT("KernelBase.dll")), "SetThreadDescription"); pf)
pf(GetCurrentThread(), UTF8ToUTF16W(name).data()); // Windows 10+
}
#else // !MSVC_VER, so must be POSIX threads
// MinGW with the POSIX threading model does not support pthread_setname_np
void SetCurrentThreadName(const char* name) {
// See for reference
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
#ifdef __APPLE__
else
; // No-op
#elif defined(__APPLE__)
pthread_setname_np(name);
#elif defined(__HAIKU__)
rename_thread(find_thread(NULL), name);
@@ -112,13 +108,38 @@ void SetCurrentThreadName(const char* name) {
pthread_setname_np(pthread_self(), buf);
}
#elif defined(_WIN32)
// mingw stub
// MinGW with the POSIX threading model does not support pthread_setname_np
// See for reference
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
(void)name;
#else
pthread_setname_np(pthread_self(), name);
#endif
}
void PinCurrentThreadToPerformanceCore(size_t core_id) {
ASSERT(core_id < 4);
// If we set a flag for a CPU that doesn't exist, the thread may not be allowed to
// run in ANY processor!
auto const total_cores = std::thread::hardware_concurrency();
if (core_id < total_cores) {
#if defined(__ANDROID__)
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(core_id, &set);
sched_setaffinity(pthread_self(), sizeof(set), &set);
#elif defined(__linux__) || defined(__FreeBSD__)
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(core_id, &set);
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
#elif defined(_WIN32)
DWORD set = 1UL << core_id;
SetThreadAffinityMask(GetCurrentThread(), set);
#else
// No pin functionality implemented
#endif
}
}
} // namespace Common
+2 -2
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: 2013 Dolphin Emulator Project
@@ -106,7 +106,7 @@ enum class ThreadPriority : u32 {
};
void SetCurrentThreadPriority(ThreadPriority new_priority);
void SetCurrentThreadName(const char* name);
void PinCurrentThreadToPerformanceCore(size_t core_id);
} // namespace Common
+11 -12
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,6 +7,7 @@
#include "common/fiber.h"
#include "common/scope_exit.h"
#include "common/thread.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
@@ -25,11 +26,8 @@ CpuManager::~CpuManager() = default;
void CpuManager::Initialize() {
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
for (std::size_t core = 0; core < num_cores; core++) {
core_data[core].host_thread =
std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
}
for (std::size_t core = 0; core < num_cores; core++)
core_data[core].host_thread = std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
}
void CpuManager::Shutdown() {
@@ -188,14 +186,15 @@ void CpuManager::ShutdownThread() {
void CpuManager::RunThread(std::stop_token token, std::size_t core) {
/// Initialization
system.RegisterCoreThread(core);
std::string name;
if (is_multicore) {
name = "CPUCore_" + std::to_string(core);
} else {
name = "CPUThread";
}
std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"};
Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical);
#ifdef __ANDROID__
// Aimed specifically for Snapdragon 8 Elite devices
// This kills performance on desktop, but boosts perf for UMA devices
// like the S8E. Mediatek and Mali likely won't suffer.
Common::PinCurrentThreadToPerformanceCore(core);
#endif
auto& data = core_data[core];
data.host_context = Common::Fiber::ThreadToFiber();
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -218,7 +218,7 @@ EmittedBlockInfo EmitArm64(oaknut::CodeGenerator& code, IR::Block block, const E
code.l(pass);
}
for (auto iter = block.begin(); iter != block.end(); ++iter) {
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) {
IR::Inst* inst = &*iter;
switch (inst->GetOpcode()) {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -582,11 +582,9 @@ void EmitIR<IR::Opcode::A32BXWritePC>(oaknut::CodeGenerator& code, EmitContext&
template<>
void EmitIR<IR::Opcode::A32UpdateUpperLocationDescriptor>(oaknut::CodeGenerator& code, EmitContext& ctx, IR::Inst*) {
for (auto& inst : ctx.block) {
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC) {
for (auto& inst : ctx.block.instructions)
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC)
return;
}
}
EmitSetUpperLocationDescriptor(code, ctx, ctx.block.EndLocation(), ctx.block.Location());
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -193,7 +193,6 @@ void RegAlloc::PrepareForCall(std::optional<Argument::copyable_reference> arg0,
void RegAlloc::DefineAsExisting(IR::Inst* inst, Argument& arg) {
defined_insts.insert(inst);
ASSERT(!ValueLocation(inst));
if (arg.value.IsImmediate()) {
@@ -208,7 +207,6 @@ void RegAlloc::DefineAsExisting(IR::Inst* inst, Argument& arg) {
void RegAlloc::DefineAsRegister(IR::Inst* inst, oaknut::Reg reg) {
defined_insts.insert(inst);
ASSERT(!ValueLocation(inst));
auto& info = reg.is_vector() ? fprs[reg.index()] : gprs[reg.index()];
ASSERT(info.IsCompletelyEmpty());
@@ -375,7 +373,6 @@ int RegAlloc::RealizeReadImpl(const IR::Value& value) {
template<HostLoc::Kind kind>
int RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
defined_insts.insert(value);
ASSERT(!ValueLocation(value));
if constexpr (kind == HostLoc::Kind::Gpr) {
@@ -400,7 +397,6 @@ int RegAlloc::RealizeWriteImpl(const IR::Inst* value) {
template<HostLoc::Kind kind>
int RegAlloc::RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value) {
defined_insts.insert(write_value);
// TODO: Move elimination
const int write_loc = RealizeWriteImpl<kind>(write_value);
@@ -464,7 +460,6 @@ void RegAlloc::SpillFpr(int index) {
void RegAlloc::ReadWriteFlags(Argument& read, IR::Inst* write) {
defined_insts.insert(write);
const auto current_location = ValueLocation(read.value.GetInst());
ASSERT(current_location);
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -302,17 +302,12 @@ public:
private:
friend struct Argument;
template<typename>
friend struct RAReg;
template<typename> friend struct RAReg;
template<HostLoc::Kind kind>
int GenerateImmediate(const IR::Value& value);
template<HostLoc::Kind kind>
int RealizeReadImpl(const IR::Value& value);
template<HostLoc::Kind kind>
int RealizeWriteImpl(const IR::Inst* value);
template<HostLoc::Kind kind>
int RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value);
template<HostLoc::Kind kind> int GenerateImmediate(const IR::Value& value);
template<HostLoc::Kind kind> int RealizeReadImpl(const IR::Value& value);
template<HostLoc::Kind kind> int RealizeWriteImpl(const IR::Inst* value);
template<HostLoc::Kind kind> int RealizeReadWriteImpl(const IR::Value& read_value, const IR::Inst* write_value);
int AllocateRegister(const std::array<HostLocInfo, 32>& regs, const std::vector<int>& order) const;
void SpillGpr(int index);
@@ -337,7 +332,6 @@ private:
std::array<HostLocInfo, SpillCount> spills;
mutable std::mt19937 rand_gen;
ankerl::unordered_dense::set<const IR::Inst*> defined_insts;
};
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -15,26 +15,24 @@
namespace Dynarmic::Backend {
template<typename ProgramCounterType>
void BlockRangeInformation<ProgramCounterType>::AddRange(boost::icl::discrete_interval<ProgramCounterType> range, IR::LocationDescriptor location) {
block_ranges.add(std::make_pair(range, std::set<IR::LocationDescriptor>{location}));
template<typename P>
void BlockRangeInformation<P>::AddRange(boost::icl::discrete_interval<P> range, IR::LocationDescriptor location) {
block_ranges.add(std::make_pair(range, ankerl::unordered_dense::set<IR::LocationDescriptor>{location}));
}
template<typename ProgramCounterType>
void BlockRangeInformation<ProgramCounterType>::ClearCache() {
template<typename P>
void BlockRangeInformation<P>::ClearCache() {
block_ranges.clear();
}
template<typename ProgramCounterType>
ankerl::unordered_dense::set<IR::LocationDescriptor> BlockRangeInformation<ProgramCounterType>::InvalidateRanges(const boost::icl::interval_set<ProgramCounterType>& ranges) {
template<typename P>
ankerl::unordered_dense::set<IR::LocationDescriptor> BlockRangeInformation<P>::InvalidateRanges(const boost::icl::interval_set<P>& ranges) {
ankerl::unordered_dense::set<IR::LocationDescriptor> erase_locations;
for (auto invalidate_interval : ranges) {
auto pair = block_ranges.equal_range(invalidate_interval);
for (auto it = pair.first; it != pair.second; ++it) {
for (const auto& descriptor : it->second) {
for (auto it = pair.first; it != pair.second; ++it)
for (const auto& descriptor : it->second)
erase_locations.insert(descriptor);
}
}
}
// TODO: EFFICIENCY: Remove ranges that are to be erased.
return erase_locations;
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
@@ -15,15 +18,13 @@
namespace Dynarmic::Backend {
template<typename ProgramCounterType>
template<typename P>
class BlockRangeInformation {
public:
void AddRange(boost::icl::discrete_interval<ProgramCounterType> range, IR::LocationDescriptor location);
void AddRange(boost::icl::discrete_interval<P> range, IR::LocationDescriptor location);
void ClearCache();
ankerl::unordered_dense::set<IR::LocationDescriptor> InvalidateRanges(const boost::icl::interval_set<ProgramCounterType>& ranges);
private:
boost::icl::interval_map<ProgramCounterType, std::set<IR::LocationDescriptor>> block_ranges;
ankerl::unordered_dense::set<IR::LocationDescriptor> InvalidateRanges(const boost::icl::interval_set<P>& ranges);
boost::icl::interval_map<P, ankerl::unordered_dense::set<IR::LocationDescriptor>> block_ranges;
};
} // namespace Dynarmic::Backend
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -122,7 +122,7 @@ EmittedBlockInfo EmitRV64(biscuit::Assembler& as, IR::Block block, const EmitCon
ebi.entry_point = reinterpret_cast<CodePtr>(as.GetCursorPointer());
for (auto iter = block.begin(); iter != block.end(); ++iter) {
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) {
IR::Inst* inst = &*iter;
switch (inst->GetOpcode()) {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -123,7 +123,7 @@ A32EmitX64::BlockDescriptor A32EmitX64::Emit(IR::Block& block) {
EmitCondPrelude(ctx);
for (auto iter = block.begin(); iter != block.end(); ++iter) [[likely]] {
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) [[likely]] {
auto* inst = &*iter;
// Call the relevant Emit* member function.
switch (inst->GetOpcode()) {
@@ -727,11 +727,9 @@ void A32EmitX64::EmitA32BXWritePC(A32EmitContext& ctx, IR::Inst* inst) {
}
void A32EmitX64::EmitA32UpdateUpperLocationDescriptor(A32EmitContext& ctx, IR::Inst*) {
for (auto& inst : ctx.block) {
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC) {
for (auto& inst : ctx.block.instructions)
if (inst.GetOpcode() == IR::Opcode::A32BXWritePC)
return;
}
}
EmitSetUpperLocationDescriptor(ctx.EndLocation(), ctx.Location());
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -116,7 +116,7 @@ A64EmitX64::BlockDescriptor A64EmitX64::Emit(IR::Block& block) noexcept {
#undef A64OPC
};
for (auto& inst : block) {
for (auto& inst : block.instructions) {
auto const opcode = inst.GetOpcode();
// Call the relevant Emit* member function.
switch (opcode) {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -38,11 +38,6 @@ EmitContext::EmitContext(RegAlloc& reg_alloc, IR::Block& block)
EmitContext::~EmitContext() = default;
void EmitContext::EraseInstruction(IR::Inst* inst) {
block.Instructions().erase(inst);
inst->ClearArgs();
}
EmitX64::EmitX64(BlockOfCode& code)
: code(code) {
exception_handler.Register(code);
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -56,11 +56,7 @@ using HalfVectorArray = std::array<T, A64FullVectorWidth::value / mcl::bitsizeof
struct EmitContext {
EmitContext(RegAlloc& reg_alloc, IR::Block& block);
virtual ~EmitContext();
void EraseInstruction(IR::Inst* inst);
virtual FP::FPCR FPCR(bool fpcr_controlled = true) const = 0;
virtual bool HasOptimization(OptimizationFlag flag) const = 0;
RegAlloc& reg_alloc;
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -34,14 +34,7 @@ namespace Dynarmic::Backend::X64 {
using namespace Xbyak::util;
#define ICODE(NAME) \
[&code](auto... args) { \
if constexpr (esize == 32) { \
code.NAME##d(args...); \
} else { \
code.NAME##q(args...); \
} \
}
#define ICODE(NAME) [&](auto... args) { if (esize == 32) code.NAME##d(args...); else code.NAME##q(args...); }
template<typename Function>
static void EmitVectorOperation(BlockOfCode& code, EmitContext& ctx, IR::Inst* inst, Function fn) {
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
@@ -26,7 +26,7 @@ bool CondCanContinue(const ConditionalState cond_state, const A32::IREmitter& ir
return true;
// TODO: This is more conservative than necessary.
return std::all_of(ir.block.begin(), ir.block.end(), [](const IR::Inst& inst) {
return std::all_of(ir.block.instructions.begin(), ir.block.instructions.end(), [](const IR::Inst& inst) {
return !WritesToCPSR(inst.GetOpcode());
});
}
@@ -66,7 +66,7 @@ bool IsConditionPassed(TranslatorVisitor& v, IR::Cond cond) {
// non-AL cond
if (!v.ir.block.empty()) {
if (!v.ir.block.instructions.empty()) {
// We've already emitted instructions. Quit for now, we'll make a new block here later.
v.cond_state = ConditionalState::Break;
v.ir.SetTerm(IR::Term::LinkBlockFast{v.ir.current_location});
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
@@ -126,7 +129,7 @@ bool TranslatorVisitor::MRS(Imm<1> o0, Imm<3> op1, Imm<4> CRn, Imm<4> CRm, Imm<3
return true;
case SystemRegisterEncoding::CNTPCT_EL0:
// HACK: Ensure that this is the first instruction in the block it's emitted in, so the cycle count is most up-to-date.
if (!ir.block.empty() && !options.wall_clock_cntpct) {
if (!ir.block.instructions.empty() && !options.wall_clock_cntpct) {
ir.block.CycleCount()--;
ir.SetTerm(IR::Term::LinkBlock{*ir.current_location});
return false;
+22 -44
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
/* This file is part of the dynarmic project.
@@ -100,57 +100,39 @@ std::string DumpBlock(const IR::Block& block) noexcept {
std::string ret = fmt::format("Block: location={}-{}\n", block.Location(), block.EndLocation())
+ fmt::format("cycles={}", block.CycleCount())
+ fmt::format(", entry_cond={}", A64::CondToString(block.GetCondition()));
if (block.GetCondition() != Cond::AL) {
if (block.GetCondition() != Cond::AL)
ret += fmt::format(", cond_fail={}", block.ConditionFailedLocation());
}
ret += '\n';
const auto arg_to_string = [](const IR::Value& arg) -> std::string {
if (arg.IsEmpty()) {
return "<null>";
} else if (!arg.IsImmediate()) {
if (const unsigned name = arg.GetInst()->GetName()) {
if (auto const name = arg.GetInst()->GetName())
return fmt::format("%{}", name);
}
return fmt::format("%<unnamed inst {:016x}>", reinterpret_cast<u64>(arg.GetInst()));
return fmt::format("%<unnamed inst {:016x}>", u64(arg.GetInst()));
}
switch (arg.GetType()) {
case Type::U1:
return fmt::format("#{}", arg.GetU1() ? '1' : '0');
case Type::U8:
return fmt::format("#{}", arg.GetU8());
case Type::U16:
return fmt::format("#{:#x}", arg.GetU16());
case Type::U32:
return fmt::format("#{:#x}", arg.GetU32());
case Type::U64:
return fmt::format("#{:#x}", arg.GetU64());
case Type::U128:
return fmt::format("#<u128 imm>");
case Type::A32Reg:
return A32::RegToString(arg.GetA32RegRef());
case Type::A32ExtReg:
return A32::ExtRegToString(arg.GetA32ExtRegRef());
case Type::A64Reg:
return A64::RegToString(arg.GetA64RegRef());
case Type::A64Vec:
return A64::VecToString(arg.GetA64VecRef());
case Type::CoprocInfo:
return fmt::format("#<coproc>");
case Type::NZCVFlags:
return fmt::format("#<NZCV flags>");
case Type::Cond:
return fmt::format("#<cond={}>", A32::CondToString(arg.GetCond()));
case Type::Table:
return fmt::format("#<table>");
case Type::AccType:
return fmt::format("#<acc-type={}>", u32(arg.GetAccType()));
default:
return fmt::format("<unknown immediate type {}>", arg.GetType());
case Type::U1: return fmt::format("#{}", arg.GetU1() ? '1' : '0');
case Type::U8: return fmt::format("#{}", arg.GetU8());
case Type::U16: return fmt::format("#{:#x}", arg.GetU16());
case Type::U32: return fmt::format("#{:#x}", arg.GetU32());
case Type::U64: return fmt::format("#{:#x}", arg.GetU64());
case Type::U128: return fmt::format("#<u128 imm>");
case Type::A32Reg: return A32::RegToString(arg.GetA32RegRef());
case Type::A32ExtReg: return A32::ExtRegToString(arg.GetA32ExtRegRef());
case Type::A64Reg: return A64::RegToString(arg.GetA64RegRef());
case Type::A64Vec: return A64::VecToString(arg.GetA64VecRef());
case Type::CoprocInfo: return fmt::format("#<coproc>");
case Type::NZCVFlags: return fmt::format("#<NZCV flags>");
case Type::Cond: return fmt::format("#<cond={}>", A32::CondToString(arg.GetCond()));
case Type::Table: return fmt::format("#<table>");
case Type::AccType: return fmt::format("#<acc-type={}>", u32(arg.GetAccType()));
default: return fmt::format("<unknown immediate type {}>", arg.GetType());
}
};
for (const auto& inst : block) {
for (const auto& inst : block.instructions) {
const Opcode op = inst.GetOpcode();
ret += fmt::format("[{:016x}] ", reinterpret_cast<u64>(&inst));
@@ -180,13 +162,9 @@ std::string DumpBlock(const IR::Block& block) noexcept {
}
}
ret += fmt::format(" (uses: {})", inst.UseCount());
ret += '\n';
ret += fmt::format(" (uses: {})", inst.UseCount()) + '\n';
}
ret += "terminal = " + TerminalToString(block.GetTerminal()) + '\n';
return ret;
}
+7 -33
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
/* This file is part of the dynarmic project.
@@ -23,7 +23,6 @@
#include "dynarmic/ir/microinstruction.h"
#include "dynarmic/ir/terminal.h"
#include "dynarmic/ir/value.h"
#include "dynarmic/ir/dense_list.h"
namespace Dynarmic::IR {
@@ -34,7 +33,7 @@ enum class Opcode;
/// Note that this is a linear IR and not a pure tree-based IR: i.e.: there is an ordering to
/// the microinstructions. This only matters before chaining is done in order to correctly
/// order memory accesses.
class Block final {
class alignas(4096) Block final {
public:
//using instruction_list_type = dense_list<Inst>;
using instruction_list_type = mcl::intrusive_list<Inst>;
@@ -51,37 +50,12 @@ public:
Block(Block&&) = default;
Block& operator=(Block&&) = default;
bool empty() const { return instructions.empty(); }
size_type size() const { return instructions.size(); }
Inst& front() { return instructions.front(); }
const Inst& front() const { return instructions.front(); }
Inst& back() { return instructions.back(); }
const Inst& back() const { return instructions.back(); }
iterator begin() { return instructions.begin(); }
const_iterator begin() const { return instructions.begin(); }
iterator end() { return instructions.end(); }
const_iterator end() const { return instructions.end(); }
reverse_iterator rbegin() { return instructions.rbegin(); }
const_reverse_iterator rbegin() const { return instructions.rbegin(); }
reverse_iterator rend() { return instructions.rend(); }
const_reverse_iterator rend() const { return instructions.rend(); }
const_iterator cbegin() const { return instructions.cbegin(); }
const_iterator cend() const { return instructions.cend(); }
const_reverse_iterator crbegin() const { return instructions.crbegin(); }
const_reverse_iterator crend() const { return instructions.crend(); }
/// Appends a new instruction to the end of this basic block,
/// handling any allocations necessary to do so.
/// @param op Opcode representing the instruction to add.
/// @param args A sequence of Value instances used as arguments for the instruction.
inline void AppendNewInst(const Opcode opcode, const std::initializer_list<IR::Value> args) noexcept {
PrependNewInst(end(), opcode, args);
inline iterator AppendNewInst(const Opcode opcode, const std::initializer_list<IR::Value> args) noexcept {
return PrependNewInst(instructions.end(), opcode, args);
}
iterator PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args) noexcept;
@@ -165,9 +139,9 @@ public:
inline const size_t& CycleCount() const noexcept {
return cycle_count;
}
private:
/// "Hot cache" for small blocks so we don't call global allocator
boost::container::static_vector<Inst, 14> inlined_inst;
boost::container::static_vector<Inst, 30> inlined_inst;
/// List of instructions in this block.
instruction_list_type instructions;
/// "Long/far" memory pool
@@ -187,7 +161,7 @@ private:
/// Number of cycles this block takes to execute.
size_t cycle_count = 0;
};
static_assert(sizeof(Block) == 2048);
static_assert(sizeof(Block) == 4096);
/// Returns a string representation of the contents of block. Intended for debugging.
std::string DumpBlock(const IR::Block& block) noexcept;
-61
View File
@@ -1,61 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cstdint>
#include <cstddef>
#include <deque>
namespace Dynarmic {
template<typename T> struct dense_list {
using difference_type = std::ptrdiff_t;
using size_type = std::size_t;
using value_type = T;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = typename std::deque<value_type>::iterator;
using const_iterator = typename std::deque<value_type>::const_iterator;
using reverse_iterator = typename std::reverse_iterator<iterator>;
using const_reverse_iterator = typename std::reverse_iterator<const_iterator>;
inline bool empty() const noexcept { return list.empty(); }
inline size_type size() const noexcept { return list.size(); }
inline value_type& front() noexcept { return list.front(); }
inline const value_type& front() const noexcept { return list.front(); }
inline value_type& back() noexcept { return list.back(); }
inline const value_type& back() const noexcept { return list.back(); }
inline iterator begin() noexcept { return list.begin(); }
inline const_iterator begin() const noexcept { return list.begin(); }
inline iterator end() noexcept { return list.end(); }
inline const_iterator end() const noexcept { return list.end(); }
inline reverse_iterator rbegin() noexcept { return list.rbegin(); }
inline const_reverse_iterator rbegin() const noexcept { return list.rbegin(); }
inline reverse_iterator rend() noexcept { return list.rend(); }
inline const_reverse_iterator rend() const noexcept { return list.rend(); }
inline const_iterator cbegin() const noexcept { return list.cbegin(); }
inline const_iterator cend() const noexcept { return list.cend(); }
inline const_reverse_iterator crbegin() const noexcept { return list.crbegin(); }
inline const_reverse_iterator crend() const noexcept { return list.crend(); }
inline iterator insert_before(iterator it, value_type& value) noexcept {
if (it == list.begin()) {
list.push_front(value);
return list.begin();
}
auto const index = std::distance(list.begin(), it - 1);
list.insert(it - 1, value);
return list.begin() + index;
}
std::deque<value_type> list;
};
}
+5 -8
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
/* This file is part of the dynarmic project.
@@ -65,15 +65,12 @@ enum class MemOp {
PREFETCH,
};
/**
* Convenience class to construct a basic block of the intermediate representation.
* `block` is the resulting block.
* The user of this class updates `current_location` as appropriate.
*/
/// @brief Convenience class to construct a basic block of the intermediate representation.
/// `block` is the resulting block.
/// The user of this class updates `current_location` as appropriate.
class IREmitter {
public:
explicit IREmitter(Block& block)
: block(block), insertion_point(block.end()) {}
explicit IREmitter(Block& block) : block(block), insertion_point(block.instructions.end()) {}
Block& block;
+17 -22
View File
@@ -33,7 +33,7 @@
namespace Dynarmic::Optimization {
static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
for (auto& inst : block) {
for (auto& inst : block.instructions) {
switch (inst.GetOpcode()) {
case IR::Opcode::A32ReadMemory8:
case IR::Opcode::A64ReadMemory8: {
@@ -131,7 +131,7 @@ static void FlagsPass(IR::Block& block) {
A32::IREmitter ir{block, A32::LocationDescriptor{block.Location()}, {}};
for (auto inst = block.rbegin(); inst != block.rend(); ++inst) {
for (auto inst = block.instructions.rbegin(); inst != block.instructions.rend(); ++inst) {
auto const opcode = inst->GetOpcode();
switch (opcode) {
case IR::Opcode::A32GetCFlag: {
@@ -318,7 +318,7 @@ static void RegisterPass(IR::Block& block) {
// Location and version don't matter here.
A32::IREmitter ir{block, A32::LocationDescriptor{block.Location()}, {}};
for (auto inst = block.begin(); inst != block.end(); ++inst) {
for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) {
auto const opcode = inst->GetOpcode();
switch (opcode) {
case IR::Opcode::A32GetRegister: {
@@ -448,7 +448,7 @@ static void A64CallbackConfigPass(IR::Block& block, const A64::UserConfig& conf)
return;
}
for (auto& inst : block) {
for (auto& inst : block.instructions) {
if (inst.GetOpcode() != IR::Opcode::A64DataCacheOperationRaised) {
continue;
}
@@ -541,7 +541,7 @@ static void A64GetSetElimination(IR::Block& block) {
do_nothing();
};
for (auto inst = block.begin(); inst != block.end(); ++inst) {
for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) {
auto const opcode = inst->GetOpcode();
switch (opcode) {
case IR::Opcode::A64GetW: {
@@ -1041,7 +1041,7 @@ static void FoldZeroExtendXToLong(IR::Inst& inst) {
}
static void ConstantPropagation(IR::Block& block) {
for (auto& inst : block) {
for (auto& inst : block.instructions) {
auto const opcode = inst.GetOpcode();
switch (opcode) {
case Op::LeastSignificantWord:
@@ -1221,25 +1221,20 @@ static void ConstantPropagation(IR::Block& block) {
static void DeadCodeElimination(IR::Block& block) {
// We iterate over the instructions in reverse order.
// This is because removing an instruction reduces the number of uses for earlier instructions.
for (auto it = block.rbegin(); it != block.rend(); ++it)
for (auto it = block.instructions.rbegin(); it != block.instructions.rend(); ++it)
if (!it->HasUses() && !MayHaveSideEffects(it->GetOpcode()))
it->Invalidate();
}
static void IdentityRemovalPass(IR::Block& block) {
boost::container::small_vector<IR::Inst*, 16> to_invalidate;
for (auto it = block.begin(); it != block.end();) {
const size_t num_args = it->NumArgs();
for (size_t i = 0; i < num_args; ++i) {
IR::Value arg = it->GetArg(i);
if (arg.IsIdentity()) {
do {
arg = arg.GetInst()->GetArg(0);
} while (arg.IsIdentity());
boost::container::small_vector<IR::Inst*, 128> to_invalidate;
for (auto it = block.instructions.begin(); it != block.instructions.end();) {
auto const num_args = it->NumArgs();
for (size_t i = 0; i < num_args; ++i)
if (IR::Value arg = it->GetArg(i); arg.IsIdentity()) {
do arg = arg.GetInst()->GetArg(0); while (arg.IsIdentity());
it->SetArg(i, arg);
}
}
if (it->GetOpcode() == IR::Opcode::Identity || it->GetOpcode() == IR::Opcode::Void) {
to_invalidate.push_back(&*it);
it = block.Instructions().erase(it);
@@ -1253,7 +1248,7 @@ static void IdentityRemovalPass(IR::Block& block) {
static void NamingPass(IR::Block& block) {
u32 name = 1;
for (auto& inst : block)
for (auto& inst : block.instructions)
inst.SetName(name++);
}
@@ -1402,7 +1397,7 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
IR::IREmitter ir{block};
for (auto& inst : block) {
for (auto& inst : block.instructions) {
ir.SetInsertionPointBefore(&inst);
switch (inst.GetOpcode()) {
@@ -1458,7 +1453,7 @@ static void PolyfillPass(IR::Block& block, const PolyfillOptions& polyfill) {
}
static void VerificationPass(const IR::Block& block) {
for (auto const& inst : block) {
for (auto const& inst : block.instructions) {
for (size_t i = 0; i < inst.NumArgs(); i++) {
const IR::Type t1 = inst.GetArg(i).GetType();
const IR::Type t2 = IR::GetArgTypeOf(inst.GetOpcode(), i);
@@ -1466,7 +1461,7 @@ static void VerificationPass(const IR::Block& block) {
}
}
ankerl::unordered_dense::map<IR::Inst*, size_t> actual_uses;
for (auto const& inst : block) {
for (auto const& inst : block.instructions) {
for (size_t i = 0; i < inst.NumArgs(); i++)
if (IR::Value const arg = inst.GetArg(i); !arg.IsImmediate())
actual_uses[arg.GetInst()]++;
+2 -2
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
/* This file is part of the dynarmic project.
@@ -93,7 +93,7 @@ bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A
return false;
}
for (const auto& ir_inst : block) {
for (const auto& ir_inst : block.instructions) {
switch (ir_inst.GetOpcode()) {
case IR::Opcode::A32ExceptionRaised:
case IR::Opcode::A32CallSupervisor:
+2 -2
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
/* This file is part of the dynarmic project.
@@ -45,7 +45,7 @@ static bool ShouldTestInst(u32 instruction, u64 pc, bool is_last_inst) {
return false;
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal))
return false;
for (const auto& ir_inst : block) {
for (const auto& ir_inst : block.instructions) {
switch (ir_inst.GetOpcode()) {
case IR::Opcode::A64ExceptionRaised:
case IR::Opcode::A64CallSupervisor:
+3 -3
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
/* This file is part of the dynarmic project.
@@ -36,9 +36,9 @@ TEST_CASE("ASIMD Decoder: Ensure table order correctness", "[decode][a32][.]") {
const auto is_decode_error = [&get_ir](const A32::ASIMDMatcher<A32::TranslatorVisitor>& matcher, u32 instruction) {
const auto block = get_ir(matcher, instruction);
return std::find_if(block.cbegin(), block.cend(), [](auto const& e) {
return std::find_if(block.instructions.cbegin(), block.instructions.cend(), [](auto const& e) {
return e.GetOpcode() == IR::Opcode::A32ExceptionRaised && A32::Exception(e.GetArg(1).GetU64()) == A32::Exception::DecodeError;
}) != block.cend();
}) != block.instructions.cend();
};
for (auto iter = table.cbegin(); iter != table.cend(); ++iter) {
+2 -2
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
/* This file is part of the dynarmic project.
@@ -54,7 +54,7 @@ bool ShouldTestInst(IR::Block& block) {
return false;
}
for (const auto& ir_inst : block) {
for (const auto& ir_inst : block.instructions) {
switch (ir_inst.GetOpcode()) {
// A32
case IR::Opcode::A32GetFpscr:
+10 -6
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
// Copyright Citra Emulator Project / Azahar Emulator Project
@@ -45,8 +45,7 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
};
client->set_follow_location(true);
httplib::Result result;
result = client->send(request);
httplib::Result result = client->send(request);
if (!result) {
LOG_ERROR(Frontend, "GET to {}{} returned null", url, path);
@@ -78,7 +77,7 @@ std::optional<std::string> UpdateChecker::GetResponse(std::string url, std::stri
}
}
std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prereleases) {
std::optional<UpdateChecker::Update> UpdateChecker::GetLatestRelease(bool include_prereleases) {
const auto update_check_url = std::string{Common::g_build_auto_update_api};
std::string update_check_path = fmt::format("/repos/{}",
std::string{Common::g_build_auto_update_repo});
@@ -96,6 +95,9 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
const std::string latest_tag
= nlohmann::json::parse(tags_response.value()).at(0).at("name");
const std::string latest_name =
nlohmann::json::parse(releases_response.value()).at(0).at("name");
const bool latest_tag_has_release = releases_response.value().find(
fmt::format("\"{}\"", latest_tag))
!= std::string::npos;
@@ -105,7 +107,7 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
if (!latest_tag_has_release)
return {};
return latest_tag;
return Update{latest_tag, latest_name};
} else { // This is a stable release, only check for other stable releases.
update_check_path += "/releases/latest";
const auto response = UpdateChecker::GetResponse(update_check_url, update_check_path);
@@ -114,7 +116,9 @@ std::optional<std::string> UpdateChecker::GetLatestRelease(bool include_prerelea
return {};
const std::string latest_tag = nlohmann::json::parse(response.value()).at("tag_name");
return latest_tag;
const std::string latest_name = nlohmann::json::parse(response.value()).at("name");
return Update{latest_tag, latest_name};
}
} catch (nlohmann::detail::out_of_range &) {
+8 -2
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
// Copyright Citra Emulator Project / Azahar Emulator Project
@@ -11,6 +11,12 @@
#include <string>
namespace UpdateChecker {
typedef struct {
std::string tag;
std::string name;
} Update;
std::optional<std::string> GetResponse(std::string url, std::string path);
std::optional<std::string> GetLatestRelease(bool include_prereleases);
std::optional<Update> GetLatestRelease(bool include_prereleases);
} // namespace UpdateChecker
+1 -13
View File
@@ -345,18 +345,6 @@ 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"),
@@ -756,7 +744,7 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
translations->insert({Settings::EnumMetadata<Settings::ExtendedDynamicState>::Index(),
{
PAIR(ExtendedDynamicState, Disabled, tr("Disabled")),
PAIR(ExtendedDynamicState, Core, tr("Core")),
PAIR(ExtendedDynamicState, EDS1, tr("ExtendedDynamicState 1")),
PAIR(ExtendedDynamicState, EDS2, tr("ExtendedDynamicState 2")),
PAIR(ExtendedDynamicState, EDS3, tr("ExtendedDynamicState 3")),
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -16,7 +19,7 @@ Block::Block(ObjectPool<Inst>& inst_pool_) : inst_pool{&inst_pool_} {}
Block::~Block() = default;
void Block::AppendNewInst(Opcode op, std::initializer_list<Value> args) {
PrependNewInst(end(), op, args);
PrependNewInst(instructions.end(), op, args);
}
Block::iterator Block::PrependNewInst(iterator insertion_point, const Inst& base_inst) {
+3 -2
View File
@@ -1,10 +1,11 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# 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
@@ -313,7 +314,7 @@ add_library(video_core STATIC
)
target_link_libraries(video_core PUBLIC common core)
target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder)
target_link_libraries(video_core PUBLIC glad shader_recompiler stb bc_decoder gpu_logging)
if (YUZU_USE_EXTERNAL_FFMPEG)
add_dependencies(video_core ffmpeg-build)
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+66 -104
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 2022 yuzu Emulator Project
@@ -29,124 +29,104 @@ class MemoryTrackerBase {
static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS);
static constexpr size_t MANAGER_POOL_SIZE = 32;
static constexpr size_t WORDS_STACK_NEEDED = HIGHER_PAGE_SIZE / BYTES_PER_WORD;
using Manager = WordManager<DeviceTracker, WORDS_STACK_NEEDED>;
using Manager = WordManager<DeviceTracker, WORDS_STACK_NEEDED, HIGHER_PAGE_SIZE>;
public:
MemoryTrackerBase(DeviceTracker& device_tracker_) : device_tracker{&device_tracker_} {}
~MemoryTrackerBase() = default;
/// Returns the inclusive CPU modified range in a begin end pair
[[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
u64 query_size) noexcept {
return IteratePairs<true>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template ModifiedRegion<Type::CPU>(offset, size);
});
[[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr, u64 query_size) noexcept {
return IteratePairs<true>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->ModifiedRegion(Type::CPU, offset, size);
});
}
/// Returns the inclusive GPU modified range in a begin end pair
[[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
u64 query_size) noexcept {
return IteratePairs<false>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template ModifiedRegion<Type::GPU>(offset, size);
});
[[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr, u64 query_size) noexcept {
return IteratePairs<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->ModifiedRegion(Type::GPU, offset, size);
});
}
/// Returns true if a region has been modified from the CPU
[[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
return IteratePages<true>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template IsRegionModified<Type::CPU>(offset, size);
});
return IteratePages<true>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->IsRegionModified(Type::CPU, offset, size);
});
}
/// Returns true if a region has been modified from the GPU
[[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
return IteratePages<false>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template IsRegionModified<Type::GPU>(offset, size);
});
return IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->IsRegionModified(Type::GPU, offset, size);
});
}
/// Returns true if a region has been marked as Preflushable
[[nodiscard]] bool IsRegionPreflushable(VAddr query_cpu_addr, u64 query_size) noexcept {
return IteratePages<false>(
query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->template IsRegionModified<Type::Preflushable>(offset, size);
});
return IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
return manager->IsRegionModified(Type::Preflushable, offset, size);
});
}
/// Mark region as CPU modified, notifying the device_tracker about this change
void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::CPU, true>(
manager->GetCpuAddr() + offset, size);
});
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
manager->ChangeRegionState(Type::CPU, true, manager->cpu_addr + offset, size);
});
}
/// Unmark region as CPU modified, notifying the device_tracker about this change
void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::CPU, false>(
manager->GetCpuAddr() + offset, size);
});
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
manager->ChangeRegionState(Type::CPU, false, manager->cpu_addr + offset, size);
});
}
/// Mark region as modified from the host GPU
void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::GPU, true>(
manager->GetCpuAddr() + offset, size);
});
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
manager->ChangeRegionState(Type::GPU, true, manager->cpu_addr + offset, size);
});
}
/// Mark region as modified from the host GPU
void MarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::Preflushable, true>(
manager->GetCpuAddr() + offset, size);
});
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
manager->ChangeRegionState(Type::Preflushable, true, manager->cpu_addr + offset, size);
});
}
/// Unmark region as modified from the host GPU
void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::GPU, false>(
manager->GetCpuAddr() + offset, size);
});
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
manager->ChangeRegionState(Type::GPU, false, manager->cpu_addr + offset, size);
});
}
/// Unmark region as modified from the host GPU
void UnmarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
IteratePages<true>(dirty_cpu_addr, query_size,
[](Manager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::Preflushable, false>(
manager->GetCpuAddr() + offset, size);
});
IteratePages<true>(dirty_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
manager->ChangeRegionState(Type::Preflushable, false, manager->cpu_addr + offset, size);
});
}
/// Mark region as modified from the CPU
/// but don't mark it as modified until FlusHCachedWrites is called.
void CachedCpuWrite(VAddr dirty_cpu_addr, u64 query_size) {
IteratePages<true>(
dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
const VAddr cpu_address = manager->GetCpuAddr() + offset;
manager->template ChangeRegionState<Type::CachedCPU, true>(cpu_address, size);
cached_pages.insert(static_cast<u32>(cpu_address >> HIGHER_PAGE_BITS));
});
IteratePages<true>(dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
const VAddr cpu_address = manager->cpu_addr + offset;
manager->ChangeRegionState(Type::CachedCPU, true, cpu_address, size);
cached_pages.insert(u32(cpu_address >> HIGHER_PAGE_BITS));
});
}
/// Flushes cached CPU writes, and notify the device_tracker about the deltas
void FlushCachedWrites(VAddr query_cpu_addr, u64 query_size) noexcept {
IteratePages<false>(query_cpu_addr, query_size,
[](Manager* manager, [[maybe_unused]] u64 offset,
[[maybe_unused]] size_t size) { manager->FlushCachedWrites(); });
IteratePages<false>(query_cpu_addr, query_size, [](Manager* manager, [[maybe_unused]] u64 offset, [[maybe_unused]] size_t size) {
manager->FlushCachedWrites();
});
}
void FlushCachedWrites() noexcept {
@@ -159,35 +139,24 @@ public:
/// Call 'func' for each CPU modified range and unmark those pages as CPU modified
template <typename Func>
void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) {
IteratePages<true>(query_cpu_range, query_size,
[&func](Manager* manager, u64 offset, size_t size) {
manager->template ForEachModifiedRange<Type::CPU, true>(
manager->GetCpuAddr() + offset, size, func);
});
IteratePages<true>(query_cpu_range, query_size, [&func](Manager* manager, u64 offset, size_t size) {
manager->ForEachModifiedRange(Type::CPU, true, manager->cpu_addr + offset, size, func);
});
}
/// Call 'func' for each GPU modified range and unmark those pages as GPU modified
template <typename Func>
void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, bool clear, Func&& func) {
IteratePages<false>(query_cpu_range, query_size,
[&func, clear](Manager* manager, u64 offset, size_t size) {
if (clear) {
manager->template ForEachModifiedRange<Type::GPU, true>(
manager->GetCpuAddr() + offset, size, func);
} else {
manager->template ForEachModifiedRange<Type::GPU, false>(
manager->GetCpuAddr() + offset, size, func);
}
});
IteratePages<false>(query_cpu_range, query_size, [&func, clear](Manager* manager, u64 offset, size_t size) {
manager->ForEachModifiedRange(Type::GPU, clear, manager->cpu_addr + offset, size, func);
});
}
template <typename Func>
void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 query_size, Func&& func) {
IteratePages<false>(query_cpu_range, query_size,
[&func](Manager* manager, u64 offset, size_t size) {
manager->template ForEachModifiedRange<Type::GPU, true>(
manager->GetCpuAddr() + offset, size, func);
});
IteratePages<false>(query_cpu_range, query_size, [&func](Manager* manager, u64 offset, size_t size) {
manager->ForEachModifiedRange(Type::GPU, true, manager->cpu_addr + offset, size, func);
});
}
private:
@@ -271,31 +240,24 @@ private:
}
Manager* GetNewManager(VAddr base_cpu_address) {
const auto on_return = [&] {
auto* new_manager = free_managers.front();
new_manager->SetCpuAddress(base_cpu_address);
free_managers.pop_front();
return new_manager;
};
if (!free_managers.empty()) {
return on_return();
if (free_managers.empty()) {
manager_pool.emplace_back();
auto& last_pool = manager_pool.back();
for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
new (&last_pool[i]) Manager(0, *device_tracker);
free_managers.push_back(&last_pool[i]);
}
}
manager_pool.emplace_back();
auto& last_pool = manager_pool.back();
for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
new (&last_pool[i]) Manager(0, *device_tracker, HIGHER_PAGE_SIZE);
free_managers.push_back(&last_pool[i]);
}
return on_return();
Manager* new_manager = free_managers.front();
new_manager->cpu_addr = base_cpu_address;
free_managers.pop_front();
return new_manager;
}
std::array<Manager*, NUM_HIGH_PAGES> top_tier{};
std::deque<std::array<Manager, MANAGER_POOL_SIZE>> manager_pool;
std::deque<Manager*> free_managers;
std::array<Manager*, NUM_HIGH_PAGES> top_tier{};
std::unordered_set<u32> cached_pages;
DeviceTracker* device_tracker = nullptr;
};
+137 -345
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 2022 yuzu Emulator Project
@@ -31,158 +31,26 @@ enum class Type {
CachedCPU,
Untracked,
Preflushable,
Max
};
/// Vector tracking modified pages tightly packed with small vector optimization
template <size_t stack_words = 1>
struct WordsArray {
/// Returns the pointer to the words state
[[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
return is_short ? stack.data() : heap;
}
template <class DeviceTracker, size_t stack_words, size_t size_bytes>
struct WordManager {
static constexpr size_t num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD);
/// Returns the pointer to the words state
[[nodiscard]] u64* Pointer(bool is_short) noexcept {
return is_short ? stack.data() : heap;
}
std::array<u64, stack_words> stack{}; ///< Small buffers storage
u64* heap; ///< Not-small buffers pointer to the storage
};
template <size_t stack_words = 1>
struct Words {
explicit Words() = default;
explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} {
num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD);
if (IsShort()) {
cpu.stack.fill(~u64{0});
gpu.stack.fill(0);
cached_cpu.stack.fill(0);
untracked.stack.fill(~u64{0});
preflushable.stack.fill(0);
} else {
// Share allocation between CPU and GPU pages and set their default values
u64* const alloc = new u64[num_words * 5];
cpu.heap = alloc;
gpu.heap = alloc + num_words;
cached_cpu.heap = alloc + num_words * 2;
untracked.heap = alloc + num_words * 3;
preflushable.heap = alloc + num_words * 4;
std::fill_n(cpu.heap, num_words, ~u64{0});
std::fill_n(gpu.heap, num_words, 0);
std::fill_n(cached_cpu.heap, num_words, 0);
std::fill_n(untracked.heap, num_words, ~u64{0});
std::fill_n(preflushable.heap, num_words, 0);
}
explicit WordManager(VAddr cpu_addr_, DeviceTracker& tracker_) : tracker{&tracker_}, cpu_addr{cpu_addr_} {
std::fill_n(heap.data() + size_t(Type::CPU) * num_words, num_words, ~u64{0});
std::fill_n(heap.data() + size_t(Type::Untracked) * num_words, num_words, ~u64{0});
// Clean up tailing bits
const u64 last_word_size = size_bytes % BYTES_PER_WORD;
const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
const u64 last_word = (~u64{0} << shift) >> shift;
cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
u64 const last_word_size = size_bytes % BYTES_PER_WORD;
u64 const last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
u64 const shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
u64 const last_word = (~u64{0} << shift) >> shift;
heap[num_words * size_t(Type::CPU) + num_words - 1] = last_word;
heap[num_words * size_t(Type::Untracked) + num_words - 1] = last_word;
}
~Words() {
Release();
}
Words& operator=(Words&& rhs) noexcept {
Release();
size_bytes = rhs.size_bytes;
num_words = rhs.num_words;
cpu = rhs.cpu;
gpu = rhs.gpu;
cached_cpu = rhs.cached_cpu;
untracked = rhs.untracked;
preflushable = rhs.preflushable;
rhs.cpu.heap = nullptr;
return *this;
}
Words(Words&& rhs) noexcept
: size_bytes{rhs.size_bytes}, num_words{rhs.num_words}, cpu{rhs.cpu}, gpu{rhs.gpu},
cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked}, preflushable{rhs.preflushable} {
rhs.cpu.heap = nullptr;
}
Words& operator=(const Words&) = delete;
Words(const Words&) = delete;
/// Returns true when the buffer fits in the small vector optimization
[[nodiscard]] bool IsShort() const noexcept {
return num_words <= stack_words;
}
/// Returns the number of words of the buffer
[[nodiscard]] size_t NumWords() const noexcept {
return num_words;
}
/// Release buffer resources
void Release() {
if (!IsShort()) {
// CPU written words is the base for the heap allocation
delete[] cpu.heap;
}
}
template <Type type>
std::span<u64> Span() noexcept {
if constexpr (type == Type::CPU) {
return std::span<u64>(cpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::GPU) {
return std::span<u64>(gpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::CachedCPU) {
return std::span<u64>(cached_cpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::Untracked) {
return std::span<u64>(untracked.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::Preflushable) {
return std::span<u64>(preflushable.Pointer(IsShort()), num_words);
}
}
template <Type type>
std::span<const u64> Span() const noexcept {
if constexpr (type == Type::CPU) {
return std::span<const u64>(cpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::GPU) {
return std::span<const u64>(gpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::CachedCPU) {
return std::span<const u64>(cached_cpu.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::Untracked) {
return std::span<const u64>(untracked.Pointer(IsShort()), num_words);
} else if constexpr (type == Type::Preflushable) {
return std::span<const u64>(preflushable.Pointer(IsShort()), num_words);
}
}
u64 size_bytes = 0;
size_t num_words = 0;
WordsArray<stack_words> cpu;
WordsArray<stack_words> gpu;
WordsArray<stack_words> cached_cpu;
WordsArray<stack_words> untracked;
WordsArray<stack_words> preflushable;
};
template <class DeviceTracker, size_t stack_words = 1>
class WordManager {
public:
explicit WordManager(VAddr cpu_addr_, DeviceTracker& tracker_, u64 size_bytes)
: cpu_addr{cpu_addr_}, tracker{&tracker_}, words{size_bytes} {}
explicit WordManager() = default;
void SetCpuAddress(VAddr new_cpu_addr) {
cpu_addr = new_cpu_addr;
}
VAddr GetCpuAddr() const {
return cpu_addr;
}
static u64 ExtractBits(u64 word, size_t page_start, size_t page_end) {
constexpr size_t number_bits = sizeof(u64) * 8;
const size_t limit_page_end = number_bits - (std::min)(page_end, number_bits);
@@ -192,7 +60,7 @@ public:
}
static std::pair<size_t, size_t> GetWordPage(VAddr address) {
const size_t converted_address = static_cast<size_t>(address);
const size_t converted_address = size_t(address);
const size_t word_number = converted_address / BYTES_PER_WORD;
const size_t amount_pages = converted_address % BYTES_PER_WORD;
return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE);
@@ -201,32 +69,28 @@ public:
template <typename Func>
void IterateWords(size_t offset, size_t size, Func&& func) const {
using FuncReturn = std::invoke_result_t<Func, std::size_t, u64>;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
const size_t start = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset), 0LL));
const size_t end = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset + size), 0LL));
if (start >= SizeBytes() || end <= start) {
return;
}
auto [start_word, start_page] = GetWordPage(start);
auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
const size_t num_words = NumWords();
start_word = (std::min)(start_word, num_words);
end_word = (std::min)(end_word, num_words);
const size_t diff = end_word - start_word;
end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
end_word = (std::min)(end_word, num_words);
end_page += diff * PAGES_PER_WORD;
constexpr u64 base_mask{~0ULL};
for (size_t word_index = start_word; word_index < end_word; word_index++) {
const u64 mask = ExtractBits(base_mask, start_page, end_page);
start_page = 0;
end_page -= PAGES_PER_WORD;
if constexpr (BOOL_BREAK) {
if (func(word_index, mask)) {
return;
const size_t start = size_t(std::max<s64>(s64(offset), 0LL));
const size_t end = size_t(std::max<s64>(s64(offset + size), 0LL));
if (!(start >= size_bytes || end <= start)) {
auto [start_word, start_page] = GetWordPage(start);
auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
start_word = (std::min)(start_word, num_words);
end_word = (std::min)(end_word, num_words);
const size_t diff = end_word - start_word;
end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
end_word = (std::min)(end_word, num_words);
end_page += diff * PAGES_PER_WORD;
constexpr u64 base_mask{~0ULL};
for (size_t word_index = start_word; word_index < end_word; word_index++) {
const u64 mask = ExtractBits(base_mask, start_page, end_page);
start_page = 0;
end_page -= PAGES_PER_WORD;
if constexpr (std::is_same_v<FuncReturn, bool>) { // bool return
if (func(word_index, mask))
return;
} else {
func(word_index, mask);
}
} else {
func(word_index, mask);
}
}
}
@@ -246,39 +110,32 @@ public:
}
}
/**
* Change the state of a range of pages
*
* @param dirty_addr Base address to mark or unmark as modified
* @param size Size in bytes to mark or unmark as modified
*/
template <Type type, bool enable>
void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
std::span<u64> state_words = words.template Span<type>();
[[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
[[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
/// @brief Change the state of a range of pages
/// @param type Type of the page
/// @param enable If enabling or disabling
/// @param dirty_addr Base address to mark or unmark as modified
/// @param size Size in bytes to mark or unmark as modified
void ChangeRegionState(Type type, bool enable, u64 dirty_addr, u64 size) noexcept {
std::span<u64> state_words = Span(type);
[[maybe_unused]] std::span<u64> untracked_words = Span(Type::Untracked);
[[maybe_unused]] std::span<u64> cached_words = Span(Type::CachedCPU);
std::vector<std::pair<VAddr, u64>> ranges;
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
CollectChangedRanges<(!enable)>(index, untracked_words[index], mask, ranges);
if (type == Type::CPU || type == Type::CachedCPU) {
CollectChangedRanges(!enable, index, untracked_words[index], mask, ranges);
}
if constexpr (enable) {
if (enable) {
state_words[index] |= mask;
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
if (type == Type::CPU || type == Type::CachedCPU)
untracked_words[index] |= mask;
}
if constexpr (type == Type::CPU) {
if (type == Type::CPU)
cached_words[index] &= ~mask;
}
} else {
if constexpr (type == Type::CPU) {
const u64 word = state_words[index] & mask;
cached_words[index] &= ~word;
}
if (type == Type::CPU)
cached_words[index] &= ~(state_words[index] & mask);
state_words[index] &= ~mask;
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
if (type == Type::CPU || type == Type::CachedCPU)
untracked_words[index] &= ~mask;
}
}
});
if (!ranges.empty()) {
@@ -286,22 +143,20 @@ public:
}
}
/**
* Loop over each page in the given range, turn off those bits and notify the tracker if
* needed. Call the given function on each turned off range.
*
* @param query_cpu_range Base CPU address to loop over
* @param size Size in bytes of the CPU range to loop over
* @param func Function to call for each turned off region
*/
template <Type type, bool clear, typename Func>
void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) {
static_assert(type != Type::Untracked);
std::span<u64> state_words = words.template Span<type>();
[[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
[[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
const size_t offset = query_cpu_range - cpu_addr;
/// @brief Loop over each page in the given range.
/// Turn off those bits and notify the tracker if needed. Call the given function on each turned off range.
/// @param type Type of the address
/// @param clear Whetever to clear
/// @param query_cpu_range Base CPU address to loop over
/// @param size Size in bytes of the CPU range to loop over
/// @param func Function to call for each turned off region
template <typename Func>
void ForEachModifiedRange(Type type, bool clear, VAddr query_cpu_range, s64 size, Func&& func) {
//static_assert(type != Type::Untracked);
std::span<u64> state_words = Span(type);
std::span<u64> untracked_words = Span(Type::Untracked);
std::span<u64> cached_words = Span(Type::CachedCPU);
size_t const offset = query_cpu_range - cpu_addr;
bool pending = false;
size_t pending_offset{};
size_t pending_pointer{};
@@ -311,39 +166,32 @@ public:
};
std::vector<std::pair<VAddr, u64>> ranges;
IterateWords(offset, size, [&](size_t index, u64 mask) {
if constexpr (type == Type::GPU) {
if (type == Type::GPU)
mask &= ~untracked_words[index];
}
const u64 word = state_words[index] & mask;
if constexpr (clear) {
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
CollectChangedRanges<true>(index, untracked_words[index], mask, ranges);
if (clear) {
if (type == Type::CPU || type == Type::CachedCPU) {
CollectChangedRanges(true, index, untracked_words[index], mask, ranges);
}
state_words[index] &= ~mask;
if constexpr (type == Type::CPU || type == Type::CachedCPU) {
if (type == Type::CPU || type == Type::CachedCPU)
untracked_words[index] &= ~mask;
}
if constexpr (type == Type::CPU) {
if (type == Type::CPU)
cached_words[index] &= ~word;
}
}
const size_t base_offset = index * PAGES_PER_WORD;
IteratePages(word, [&](size_t pages_offset, size_t pages_size) {
const auto reset = [&]() {
if (!pending) {
pending_offset = base_offset + pages_offset;
pending_pointer = base_offset + pages_offset + pages_size;
};
if (!pending) {
reset();
pending = true;
return;
}
if (pending_pointer == base_offset + pages_offset) {
} else if (pending_pointer == base_offset + pages_offset) {
pending_pointer += pages_size;
return;
} else {
func(cpu_addr + pending_offset * BYTES_PER_PAGE, (pending_pointer - pending_offset) * BYTES_PER_PAGE);
pending_offset = base_offset + pages_offset;
pending_pointer = base_offset + pages_offset + pages_size;
}
release();
reset();
});
});
if (pending) {
@@ -354,90 +202,55 @@ public:
}
}
/**
* Returns true when a region has been modified
*
* @param offset Offset in bytes from the start of the buffer
* @param size Size in bytes of the region to query for modifications
*/
template <Type type>
[[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
static_assert(type != Type::Untracked);
const std::span<const u64> state_words = words.template Span<type>();
[[maybe_unused]] const std::span<const u64> untracked_words =
words.template Span<Type::Untracked>();
/// @brief Returns true when a region has been modified
/// @param type Type of region
/// @param offset Offset in bytes from the start of the buffer
/// @param size Size in bytes of the region to query for modifications
[[nodiscard]] bool IsRegionModified(Type type, u64 offset, u64 size) const noexcept {
//static_assert(type != Type::Untracked);
const std::span<const u64> state_words = Span(type);
const std::span<const u64> untracked_words = Span(Type::Untracked);
bool result = false;
IterateWords(offset, size, [&](size_t index, u64 mask) {
if constexpr (type == Type::GPU) {
if (type == Type::GPU)
mask &= ~untracked_words[index];
}
const u64 word = state_words[index] & mask;
if (word != 0) {
result = true;
return true;
}
return false;
return (state_words[index] & mask) != 0 ? (result = true) : false;
});
return result;
}
/**
* Returns a begin end pair with the inclusive modified region
*
* @param offset Offset in bytes from the start of the buffer
* @param size Size in bytes of the region to query for modifications
*/
template <Type type>
[[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
static_assert(type != Type::Untracked);
const std::span<const u64> state_words = words.template Span<type>();
[[maybe_unused]] const std::span<const u64> untracked_words =
words.template Span<Type::Untracked>();
u64 begin = (std::numeric_limits<u64>::max)();
u64 end = 0;
/// @brief Returns a begin end pair with the inclusive modified region
/// @param offset Offset in bytes from the start of the buffer
/// @param size Size in bytes of the region to query for modifications
[[nodiscard]] std::pair<u64, u64> ModifiedRegion(Type type, u64 offset, u64 size) const noexcept {
//static_assert(type != Type::Untracked);
const std::span<const u64> state_words = Span(type);
const std::span<const u64> untracked_words = Span(Type::Untracked);
u64 begin = (std::numeric_limits<u64>::max)(), end = 0;
IterateWords(offset, size, [&](size_t index, u64 mask) {
if constexpr (type == Type::GPU) {
if (type == Type::GPU)
mask &= ~untracked_words[index];
}
const u64 word = state_words[index] & mask;
if (word == 0) {
return;
if (word != 0) {
const u64 local_page_begin = std::countr_zero(word);
const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
const u64 page_index = index * PAGES_PER_WORD;
begin = (std::min)(begin, page_index + local_page_begin);
end = page_index + local_page_end;
}
const u64 local_page_begin = std::countr_zero(word);
const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
const u64 page_index = index * PAGES_PER_WORD;
begin = (std::min)(begin, page_index + local_page_begin);
end = page_index + local_page_end;
});
static constexpr std::pair<u64, u64> EMPTY{0, 0};
return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
}
/// Returns the number of words of the manager
[[nodiscard]] size_t NumWords() const noexcept {
return words.NumWords();
}
/// Returns the size in bytes of the manager
[[nodiscard]] u64 SizeBytes() const noexcept {
return words.size_bytes;
}
/// Returns true when the buffer fits in the small vector optimization
[[nodiscard]] bool IsShort() const noexcept {
return words.IsShort();
return begin < end ? std::make_pair<u64, u64>(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE)
: std::make_pair<u64, u64>(0, 0);
}
void FlushCachedWrites() noexcept {
const u64 num_words = NumWords();
u64* const cached_words = Array<Type::CachedCPU>();
u64* const untracked_words = Array<Type::Untracked>();
u64* const cpu_words = Array<Type::CPU>();
auto const cached_words = Span(Type::CachedCPU);
auto const untracked_words = Span(Type::Untracked);
auto const cpu_words = Span(Type::CPU);
std::vector<std::pair<VAddr, u64>> ranges;
for (u64 word_index = 0; word_index < num_words; ++word_index) {
const u64 cached_bits = cached_words[word_index];
CollectChangedRanges<false>(word_index, untracked_words[word_index], cached_bits, ranges);
CollectChangedRanges(false, word_index, untracked_words[word_index], cached_bits, ranges);
untracked_words[word_index] |= cached_bits;
cpu_words[word_index] |= cached_bits;
cached_words[word_index] = 0;
@@ -447,45 +260,13 @@ public:
}
}
private:
template <Type type>
u64* Array() noexcept {
if constexpr (type == Type::CPU) {
return words.cpu.Pointer(IsShort());
} else if constexpr (type == Type::GPU) {
return words.gpu.Pointer(IsShort());
} else if constexpr (type == Type::CachedCPU) {
return words.cached_cpu.Pointer(IsShort());
} else if constexpr (type == Type::Untracked) {
return words.untracked.Pointer(IsShort());
}
}
template <Type type>
const u64* Array() const noexcept {
if constexpr (type == Type::CPU) {
return words.cpu.Pointer(IsShort());
} else if constexpr (type == Type::GPU) {
return words.gpu.Pointer(IsShort());
} else if constexpr (type == Type::CachedCPU) {
return words.cached_cpu.Pointer(IsShort());
} else if constexpr (type == Type::Untracked) {
return words.untracked.Pointer(IsShort());
}
}
/**
* Notify tracker about changes in the CPU tracking state of a word in the buffer
*
* @param word_index Index to the word to notify to the tracker
* @param current_bits Current state of the word
* @param new_bits New state of the word
*
* @tparam add_to_tracker True when the tracker should start tracking the new pages
*/
template <bool add_to_tracker>
void CollectChangedRanges(u64 word_index, u64 current_bits, u64 new_bits,
std::vector<std::pair<VAddr, u64>>& out_ranges) const {
/// @brief Notify tracker about changes in the CPU tracking state of a word in the buffer
/// @param add_to_tracker If add to tracker (selects changed bits)
/// @param word_index Index to the word to notify to the tracker
/// @param current_bits Current state of the word
/// @param new_bits New state of the word
/// @tparam add_to_tracker True when the tracker should start tracking the new pages
void CollectChangedRanges(bool add_to_tracker, u64 word_index, u64 current_bits, u64 new_bits, std::vector<std::pair<VAddr, u64>>& out_ranges) const {
u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits;
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
IteratePages(changed_bits, [&](size_t offset, size_t size) {
@@ -494,9 +275,9 @@ private:
}
void ApplyCollectedRanges(std::vector<std::pair<VAddr, u64>>& ranges, int delta) const {
if (ranges.empty()) return;
std::sort(ranges.begin(), ranges.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
if (ranges.empty())
return;
std::sort(ranges.begin(), ranges.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
// Coalesce adjacent/contiguous ranges
std::vector<std::pair<VAddr, size_t>> coalesced;
coalesced.reserve(ranges.size());
@@ -517,19 +298,30 @@ private:
ranges.clear();
}
template <bool add_to_tracker>
void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
/// @brief Notify tracker about changes in the CPU tracking state of a word in the buffer
/// @param add_to_tracker True when the tracker should start tracking the new pages
/// @param word_index Index to the word to notify to the tracker
/// @param current_bits Current state of the word
/// @param new_bits New state of the word
void NotifyRasterizer(bool add_to_tracker, u64 word_index, u64 current_bits, u64 new_bits) const {
u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits;
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
IteratePages(changed_bits, [&](size_t offset, size_t size) {
tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE,
add_to_tracker ? 1 : -1);
tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE, add_to_tracker ? 1 : -1);
});
}
VAddr cpu_addr = 0;
std::span<u64> Span(Type type) noexcept {
return std::span<u64>(heap.data() + num_words * size_t(type), num_words);
}
std::span<const u64> Span(Type type) const noexcept {
return std::span<const u64>(heap.data() + num_words * size_t(type), num_words);
}
std::array<u64, size_t(Type::Max) * num_words> heap = {};
DeviceTracker* tracker = nullptr;
Words<stack_words> words;
VAddr cpu_addr = 0;
};
} // namespace VideoCommon
+25
View File
@@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
add_library(gpu_logging STATIC
gpu_logging.cpp
gpu_logging.h
gpu_state_capture.cpp
gpu_state_capture.h
qualcomm_debug.cpp
qualcomm_debug.h
)
if(ANDROID)
target_sources(gpu_logging PRIVATE
freedreno_debug.cpp
freedreno_debug.h
)
endif()
target_link_libraries(gpu_logging PUBLIC common)
if(ANDROID)
# Link with adrenotools when available for future Qualcomm integration
# target_link_libraries(gpu_logging PUBLIC adrenotools)
endif()
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#ifdef ANDROID
#include "video_core/gpu_logging/freedreno_debug.h"
#include "common/logging/log.h"
#include <cstdlib>
namespace GPU::Logging::Freedreno {
bool FreedrenoDebugger::is_initialized = false;
void FreedrenoDebugger::Initialize() {
if (is_initialized) {
return;
}
is_initialized = true;
LOG_INFO(Render_Vulkan, "[Freedreno Debug] Initialized");
}
void FreedrenoDebugger::SetTUDebugFlags(const std::string& flags) {
if (flags.empty()) {
return;
}
// Set TU_DEBUG environment variable
// Note: This should be set BEFORE Vulkan driver is loaded
setenv("TU_DEBUG", flags.c_str(), 1);
LOG_INFO(Render_Vulkan, "[Freedreno Debug] TU_DEBUG set to: {}", flags);
}
void FreedrenoDebugger::EnableCommandStreamDump(bool frames_only) {
// Enable FD_RD_DUMP for command stream capture
const char* dump_flags = frames_only ? "frames" : "all";
setenv("FD_RD_DUMP", dump_flags, 1);
LOG_INFO(Render_Vulkan, "[Freedreno Debug] Command stream dump enabled: {}", dump_flags);
}
std::string FreedrenoDebugger::GetBreadcrumbs() {
// Breadcrumb reading requires driver-specific implementation
// This is a stub for future implementation
return "Breadcrumb capture not yet implemented";
}
} // namespace GPU::Logging::Freedreno
#endif // ANDROID
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#ifdef ANDROID
#include <string>
namespace GPU::Logging::Freedreno {
class FreedrenoDebugger {
public:
// Initialize Freedreno debugging
static void Initialize();
// Set TU_DEBUG environment variable flags
static void SetTUDebugFlags(const std::string& flags);
// Enable command stream dump
static void EnableCommandStreamDump(bool frames_only = false);
// Get breadcrumb information (if available)
static std::string GetBreadcrumbs();
private:
static bool is_initialized;
};
} // namespace GPU::Logging::Freedreno
#endif // ANDROID
+734
View File
@@ -0,0 +1,734 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "video_core/gpu_logging/gpu_logging.h"
#include <fmt/format.h>
#include <thread>
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/literals.h"
#include "common/logging/log.h"
#include "common/settings.h"
namespace GPU::Logging {
// Static instance
static GPULogger* g_instance = nullptr;
GPULogger& GPULogger::GetInstance() {
if (!g_instance) {
g_instance = new GPULogger();
}
return *g_instance;
}
GPULogger::GPULogger() = default;
GPULogger::~GPULogger() {
Shutdown();
}
void GPULogger::Initialize(LogLevel level, DriverType driver) {
if (initialized) {
LOG_WARNING(Render_Vulkan, "[GPU Logging] Already initialized");
return;
}
current_level = level;
detected_driver = driver;
if (current_level == LogLevel::Off) {
return;
}
// Create log directory
using namespace Common::FS;
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
[[maybe_unused]] const bool log_dir_created = CreateDir(log_dir);
// Create GPU crashes directory
const auto crashes_dir = log_dir / "gpu_crashes";
[[maybe_unused]] const bool crashes_dir_created = CreateDir(crashes_dir);
// Open GPU log file
const auto gpu_log_path = log_dir / "eden_gpu.log";
// Rotate old log
const auto old_log_path = log_dir / "eden_gpu.log.old.txt";
RemoveFile(old_log_path);
[[maybe_unused]] const bool log_renamed = RenameFile(gpu_log_path, old_log_path);
// Open new log file
gpu_log_file = std::make_unique<Common::FS::IOFile>(
gpu_log_path, Common::FS::FileAccessMode::Write, Common::FS::FileType::TextFile);
if (!gpu_log_file->IsOpen()) {
LOG_ERROR(Render_Vulkan, "[GPU Logging] Failed to open GPU log file");
return;
}
// Initialize ring buffer
call_ring_buffer.resize(ring_buffer_size);
// Write header
const char* driver_name = "Unknown";
switch (detected_driver) {
case DriverType::Turnip:
driver_name = "Turnip (Mesa Freedreno)";
break;
case DriverType::Qualcomm:
driver_name = "Qualcomm Proprietary";
break;
default:
driver_name = "Unknown";
break;
}
const char* level_name = "Unknown";
switch (current_level) {
case LogLevel::Off:
level_name = "Off";
break;
case LogLevel::Errors:
level_name = "Errors";
break;
case LogLevel::Standard:
level_name = "Standard";
break;
case LogLevel::Verbose:
level_name = "Verbose";
break;
case LogLevel::All:
level_name = "All";
break;
}
const auto header = fmt::format(
"=== Eden GPU Logging Started ===\n"
"Timestamp: {}\n"
"Log Level: {}\n"
"Driver: {}\n"
"Ring Buffer Size: {}\n"
"================================\n\n",
FormatTimestamp(std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())),
level_name, driver_name, ring_buffer_size);
WriteToLog(header);
// Note: Crash handler is initialized independently in EmulationSession::InitializeSystem()
// to ensure it remains active even if Vulkan device initialization fails
initialized = true;
LOG_INFO(Render_Vulkan, "[GPU Logging] Initialized with level: {}, driver: {}", level_name,
driver_name);
}
void GPULogger::Shutdown() {
if (!initialized) {
return;
}
// Write statistics
const auto stats = fmt::format(
"\n=== GPU Logging Statistics ===\n"
"Total Vulkan Calls: {}\n"
"Total Memory Allocations: {}\n"
"Total Memory Deallocations: {}\n"
"Peak Memory Usage: {}\n"
"Current Memory Usage: {}\n"
"Log Size: {} bytes\n"
"==============================\n",
total_vulkan_calls, total_allocations, total_deallocations,
FormatMemorySize(peak_allocated_bytes), FormatMemorySize(current_allocated_bytes),
bytes_written);
WriteToLog(stats);
// Close file
if (gpu_log_file) {
gpu_log_file->Flush();
gpu_log_file->Close();
gpu_log_file.reset();
}
// Note: Crash handler is NOT shut down here - it remains active throughout app lifetime
// It will be shut down when EmulationSession is destroyed
initialized = false;
LOG_INFO(Render_Vulkan, "[GPU Logging] Shutdown complete");
}
void GPULogger::LogVulkanCall(const std::string& call_name, const std::string& params,
int result) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls) {
return;
}
// Only log all calls in Verbose or All mode
if (current_level != LogLevel::Verbose && current_level != LogLevel::All) {
// In Standard mode, only log important calls
if (call_name.find("vkCmd") == std::string::npos &&
call_name.find("vkCreate") == std::string::npos &&
call_name.find("vkDestroy") == std::string::npos) {
return;
}
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto thread_id = static_cast<u32>(std::hash<std::thread::id>{}(std::this_thread::get_id()));
// Add to ring buffer
{
std::lock_guard lock(ring_buffer_mutex);
call_ring_buffer[ring_buffer_index] = {
.timestamp = timestamp,
.call_name = call_name,
.parameters = params,
.result = result,
.thread_id = thread_id,
};
ring_buffer_index = (ring_buffer_index + 1) % ring_buffer_size;
total_vulkan_calls++;
}
// Log to file
const auto log_entry =
fmt::format("[{}] [Vulkan] [Thread:{}] {}({}) -> {}\n", FormatTimestamp(timestamp),
thread_id, call_name, params, result);
WriteToLog(log_entry);
}
void GPULogger::LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_memory) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const bool is_device_local = (memory_flags & 0x1) != 0; // VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
const bool is_host_visible = (memory_flags & 0x2) != 0; // VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
{
std::lock_guard lock(memory_mutex);
memory_allocations[memory] = {
.memory_handle = memory,
.size = size,
.memory_flags = memory_flags,
.timestamp = timestamp,
.is_device_local = is_device_local,
.is_host_visible = is_host_visible,
};
total_allocations++;
current_allocated_bytes += size;
if (current_allocated_bytes > peak_allocated_bytes) {
peak_allocated_bytes = current_allocated_bytes;
}
}
const auto log_entry = fmt::format(
"[{}] [Memory] Allocated {} at 0x{:x} (Device:{}, Host:{})\n", FormatTimestamp(timestamp),
FormatMemorySize(size), memory, is_device_local ? "Yes" : "No",
is_host_visible ? "Yes" : "No");
WriteToLog(log_entry);
}
void GPULogger::LogMemoryDeallocation(uintptr_t memory) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_memory) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
u64 size = 0;
{
std::lock_guard lock(memory_mutex);
auto it = memory_allocations.find(memory);
if (it != memory_allocations.end()) {
size = it->second.size;
current_allocated_bytes -= size;
memory_allocations.erase(it);
total_deallocations++;
}
}
if (size > 0) {
const auto log_entry =
fmt::format("[{}] [Memory] Deallocated {} at 0x{:x}\n", FormatTimestamp(timestamp),
FormatMemorySize(size), memory);
WriteToLog(log_entry);
}
}
void GPULogger::LogShaderCompilation(const std::string& shader_name,
const std::string& shader_info,
std::span<const u32> spirv_code) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!dump_shaders && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Shader] Compiled: {} ({})\n",
FormatTimestamp(timestamp), shader_name, shader_info);
WriteToLog(log_entry);
// Dump SPIR-V binary if enabled and we have data
if (dump_shaders && !spirv_code.empty()) {
using namespace Common::FS;
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
const auto shaders_dir = log_dir / "shaders";
// Create directory on first dump
if (!shader_dump_dir_created) {
[[maybe_unused]] const bool created = CreateDir(shaders_dir);
shader_dump_dir_created = true;
}
// Write SPIR-V binary file
const auto shader_path = shaders_dir / fmt::format("{}.spv", shader_name);
auto shader_file = std::make_unique<Common::FS::IOFile>(
shader_path, FileAccessMode::Write, FileType::BinaryFile);
if (shader_file->IsOpen()) {
const size_t bytes_to_write = spirv_code.size() * sizeof(u32);
static_cast<void>(shader_file->WriteSpan(spirv_code));
shader_file->Close();
const auto dump_log = fmt::format("[{}] [Shader] Dumped SPIR-V: {} ({} bytes)\n",
FormatTimestamp(timestamp), shader_path.string(), bytes_to_write);
WriteToLog(dump_log);
} else {
LOG_WARNING(Render_Vulkan, "[GPU Logging] Failed to dump shader: {}", shader_path.string());
}
}
}
void GPULogger::LogPipelineStateChange(const std::string& state_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
// Store pipeline state for crash dumps
{
std::lock_guard lock(state_mutex);
stored_pipeline_state = state_info;
}
if (current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry =
fmt::format("[{}] [Pipeline] State change: {}\n", FormatTimestamp(timestamp), state_info);
WriteToLog(log_entry);
}
void GPULogger::LogDriverDebugInfo(const std::string& debug_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
// Store driver debug info for crash dumps
{
std::lock_guard lock(state_mutex);
stored_driver_debug_info = debug_info;
}
if (!capture_driver_debug) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry =
fmt::format("[{}] [Driver] {}\n", FormatTimestamp(timestamp), debug_info);
WriteToLog(log_entry);
}
void GPULogger::LogExtensionUsage(const std::string& extension_name, const std::string& function_name) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
bool is_first_use = false;
{
std::lock_guard lock(extension_mutex);
auto [iter, inserted] = used_extensions.insert(extension_name);
is_first_use = inserted;
}
if (is_first_use) {
const auto log_entry = fmt::format("[{}] [Extension] First use of {} in {}\n",
FormatTimestamp(timestamp), extension_name, function_name);
WriteToLog(log_entry);
LOG_INFO(Render_Vulkan, "[GPU Logging] First use of extension {} in {}",
extension_name, function_name);
} else if (current_level >= LogLevel::Verbose) {
const auto log_entry = fmt::format("[{}] [Extension] {} used in {}\n",
FormatTimestamp(timestamp), extension_name, function_name);
WriteToLog(log_entry);
}
}
void GPULogger::LogRenderPassBegin(const std::string& render_pass_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [RenderPass] Begin: {}\n",
FormatTimestamp(timestamp), render_pass_info);
WriteToLog(log_entry);
}
void GPULogger::LogRenderPassEnd() {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [RenderPass] End\n", FormatTimestamp(timestamp));
WriteToLog(log_entry);
}
void GPULogger::LogPipelineBind(bool is_compute, const std::string& pipeline_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const char* pipeline_type = is_compute ? "Compute" : "Graphics";
const auto log_entry = fmt::format("[{}] [Pipeline] Bind {} pipeline: {}\n",
FormatTimestamp(timestamp), pipeline_type, pipeline_info);
WriteToLog(log_entry);
}
void GPULogger::LogDescriptorSetBind(const std::string& descriptor_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Descriptor] Bind: {}\n",
FormatTimestamp(timestamp), descriptor_info);
WriteToLog(log_entry);
}
void GPULogger::LogPipelineBarrier(const std::string& barrier_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Barrier] {}\n",
FormatTimestamp(timestamp), barrier_info);
WriteToLog(log_entry);
}
void GPULogger::LogImageOperation(const std::string& operation, const std::string& image_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Image] {}: {}\n",
FormatTimestamp(timestamp), operation, image_info);
WriteToLog(log_entry);
}
void GPULogger::LogClearOperation(const std::string& clear_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Clear] {}\n",
FormatTimestamp(timestamp), clear_info);
WriteToLog(log_entry);
}
GPUStateSnapshot GPULogger::GetCurrentSnapshot() {
GPUStateSnapshot snapshot;
snapshot.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
snapshot.driver_type = detected_driver;
// Capture recent Vulkan calls
{
std::lock_guard lock(ring_buffer_mutex);
snapshot.recent_calls.reserve(ring_buffer_size);
// Copy from current position to end
for (size_t i = ring_buffer_index; i < ring_buffer_size; ++i) {
if (!call_ring_buffer[i].call_name.empty()) {
snapshot.recent_calls.push_back(call_ring_buffer[i]);
}
}
// Copy from beginning to current position
for (size_t i = 0; i < ring_buffer_index; ++i) {
if (!call_ring_buffer[i].call_name.empty()) {
snapshot.recent_calls.push_back(call_ring_buffer[i]);
}
}
}
// Capture memory status
{
std::lock_guard lock(memory_mutex);
snapshot.memory_status = fmt::format(
"Total Allocations: {}\n"
"Current Usage: {}\n"
"Peak Usage: {}\n"
"Active Allocations: {}\n",
total_allocations, FormatMemorySize(current_allocated_bytes),
FormatMemorySize(peak_allocated_bytes), memory_allocations.size());
}
// Capture stored pipeline and driver debug info
{
std::lock_guard lock(state_mutex);
snapshot.pipeline_state = stored_pipeline_state.empty() ?
"No pipeline state logged yet" : stored_pipeline_state;
snapshot.driver_debug_info = stored_driver_debug_info.empty() ?
"No driver debug info logged yet" : stored_driver_debug_info;
}
return snapshot;
}
void GPULogger::DumpStateToFile(const std::string& crash_reason) {
using namespace Common::FS;
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
const auto crashes_dir = log_dir / "gpu_crashes";
[[maybe_unused]] const bool crashes_dir_created = CreateDir(crashes_dir);
// Generate crash dump filename with timestamp
const auto now = std::chrono::system_clock::now();
const auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(
now.time_since_epoch()).count();
const auto crash_dump_path = crashes_dir / fmt::format("crash_{}.gpu-dump", timestamp);
auto crash_file =
std::make_unique<Common::FS::IOFile>(crash_dump_path, FileAccessMode::Write, FileType::TextFile);
if (!crash_file->IsOpen()) {
LOG_ERROR(Render_Vulkan, "[GPU Logging] Failed to create crash dump file");
return;
}
auto snapshot = GetCurrentSnapshot();
const char* driver_name = "Unknown";
switch (snapshot.driver_type) {
case DriverType::Turnip:
driver_name = "Turnip (Mesa Freedreno)";
break;
case DriverType::Qualcomm:
driver_name = "Qualcomm Proprietary";
break;
default:
driver_name = "Unknown";
break;
}
// Write crash dump header
const auto header = fmt::format(
"=== GPU CRASH DUMP ===\n"
"Timestamp: {}\n"
"Reason: {}\n"
"Driver: {}\n"
"\n",
FormatTimestamp(snapshot.timestamp), crash_reason, driver_name);
static_cast<void>(crash_file->WriteString(header));
// Write recent Vulkan calls
static_cast<void>(crash_file->WriteString(fmt::format("=== RECENT VULKAN API CALLS (Last {}) ===\n",
snapshot.recent_calls.size())));
for (const auto& call : snapshot.recent_calls) {
const auto call_str =
fmt::format("[{}] [Thread:{}] {}({}) -> {}\n", FormatTimestamp(call.timestamp),
call.thread_id, call.call_name, call.parameters, call.result);
static_cast<void>(crash_file->WriteString(call_str));
}
static_cast<void>(crash_file->WriteString("\n"));
// Write memory status
static_cast<void>(crash_file->WriteString("=== MEMORY STATUS ===\n"));
static_cast<void>(crash_file->WriteString(snapshot.memory_status));
static_cast<void>(crash_file->WriteString("\n"));
// Write pipeline state
static_cast<void>(crash_file->WriteString("=== PIPELINE STATE ===\n"));
static_cast<void>(crash_file->WriteString(snapshot.pipeline_state));
static_cast<void>(crash_file->WriteString("\n"));
// Write driver debug info
static_cast<void>(crash_file->WriteString("=== DRIVER DEBUG INFO ===\n"));
static_cast<void>(crash_file->WriteString(snapshot.driver_debug_info));
static_cast<void>(crash_file->WriteString("\n"));
crash_file->Flush();
crash_file->Close();
LOG_CRITICAL(Render_Vulkan, "[GPU Logging] Crash dump written to: {}",
crash_dump_path.string());
}
void GPULogger::SetLogLevel(LogLevel level) {
current_level = level;
}
void GPULogger::EnableVulkanCallTracking(bool enabled) {
track_vulkan_calls = enabled;
}
void GPULogger::EnableShaderDumps(bool enabled) {
dump_shaders = enabled;
}
void GPULogger::EnableMemoryTracking(bool enabled) {
track_memory = enabled;
}
void GPULogger::EnableDriverDebugInfo(bool enabled) {
capture_driver_debug = enabled;
}
void GPULogger::SetRingBufferSize(size_t entries) {
std::lock_guard lock(ring_buffer_mutex);
ring_buffer_size = entries;
call_ring_buffer.resize(entries);
ring_buffer_index = 0;
}
LogLevel GPULogger::GetLogLevel() const {
return current_level;
}
DriverType GPULogger::GetDriverType() const {
return detected_driver;
}
std::string GPULogger::GetStatistics() const {
std::lock_guard lock(memory_mutex);
return fmt::format(
"Vulkan Calls: {}, Allocations: {}, Deallocations: {}, "
"Current Memory: {}, Peak Memory: {}",
total_vulkan_calls, total_allocations, total_deallocations,
FormatMemorySize(current_allocated_bytes), FormatMemorySize(peak_allocated_bytes));
}
bool GPULogger::IsInitialized() const {
return initialized;
}
void GPULogger::WriteToLog(const std::string& message) {
if (!gpu_log_file || !gpu_log_file->IsOpen()) {
return;
}
std::lock_guard lock(file_mutex);
bytes_written += gpu_log_file->WriteString(message);
// Flush on errors or if we've written a lot
using namespace Common::Literals;
if (bytes_written % (1_MiB) == 0) {
gpu_log_file->Flush();
}
}
std::string GPULogger::FormatTimestamp(std::chrono::microseconds timestamp) const {
const auto seconds = timestamp.count() / 1000000;
const auto microseconds = timestamp.count() % 1000000;
return fmt::format("{:4d}.{:06d}", seconds, microseconds);
}
std::string GPULogger::FormatMemorySize(u64 bytes) const {
using namespace Common::Literals;
if (bytes >= 1_GiB) {
return fmt::format("{:.2f} GiB", static_cast<double>(bytes) / (1_GiB));
} else if (bytes >= 1_MiB) {
return fmt::format("{:.2f} MiB", static_cast<double>(bytes) / (1_MiB));
} else if (bytes >= 1_KiB) {
return fmt::format("{:.2f} KiB", static_cast<double>(bytes) / (1_KiB));
} else {
return fmt::format("{} B", bytes);
}
}
} // namespace GPU::Logging
+199
View File
@@ -0,0 +1,199 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <chrono>
#include <memory>
#include <mutex>
#include <set>
#include <span>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
// Forward declarations
namespace Common::FS {
class IOFile;
}
namespace Vulkan {
class Device;
}
namespace GPU::Logging {
enum class LogLevel : u8 {
Off = 0,
Errors = 1,
Standard = 2,
Verbose = 3,
All = 4,
};
enum class DriverType : u8 {
Unknown,
Turnip, // Mesa Turnip driver
Qualcomm, // Qualcomm proprietary driver
};
// Ring buffer entry for tracking Vulkan API calls
struct VulkanCallEntry {
std::chrono::microseconds timestamp;
std::string call_name; // e.g., "vkCmdDraw", "vkBeginRenderPass"
std::string parameters; // Serialized parameters
int result; // VkResult return code
u32 thread_id;
};
// GPU memory allocation entry
struct MemoryAllocationEntry {
uintptr_t memory_handle;
u64 size;
u32 memory_flags;
std::chrono::microseconds timestamp;
bool is_device_local;
bool is_host_visible;
};
// GPU state snapshot for crash dumps
struct GPUStateSnapshot {
std::vector<VulkanCallEntry> recent_calls; // Last N API calls
std::vector<std::string> active_shaders; // Currently bound shaders
std::string pipeline_state; // Current pipeline state
std::string memory_status; // Current memory allocations
std::string driver_debug_info; // Driver-specific debug data
std::chrono::microseconds timestamp;
DriverType driver_type;
};
/// Main GPU logging system singleton
class GPULogger {
public:
static GPULogger& GetInstance();
// Prevent copying
GPULogger(const GPULogger&) = delete;
GPULogger& operator=(const GPULogger&) = delete;
// Initialization and control
void Initialize(LogLevel level, DriverType detected_driver = DriverType::Unknown);
void Shutdown();
// Logging API
void LogVulkanCall(const std::string& call_name, const std::string& params, int result);
void LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags);
void LogMemoryDeallocation(uintptr_t memory);
void LogShaderCompilation(const std::string& shader_name, const std::string& shader_info,
std::span<const u32> spirv_code = {});
void LogPipelineStateChange(const std::string& state_info);
void LogDriverDebugInfo(const std::string& debug_info);
// Extension usage tracking
void LogExtensionUsage(const std::string& extension_name, const std::string& function_name);
// Render pass logging
void LogRenderPassBegin(const std::string& render_pass_info);
void LogRenderPassEnd();
// Pipeline binding logging
void LogPipelineBind(bool is_compute, const std::string& pipeline_info);
// Descriptor set binding logging
void LogDescriptorSetBind(const std::string& descriptor_info);
// Pipeline barrier logging
void LogPipelineBarrier(const std::string& barrier_info);
// Image operation logging
void LogImageOperation(const std::string& operation, const std::string& image_info);
// Clear operation logging
void LogClearOperation(const std::string& clear_info);
// Crash handling
GPUStateSnapshot GetCurrentSnapshot();
void DumpStateToFile(const std::string& crash_reason);
// Settings
void SetLogLevel(LogLevel level);
void EnableVulkanCallTracking(bool enabled);
void EnableShaderDumps(bool enabled);
void EnableMemoryTracking(bool enabled);
void EnableDriverDebugInfo(bool enabled);
void SetRingBufferSize(size_t entries);
// Query
LogLevel GetLogLevel() const;
DriverType GetDriverType() const;
std::string GetStatistics() const;
bool IsInitialized() const;
private:
GPULogger();
~GPULogger();
// Helper functions
void WriteToLog(const std::string& message);
void RotateLogFile();
std::string FormatTimestamp(std::chrono::microseconds timestamp) const;
std::string FormatMemorySize(u64 bytes) const;
// State
bool initialized = false;
LogLevel current_level = LogLevel::Off;
DriverType detected_driver = DriverType::Unknown;
// Ring buffer for API calls
std::vector<VulkanCallEntry> call_ring_buffer;
size_t ring_buffer_index = 0;
size_t ring_buffer_size = 512;
mutable std::mutex ring_buffer_mutex;
// Memory tracking
std::unordered_map<uintptr_t, MemoryAllocationEntry> memory_allocations;
mutable std::mutex memory_mutex;
// Statistics
u64 total_vulkan_calls = 0;
u64 total_allocations = 0;
u64 total_deallocations = 0;
u64 current_allocated_bytes = 0;
u64 peak_allocated_bytes = 0;
// File backend for GPU logs
std::unique_ptr<Common::FS::IOFile> gpu_log_file;
mutable std::mutex file_mutex;
u64 bytes_written = 0;
// Feature flags
bool track_vulkan_calls = true;
bool dump_shaders = false;
bool track_memory = false;
bool capture_driver_debug = false;
// Extension usage tracking
std::set<std::string> used_extensions;
mutable std::mutex extension_mutex;
// Shader dump directory (created on demand)
bool shader_dump_dir_created = false;
// Stored state for crash dumps
std::string stored_driver_debug_info;
std::string stored_pipeline_state;
mutable std::mutex state_mutex;
};
// Helper to get stage name from index
inline const char* GetShaderStageName(size_t stage_index) {
static constexpr std::array<const char*, 5> stage_names{
"vertex", "tess_control", "tess_eval", "geometry", "fragment"
};
return stage_index < stage_names.size() ? stage_names[stage_index] : "unknown";
}
} // namespace GPU::Logging
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "video_core/gpu_logging/gpu_state_capture.h"
#include <fmt/format.h>
namespace GPU::Logging {
GPUStateSnapshot GPUStateCapture::CaptureState() {
return GPULogger::GetInstance().GetCurrentSnapshot();
}
std::string GPUStateCapture::SerializeState(const GPUStateSnapshot& snapshot) {
std::string result;
result += "=== GPU STATE SNAPSHOT ===\n\n";
result += fmt::format("Driver: {}\n", static_cast<int>(snapshot.driver_type));
result += fmt::format("Recent Calls: {}\n\n", snapshot.recent_calls.size());
result += "=== RECENT VULKAN CALLS ===\n";
for (const auto& call : snapshot.recent_calls) {
result += fmt::format("{}: {}({}) -> {}\n", call.timestamp.count(), call.call_name,
call.parameters, call.result);
}
result += "\n=== MEMORY STATUS ===\n";
result += snapshot.memory_status;
result += "\n=== PIPELINE STATE ===\n";
result += snapshot.pipeline_state;
result += "\n=== DRIVER DEBUG INFO ===\n";
result += snapshot.driver_debug_info;
return result;
}
void GPUStateCapture::WriteCrashDump(const std::string& crash_reason) {
GPULogger::GetInstance().DumpStateToFile(crash_reason);
}
} // namespace GPU::Logging
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string>
#include "video_core/gpu_logging/gpu_logging.h"
namespace GPU::Logging {
class GPUStateCapture {
public:
// Capture current GPU state from logging system
static GPUStateSnapshot CaptureState();
// Serialize state to human-readable format
static std::string SerializeState(const GPUStateSnapshot& snapshot);
// Write detailed crash dump (implemented in GPULogger)
static void WriteCrashDump(const std::string& crash_reason);
};
} // namespace GPU::Logging
@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "video_core/gpu_logging/qualcomm_debug.h"
#include "common/logging/log.h"
namespace GPU::Logging::Qualcomm {
bool QualcommDebugger::is_initialized = false;
void QualcommDebugger::Initialize() {
if (is_initialized) {
return;
}
is_initialized = true;
LOG_INFO(Render_Vulkan, "[Qualcomm Debug] Initialized (stub)");
}
std::string QualcommDebugger::GetDebugInfo() {
// Stub for future Qualcomm proprietary driver debug extension support
// This requires libadrenotools integration and Qualcomm-specific APIs
return "Qualcomm debug info not yet implemented";
}
} // namespace GPU::Logging::Qualcomm
@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string>
namespace GPU::Logging::Qualcomm {
class QualcommDebugger {
public:
// Initialize Qualcomm debugging (stub for future implementation)
static void Initialize();
// Get debug information from Qualcomm driver
static std::string GetDebugInfo();
private:
static bool is_initialized;
};
} // namespace GPU::Logging::Qualcomm
+1 -1
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 2019 yuzu Emulator Project
+4 -1
View File
@@ -1,3 +1,6 @@
// 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
@@ -89,7 +92,7 @@ struct CommandDataContainer {
/// Struct used to synchronize the GPU thread
struct SynchState final {
using CommandQueue = Common::MPSCQueue<CommandDataContainer>;
using CommandQueue = Common::SPSCQueue<CommandDataContainer>;
std::mutex write_lock;
CommandQueue queue;
u64 last_fence{};
+1 -10
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
set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr)
@@ -19,7 +19,6 @@ set(SHADER_FILES
block_linear_unswizzle_2d.comp
block_linear_unswizzle_3d.comp
block_linear_unswizzle_3d_bcn.comp
convert_abgr8_srgb_to_d24s8.frag
convert_abgr8_to_d24s8.frag
convert_abgr8_to_d32f.frag
convert_d32f_to_abgr8.frag
@@ -77,14 +76,6 @@ set(SHADER_FILES
vulkan_quad_indexed.comp
vulkan_turbo_mode.comp
vulkan_uint8.comp
convert_rgba8_to_bgra8.frag
convert_yuv420_to_rgb.comp
convert_rgb_to_yuv420.comp
convert_bc7_to_rgba8.comp
convert_astc_hdr_to_rgba16f.comp
convert_rgba16f_to_rgba8.frag
dither_temporal.frag
dynamic_resolution_scale.comp
)
if (PLATFORM_HAIKU)
@@ -1,46 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#version 450
#extension GL_ARB_shader_stencil_export : require
layout(binding = 0) uniform sampler2D color_texture;
// Even more accurate sRGB to linear conversion
// https://entropymine.com/imageworsener/srgbformula/
float srgbToLinear(float srgb) {
if (srgb <= 0.0404482362771082f) { //assumes it's >= 0
return srgb / 12.92;
} else {
return pow((srgb + 0.055) / 1.055, 2.4);
}
}
void main() {
ivec2 coord = ivec2(gl_FragCoord.xy);
vec4 srgbColor = texelFetch(color_texture, coord, 0);
// Convert sRGB to linear space with proper gamma correction
vec3 linearColor = vec3(
srgbToLinear(srgbColor.r),
srgbToLinear(srgbColor.g),
srgbToLinear(srgbColor.b)
);
// Use standard luminance coefficients
float luminance = dot(linearColor, vec3(0.2126, 0.7152, 0.0722));
// Ensure proper depth range
luminance = clamp(luminance, 0.0, 1.0);
// Convert to 24-bit depth value
uint depth_val = uint(luminance * float(0xFFFFFF));
// Extract 8-bit stencil from alpha
uint stencil_val = uint(srgbColor.a * 255.0);
// Pack values efficiently
uint depth_stencil = (stencil_val << 24) | (depth_val & 0x00FFFFFF);
gl_FragDepth = float(depth_val) / float(0xFFFFFF);
gl_FragStencilRefARB = int(stencil_val);
}
@@ -1,28 +0,0 @@
#version 450
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform samplerBuffer astc_data;
layout(binding = 1, rgba16f) uniform writeonly image2D output_image;
// Note: This is a simplified version. Real ASTC HDR decompression is more complex
void main() {
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(output_image);
if (pos.x >= size.x || pos.y >= size.y) {
return;
}
// Calculate block and pixel within block
ivec2 block = pos / 8; // Assuming 8x8 ASTC blocks
ivec2 pixel = pos % 8;
// Each ASTC block is 16 bytes
int block_index = block.y * (size.x / 8) + block.x;
// Simplified ASTC HDR decoding - you'll need to implement full ASTC decoding
vec4 color = texelFetch(astc_data, block_index * 8 + pixel.y * 8 + pixel.x);
imageStore(output_image, pos, color);
}
@@ -1,29 +0,0 @@
#version 450
#extension GL_ARB_shader_ballot : require
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform samplerBuffer bc7_data;
layout(binding = 1, rgba8) uniform writeonly image2D output_image;
// Note: This is a simplified version. Real BC7 decompression is more complex
void main() {
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(output_image);
if (pos.x >= size.x || pos.y >= size.y) {
return;
}
// Calculate block and pixel within block
ivec2 block = pos / 4;
ivec2 pixel = pos % 4;
// Each BC7 block is 16 bytes
int block_index = block.y * (size.x / 4) + block.x;
// Simplified BC7 decoding - you'll need to implement full BC7 decoding
vec4 color = texelFetch(bc7_data, block_index * 4 + pixel.y * 4 + pixel.x);
imageStore(output_image, pos, color);
}
@@ -1,29 +0,0 @@
#version 450
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform sampler2D input_texture;
layout(binding = 1, r8) uniform writeonly image2D y_output;
layout(binding = 2, r8) uniform writeonly image2D u_output;
layout(binding = 3, r8) uniform writeonly image2D v_output;
void main() {
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(y_output);
if (pos.x >= size.x || pos.y >= size.y) {
return;
}
vec2 tex_coord = vec2(pos) / vec2(size);
vec3 rgb = texture(input_texture, tex_coord).rgb;
// RGB to YUV conversion
float y = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b;
float u = -0.147 * rgb.r - 0.289 * rgb.g + 0.436 * rgb.b + 0.5;
float v = 0.615 * rgb.r - 0.515 * rgb.g - 0.100 * rgb.b + 0.5;
imageStore(y_output, pos, vec4(y));
imageStore(u_output, pos / 2, vec4(u));
imageStore(v_output, pos / 2, vec4(v));
}
@@ -1,31 +0,0 @@
#version 450
layout(location = 0) in vec2 texcoord;
layout(location = 0) out vec4 color;
layout(binding = 0) uniform sampler2D input_texture;
layout(push_constant) uniform PushConstants {
float exposure;
float gamma;
} constants;
vec3 tonemap(vec3 hdr) {
// Reinhard tonemapping
return hdr / (hdr + vec3(1.0));
}
void main() {
vec4 hdr = texture(input_texture, texcoord);
// Apply exposure
vec3 exposed = hdr.rgb * constants.exposure;
// Tonemap
vec3 tonemapped = tonemap(exposed);
// Gamma correction
vec3 gamma_corrected = pow(tonemapped, vec3(1.0 / constants.gamma));
color = vec4(gamma_corrected, hdr.a);
}
@@ -1,11 +0,0 @@
#version 450
layout(location = 0) in vec2 texcoord;
layout(location = 0) out vec4 color;
layout(binding = 0) uniform sampler2D input_texture;
void main() {
vec4 rgba = texture(input_texture, texcoord);
color = rgba.bgra; // Swap red and blue channels
}
@@ -1,30 +0,0 @@
#version 450
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform sampler2D y_texture;
layout(binding = 1) uniform sampler2D u_texture;
layout(binding = 2) uniform sampler2D v_texture;
layout(binding = 3, rgba8) uniform writeonly image2D output_image;
void main() {
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(output_image);
if (pos.x >= size.x || pos.y >= size.y) {
return;
}
vec2 tex_coord = vec2(pos) / vec2(size);
float y = texture(y_texture, tex_coord).r;
float u = texture(u_texture, tex_coord).r - 0.5;
float v = texture(v_texture, tex_coord).r - 0.5;
// YUV to RGB conversion
vec3 rgb;
rgb.r = y + 1.402 * v;
rgb.g = y - 0.344 * u - 0.714 * v;
rgb.b = y + 1.772 * u;
imageStore(output_image, pos, vec4(rgb, 1.0));
}
@@ -1,29 +0,0 @@
#version 450
layout(location = 0) in vec2 texcoord;
layout(location = 0) out vec4 color;
layout(binding = 0) uniform sampler2D input_texture;
layout(push_constant) uniform PushConstants {
float frame_count;
float dither_strength;
} constants;
// Pseudo-random number generator
float rand(vec2 co) {
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}
void main() {
vec4 input_color = texture(input_texture, texcoord);
// Generate temporal noise based on frame count
vec2 noise_coord = gl_FragCoord.xy + vec2(constants.frame_count);
float noise = rand(noise_coord) * 2.0 - 1.0;
// Apply dithering
vec3 dithered = input_color.rgb + noise * constants.dither_strength;
color = vec4(dithered, input_color.a);
}
@@ -1,68 +0,0 @@
#version 450
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform sampler2D input_texture;
layout(binding = 1, rgba8) uniform writeonly image2D output_image;
layout(push_constant) uniform PushConstants {
vec2 scale_factor;
vec2 input_size;
} constants;
vec4 cubic(float v) {
vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
vec4 s = n * n * n;
float x = s.x;
float y = s.y - 4.0 * s.x;
float z = s.z - 4.0 * s.y + 6.0 * s.x;
float w = s.w - 4.0 * s.z + 6.0 * s.y - 4.0 * s.x;
return vec4(x, y, z, w) * (1.0/6.0);
}
vec4 bicubic_sample(sampler2D tex, vec2 tex_coord) {
vec2 tex_size = constants.input_size;
vec2 inv_tex_size = 1.0 / tex_size;
tex_coord = tex_coord * tex_size - 0.5;
vec2 fxy = fract(tex_coord);
tex_coord -= fxy;
vec4 xcubic = cubic(fxy.x);
vec4 ycubic = cubic(fxy.y);
vec4 c = tex_coord.xxyy + vec2(-0.5, +1.5).xyxy;
vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s;
offset *= inv_tex_size.xxyy;
vec4 sample0 = texture(tex, offset.xz);
vec4 sample1 = texture(tex, offset.yz);
vec4 sample2 = texture(tex, offset.xw);
vec4 sample3 = texture(tex, offset.yw);
float sx = s.x / (s.x + s.y);
float sy = s.z / (s.z + s.w);
return mix(
mix(sample3, sample2, sx),
mix(sample1, sample0, sx),
sy
);
}
void main() {
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(output_image);
if (pos.x >= size.x || pos.y >= size.y) {
return;
}
vec2 tex_coord = vec2(pos) / vec2(size);
vec4 color = bicubic_sample(input_texture, tex_coord);
imageStore(output_image, pos, color);
}
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+1 -98
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 2021 yuzu Emulator Project
@@ -31,15 +31,6 @@
#include "video_core/surface.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/host_shaders/convert_abgr8_srgb_to_d24s8_frag_spv.h"
#include "video_core/host_shaders/convert_rgba8_to_bgra8_frag_spv.h"
#include "video_core/host_shaders/convert_yuv420_to_rgb_comp_spv.h"
#include "video_core/host_shaders/convert_rgb_to_yuv420_comp_spv.h"
#include "video_core/host_shaders/convert_bc7_to_rgba8_comp_spv.h"
#include "video_core/host_shaders/convert_astc_hdr_to_rgba16f_comp_spv.h"
#include "video_core/host_shaders/convert_rgba16f_to_rgba8_frag_spv.h"
#include "video_core/host_shaders/dither_temporal_frag_spv.h"
#include "video_core/host_shaders/dynamic_resolution_scale_comp_spv.h"
namespace Vulkan {
@@ -540,17 +531,6 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
convert_abgr8_srgb_to_d24s8_frag(device.IsExtShaderStencilExportSupported()
? BuildShader(device, CONVERT_ABGR8_SRGB_TO_D24S8_FRAG_SPV)
: vk::ShaderModule{}),
convert_rgba_to_bgra_frag(BuildShader(device, CONVERT_RGBA8_TO_BGRA8_FRAG_SPV)),
convert_yuv420_to_rgb_comp(BuildShader(device, CONVERT_YUV420_TO_RGB_COMP_SPV)),
convert_rgb_to_yuv420_comp(BuildShader(device, CONVERT_RGB_TO_YUV420_COMP_SPV)),
convert_bc7_to_rgba8_comp(BuildShader(device, CONVERT_BC7_TO_RGBA8_COMP_SPV)),
convert_astc_hdr_to_rgba16f_comp(BuildShader(device, CONVERT_ASTC_HDR_TO_RGBA16F_COMP_SPV)),
convert_rgba16f_to_rgba8_frag(BuildShader(device, CONVERT_RGBA16F_TO_RGBA8_FRAG_SPV)),
dither_temporal_frag(BuildShader(device, DITHER_TEMPORAL_FRAG_SPV)),
dynamic_resolution_scale_comp(BuildShader(device, DYNAMIC_RESOLUTION_SCALE_COMP_SPV)),
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_NEAREST>)) {}
@@ -711,19 +691,6 @@ void BlitImageHelper::ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer,
ConvertDepthStencil(*convert_s8d24_to_abgr8_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ConvertABGR8SRGBToD24S8(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
if (!device.IsExtShaderStencilExportSupported()) {
// Shader requires VK_EXT_shader_stencil_export which is not available
LOG_WARNING(Render_Vulkan, "ConvertABGR8SRGBToD24S8 requires shader_stencil_export, skipping");
return;
}
ConvertPipelineDepthTargetEx(convert_abgr8_srgb_to_d24s8_pipeline,
dst_framebuffer->RenderPass(),
convert_abgr8_srgb_to_d24s8_frag);
Convert(*convert_abgr8_srgb_to_d24s8_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ClearColor(const Framebuffer* dst_framebuffer, u8 color_mask,
const std::array<f32, 4>& clear_color,
const Region2D& dst_region) {
@@ -1192,68 +1159,4 @@ void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass rende
});
}
void BlitImageHelper::ConvertRGBAtoGBRA(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
ConvertPipeline(convert_rgba_to_bgra_pipeline,
dst_framebuffer->RenderPass(),
false);
Convert(*convert_rgba_to_bgra_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ConvertYUV420toRGB(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
ConvertPipeline(convert_yuv420_to_rgb_pipeline,
dst_framebuffer->RenderPass(),
false);
Convert(*convert_yuv420_to_rgb_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ConvertRGBtoYUV420(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
ConvertPipeline(convert_rgb_to_yuv420_pipeline,
dst_framebuffer->RenderPass(),
false);
Convert(*convert_rgb_to_yuv420_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ConvertBC7toRGBA8(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
ConvertPipeline(convert_bc7_to_rgba8_pipeline,
dst_framebuffer->RenderPass(),
false);
Convert(*convert_bc7_to_rgba8_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ConvertASTCHDRtoRGBA16F(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
ConvertPipeline(convert_astc_hdr_to_rgba16f_pipeline,
dst_framebuffer->RenderPass(),
false);
Convert(*convert_astc_hdr_to_rgba16f_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ConvertRGBA16FtoRGBA8(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
ConvertPipeline(convert_rgba16f_to_rgba8_pipeline,
dst_framebuffer->RenderPass(),
false);
Convert(*convert_rgba16f_to_rgba8_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ApplyDitherTemporal(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
ConvertPipeline(dither_temporal_pipeline,
dst_framebuffer->RenderPass(),
false);
Convert(*dither_temporal_pipeline, dst_framebuffer, src_image_view);
}
void BlitImageHelper::ApplyDynamicResolutionScale(const Framebuffer* dst_framebuffer,
const ImageView& src_image_view) {
ConvertPipeline(dynamic_resolution_scale_pipeline,
dst_framebuffer->RenderPass(),
false);
Convert(*dynamic_resolution_scale_pipeline, dst_framebuffer, src_image_view);
}
} // namespace Vulkan

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