diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index b7ae56f9b4..40e75acff7 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -72,7 +72,14 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { USE_LRU_CACHE("use_lru_cache"), DONT_SHOW_DRIVER_SHADER_WARNING("dont_show_driver_shader_warning"), - ENABLE_OVERLAY("enable_overlay"); + ENABLE_OVERLAY("enable_overlay"), + + // GPU Logging + GPU_LOGGING_ENABLED("gpu_logging_enabled"), + GPU_LOG_VULKAN_CALLS("gpu_log_vulkan_calls"), + GPU_LOG_SHADER_DUMPS("gpu_log_shader_dumps"), + GPU_LOG_MEMORY_TRACKING("gpu_log_memory_tracking"), + GPU_LOG_DRIVER_DEBUG("gpu_log_driver_debug"); // external fun isFrameSkippingEnabled(): Boolean diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt index 245fe9c9ca..aa4a939952 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt @@ -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) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index 96db3cf7c0..be3b2f4a48 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -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) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 8396672ba3..afa76362ff 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -836,6 +836,62 @@ abstract class SettingsItem( ) ) + // GPU Logging settings + put( + SwitchSetting( + BooleanSetting.GPU_LOGGING_ENABLED, + titleId = R.string.gpu_logging_enabled, + descriptionId = R.string.gpu_logging_enabled_description + ) + ) + put( + SingleChoiceSetting( + ByteSetting.GPU_LOG_LEVEL, + titleId = R.string.gpu_log_level, + descriptionId = R.string.gpu_log_level_description, + choicesId = R.array.gpuLogLevelEntries, + valuesId = R.array.gpuLogLevelValues + ) + ) + put( + SwitchSetting( + BooleanSetting.GPU_LOG_VULKAN_CALLS, + titleId = R.string.gpu_log_vulkan_calls, + descriptionId = R.string.gpu_log_vulkan_calls_description + ) + ) + put( + SwitchSetting( + BooleanSetting.GPU_LOG_SHADER_DUMPS, + titleId = R.string.gpu_log_shader_dumps, + descriptionId = R.string.gpu_log_shader_dumps_description + ) + ) + put( + SwitchSetting( + BooleanSetting.GPU_LOG_MEMORY_TRACKING, + titleId = R.string.gpu_log_memory_tracking, + descriptionId = R.string.gpu_log_memory_tracking_description + ) + ) + put( + SwitchSetting( + BooleanSetting.GPU_LOG_DRIVER_DEBUG, + titleId = R.string.gpu_log_driver_debug, + descriptionId = R.string.gpu_log_driver_debug_description + ) + ) + put( + SpinBoxSetting( + IntSetting.GPU_LOG_RING_BUFFER_SIZE, + titleId = R.string.gpu_log_ring_buffer_size, + descriptionId = R.string.gpu_log_ring_buffer_size_description, + valueHint = R.string.gpu_log_ring_buffer_size_hint, + min = 64, + max = 4096 + ) + ) + val fastmem = object : AbstractBooleanSetting { override fun getBoolean(needsGlobal: Boolean): Boolean = BooleanSetting.FASTMEM.getBoolean() && diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 27222c85af..b6cb7acf1e 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1220,6 +1220,15 @@ class SettingsFragmentPresenter( add(HeaderSetting(R.string.general)) add(IntSetting.DEBUG_KNOBS.key) + + add(HeaderSetting(R.string.gpu_logging_header)) + add(BooleanSetting.GPU_LOGGING_ENABLED.key) + add(ByteSetting.GPU_LOG_LEVEL.key) + add(BooleanSetting.GPU_LOG_VULKAN_CALLS.key) + add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key) + add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key) + add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key) + add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key) } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt index 6cb35014b4..464572e777 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt @@ -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()) diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 3e77d82267..69f1590844 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -631,4 +631,21 @@ @string/error_keys_invalid_filename @string/error_keys_failed_init + + + + Off + Errors Only + Standard + Verbose + All + + + + 0 + 1 + 2 + 3 + 4 + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index ec4f9a2cf4..3740a54639 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -325,6 +325,9 @@ Share debug logs Share Eden\'s log file to debug issues No log file found + Share GPU logs + Share Eden\'s GPU log file to debug graphics issues + No GPU log file found Install game content Install game updates or DLC Installing content… @@ -552,6 +555,26 @@ Flush debug logs by line Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing. + + GPU Logging + GPU Logging + Enable GPU Logging + Log GPU operations to eden_gpu.log for debugging Adreno drivers + Log Level + Detail level for GPU logs (higher = more detail, more overhead) + Logging Features + Log Vulkan API Calls + Track all Vulkan API calls in ring buffer + Dump Shaders + Save compiled shader SPIR-V to files + Track GPU Memory + Monitor GPU memory allocations and deallocations + Driver Debug Info + Capture driver-specific debug information (Turnip breadcrumbs, etc.) + Ring Buffer Size + Number of recent Vulkan calls to track (default: 512) + 64 to 4096 entries + General diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 2c88356888..1c28adafe5 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -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); diff --git a/src/common/settings.h b/src/common/settings.h index d5bf9f7660..09c05a812a 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -738,6 +738,18 @@ struct Values { Setting perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging}; Setting disable_web_applet{linkage, true, "disable_web_applet", Category::Debugging}; + // GPU Logging + Setting gpu_logging_enabled{linkage, true, "gpu_logging_enabled", Category::Debugging}; + SwitchableSetting gpu_log_level{linkage, GpuLogLevel::Standard, "gpu_log_level", + Category::Debugging}; + Setting gpu_log_vulkan_calls{linkage, true, "gpu_log_vulkan_calls", Category::Debugging}; + Setting gpu_log_shader_dumps{linkage, false, "gpu_log_shader_dumps", Category::Debugging}; + Setting gpu_log_memory_tracking{linkage, true, "gpu_log_memory_tracking", + Category::Debugging}; + Setting gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging}; + Setting gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size", + Category::Debugging}; + SwitchableSetting debug_knobs{linkage, 0, 0, diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 7d8d3f40d4..30d075565b 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project @@ -154,6 +154,7 @@ ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High) ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High) ENUM(TemperatureUnits, Celsius, Fahrenheit) ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3); +ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All) template inline std::string_view CanonicalizeEnum(Type id) { diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index c94b66e6bc..ed77ae8934 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -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) diff --git a/src/video_core/gpu_logging/CMakeLists.txt b/src/video_core/gpu_logging/CMakeLists.txt new file mode 100644 index 0000000000..9a9f1e5f8d --- /dev/null +++ b/src/video_core/gpu_logging/CMakeLists.txt @@ -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() diff --git a/src/video_core/gpu_logging/freedreno_debug.cpp b/src/video_core/gpu_logging/freedreno_debug.cpp new file mode 100644 index 0000000000..14ffe04f78 --- /dev/null +++ b/src/video_core/gpu_logging/freedreno_debug.cpp @@ -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 + +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 diff --git a/src/video_core/gpu_logging/freedreno_debug.h b/src/video_core/gpu_logging/freedreno_debug.h new file mode 100644 index 0000000000..feb57a0b2a --- /dev/null +++ b/src/video_core/gpu_logging/freedreno_debug.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#ifdef ANDROID + +#include + +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 diff --git a/src/video_core/gpu_logging/gpu_logging.cpp b/src/video_core/gpu_logging/gpu_logging.cpp new file mode 100644 index 0000000000..059e8a6c06 --- /dev/null +++ b/src/video_core/gpu_logging/gpu_logging.cpp @@ -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 +#include + +#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( + 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::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::steady_clock::now().time_since_epoch()); + const auto thread_id = static_cast(std::hash{}(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::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::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 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::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( + shader_path, FileAccessMode::Write, FileType::BinaryFile); + + if (shader_file->IsOpen()) { + const size_t bytes_to_write = spirv_code.size() * sizeof(u32); + static_cast(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::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::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::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::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::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::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::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::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::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::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::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( + now.time_since_epoch()).count(); + const auto crash_dump_path = crashes_dir / fmt::format("crash_{}.gpu-dump", timestamp); + + auto crash_file = + std::make_unique(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(crash_file->WriteString(header)); + + // Write recent Vulkan calls + static_cast(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(crash_file->WriteString(call_str)); + } + static_cast(crash_file->WriteString("\n")); + + // Write memory status + static_cast(crash_file->WriteString("=== MEMORY STATUS ===\n")); + static_cast(crash_file->WriteString(snapshot.memory_status)); + static_cast(crash_file->WriteString("\n")); + + // Write pipeline state + static_cast(crash_file->WriteString("=== PIPELINE STATE ===\n")); + static_cast(crash_file->WriteString(snapshot.pipeline_state)); + static_cast(crash_file->WriteString("\n")); + + // Write driver debug info + static_cast(crash_file->WriteString("=== DRIVER DEBUG INFO ===\n")); + static_cast(crash_file->WriteString(snapshot.driver_debug_info)); + static_cast(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(bytes) / (1_GiB)); + } else if (bytes >= 1_MiB) { + return fmt::format("{:.2f} MiB", static_cast(bytes) / (1_MiB)); + } else if (bytes >= 1_KiB) { + return fmt::format("{:.2f} KiB", static_cast(bytes) / (1_KiB)); + } else { + return fmt::format("{} B", bytes); + } +} + +} // namespace GPU::Logging diff --git a/src/video_core/gpu_logging/gpu_logging.h b/src/video_core/gpu_logging/gpu_logging.h new file mode 100644 index 0000000000..c248e9b036 --- /dev/null +++ b/src/video_core/gpu_logging/gpu_logging.h @@ -0,0 +1,199 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 recent_calls; // Last N API calls + std::vector 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 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 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 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 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 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 stage_names{ + "vertex", "tess_control", "tess_eval", "geometry", "fragment" + }; + return stage_index < stage_names.size() ? stage_names[stage_index] : "unknown"; +} + +} // namespace GPU::Logging diff --git a/src/video_core/gpu_logging/gpu_state_capture.cpp b/src/video_core/gpu_logging/gpu_state_capture.cpp new file mode 100644 index 0000000000..19e655a71b --- /dev/null +++ b/src/video_core/gpu_logging/gpu_state_capture.cpp @@ -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 + +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(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 diff --git a/src/video_core/gpu_logging/gpu_state_capture.h b/src/video_core/gpu_logging/gpu_state_capture.h new file mode 100644 index 0000000000..aca06785ed --- /dev/null +++ b/src/video_core/gpu_logging/gpu_state_capture.h @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#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 diff --git a/src/video_core/gpu_logging/qualcomm_debug.cpp b/src/video_core/gpu_logging/qualcomm_debug.cpp new file mode 100644 index 0000000000..4862bc2349 --- /dev/null +++ b/src/video_core/gpu_logging/qualcomm_debug.cpp @@ -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 diff --git a/src/video_core/gpu_logging/qualcomm_debug.h b/src/video_core/gpu_logging/qualcomm_debug.h new file mode 100644 index 0000000000..c54ae5ed0c --- /dev/null +++ b/src/video_core/gpu_logging/qualcomm_debug.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +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 diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp index 2d9c5d4148..96a9fe59e7 100644 --- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -8,6 +8,7 @@ #include #include +#include #include "video_core/renderer_vulkan/pipeline_helper.h" #include "video_core/renderer_vulkan/pipeline_statistics.h" @@ -20,6 +21,8 @@ #include "video_core/shader_notify.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" +#include "video_core/gpu_logging/gpu_logging.h" +#include "common/settings.h" namespace Vulkan { @@ -82,6 +85,13 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel }, *pipeline_cache); + // Log compute pipeline creation + if (Settings::values.gpu_logging_enabled.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogPipelineStateChange( + "ComputePipeline created" + ); + } + if (pipeline_statistics) { pipeline_statistics->Collect(*pipeline); } @@ -207,6 +217,13 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute, build_condvar.wait(lock, [this] { return is_built.load(std::memory_order::relaxed); }); }); } + + // Log compute pipeline binding + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_vulkan_calls.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogPipelineBind(true, "compute pipeline"); + } + const void* const descriptor_data{guest_descriptor_queue.UpdateData()}; const bool is_rescaling = !info.texture_descriptors.empty() || !info.image_descriptors.empty(); scheduler.Record([this, descriptor_data, is_rescaling, diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp index fca235b58e..d36553da4a 100644 --- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project @@ -10,6 +10,7 @@ #include #include +#include #include "video_core/renderer_vulkan/pipeline_helper.h" @@ -25,6 +26,8 @@ #include "video_core/shader_notify.h" #include "video_core/texture_cache/texture_cache.h" #include "video_core/vulkan_common/vulkan_device.h" +#include "video_core/gpu_logging/gpu_logging.h" +#include "common/settings.h" #if defined(_MSC_VER) && defined(NDEBUG) #define LAMBDA_FORCEINLINE [[msvc::forceinline]] @@ -513,6 +516,14 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling, const bool is_rescaling{texture_cache.IsRescaling()}; const bool update_rescaling{scheduler.UpdateRescaling(is_rescaling)}; const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)}; + + // Log graphics pipeline binding + if (bind_pipeline && Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_vulkan_calls.GetValue()) { + const std::string pipeline_info = fmt::format("hash=0x{:016x}", key.Hash()); + GPU::Logging::GPULogger::GetInstance().LogPipelineBind(false, pipeline_info); + } + const void* const descriptor_data{guest_descriptor_queue.UpdateData()}; scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(), is_rescaling, update_rescaling, @@ -954,6 +965,16 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) { .basePipelineIndex = 0, }, *pipeline_cache); + + // Log graphics pipeline creation + if (Settings::values.gpu_logging_enabled.GetValue()) { + const std::string pipeline_info = fmt::format( + "GraphicsPipeline created: stages={}, attachments={}", + shader_stages.size(), + color_blend_ci.attachmentCount + ); + GPU::Logging::GPULogger::GetInstance().LogPipelineStateChange(pipeline_info); + } } void GraphicsPipeline::Validate() { diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 6207e127c1..f3dd0f90d8 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ #include "video_core/surface.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" +#include "video_core/gpu_logging/gpu_logging.h" namespace Vulkan { @@ -724,6 +726,17 @@ std::unique_ptr PipelineCache::CreateGraphicsPipeline( const std::vector code{EmitSPIRV(profile, runtime_info, program, binding, this->optimize_spirv_output)}; device.SaveShader(code); modules[stage_index] = BuildShader(device, code); + + // Log shader compilation to GPU logger (with SPIR-V binary dump if enabled) + if (Settings::values.gpu_logging_enabled.GetValue()) { + static constexpr std::array stage_names{"vertex", "tess_control", "tess_eval", "geometry", "fragment"}; + const std::string shader_name = fmt::format("shader_{:016x}_{}", key.unique_hashes[index], stage_names[stage_index]); + const std::string shader_info = fmt::format("SPIR-V size: {} bytes, hash: {:016x}", + code.size() * sizeof(u32), key.unique_hashes[index]); + GPU::Logging::GPULogger::GetInstance().LogShaderCompilation(shader_name, shader_info, + std::span(code.data(), code.size())); + } + if (device.HasDebuggingToolAttached()) { const std::string name{fmt::format("Shader {:016x}", key.unique_hashes[index])}; modules[stage_index].SetObjectNameEXT(name.c_str()); @@ -831,6 +844,16 @@ std::unique_ptr PipelineCache::CreateComputePipeline( const std::vector code{EmitSPIRV(profile, program, this->optimize_spirv_output)}; device.SaveShader(code); vk::ShaderModule spv_module{BuildShader(device, code)}; + + // Log compute shader compilation to GPU logger (with SPIR-V binary dump if enabled) + if (Settings::values.gpu_logging_enabled.GetValue()) { + const std::string shader_name = fmt::format("shader_{:016x}_compute", key.unique_hash); + const std::string shader_info = fmt::format("SPIR-V size: {} bytes, hash: {:016x}", + code.size() * sizeof(u32), key.unique_hash); + GPU::Logging::GPULogger::GetInstance().LogShaderCompilation(shader_name, shader_info, + std::span(code.data(), code.size())); + } + if (device.HasDebuggingToolAttached()) { const auto name{fmt::format("Shader {:016x}", key.unique_hash)}; spv_module.SetObjectNameEXT(name.c_str()); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index c3a5ed391b..60b899a811 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -9,6 +9,8 @@ #include #include +#include + #include "video_core/renderer_vulkan/renderer_vulkan.h" #include "common/assert.h" @@ -16,6 +18,7 @@ #include "common/scope_exit.h" #include "common/settings.h" #include "video_core/buffer_cache/buffer_cache.h" +#include "video_core/gpu_logging/gpu_logging.h" #include "video_core/control/channel_state.h" #include "video_core/engines/draw_manager.h" #include "video_core/engines/kepler_compute.h" @@ -277,6 +280,20 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) { } }); } + + // Log draw call + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_vulkan_calls.GetValue()) { + const std::string params = is_indexed ? + fmt::format("vertices={}, instances={}, firstIndex={}, baseVertex={}, baseInstance={}", + draw_params.num_vertices, draw_params.num_instances, + draw_params.first_index, draw_params.base_vertex, draw_params.base_instance) : + fmt::format("vertices={}, instances={}, firstVertex={}, firstInstance={}", + draw_params.num_vertices, draw_params.num_instances, + draw_params.base_vertex, draw_params.base_instance); + GPU::Logging::GPULogger::GetInstance().LogVulkanCall( + is_indexed ? "vkCmdDrawIndexed" : "vkCmdDraw", params, VK_SUCCESS); + } }); } @@ -324,6 +341,16 @@ void RasterizerVulkan::DrawIndirect() { static_cast(params.stride)); } }); + + // Log indirect draw call + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_vulkan_calls.GetValue()) { + const std::string log_params = fmt::format("drawCount={}, stride={}", + params.max_draw_counts, params.stride); + GPU::Logging::GPULogger::GetInstance().LogVulkanCall( + params.is_indexed ? "vkCmdDrawIndexedIndirect" : "vkCmdDrawIndirect", + log_params, VK_SUCCESS); + } }); buffer_cache.SetDrawIndirect(nullptr); } @@ -568,6 +595,15 @@ void RasterizerVulkan::DispatchCompute() { scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, READ_BARRIER); }); scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); }); + + // Log compute dispatch + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_vulkan_calls.GetValue()) { + const std::string params = fmt::format("groupCountX={}, groupCountY={}, groupCountZ={}", + dim[0], dim[1], dim[2]); + GPU::Logging::GPULogger::GetInstance().LogVulkanCall( + "vkCmdDispatch", params, VK_SUCCESS); + } } void RasterizerVulkan::ResetCounter(VideoCommon::QueryType type) { @@ -1066,6 +1102,11 @@ void RasterizerVulkan::HandleTransformFeedback() { query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount, regs.transform_feedback_enabled); if (regs.transform_feedback_enabled != 0) { + // Log extension usage for transform feedback + if (Settings::values.gpu_logging_enabled.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( + "VK_EXT_transform_feedback", "HandleTransformFeedback"); + } UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) || regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation)); } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index e19029812b..e526d606dc 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -9,9 +9,13 @@ #include #include +#include + #include "video_core/renderer_vulkan/vk_query_cache.h" +#include "common/settings.h" #include "common/thread.h" +#include "video_core/gpu_logging/gpu_logging.h" #include "video_core/renderer_vulkan/vk_command_pool.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_master_semaphore.h" @@ -114,6 +118,15 @@ void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) { state.framebuffer = framebuffer_handle; state.render_area = render_area; + // Log render pass begin + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_vulkan_calls.GetValue()) { + const std::string render_pass_info = fmt::format( + "renderArea={}x{}, numImages={}", + render_area.width, render_area.height, framebuffer->NumImages()); + GPU::Logging::GPULogger::GetInstance().LogRenderPassBegin(render_pass_info); + } + Record([renderpass, framebuffer_handle, render_area](vk::CommandBuffer cmdbuf) { const VkRenderPassBeginInfo renderpass_bi{ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, @@ -270,6 +283,12 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se switch (const VkResult result = master_semaphore->SubmitQueue( cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, signal_value)) { case VK_SUCCESS: + // Log successful queue submission + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_vulkan_calls.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogVulkanCall( + "vkQueueSubmit", "", VK_SUCCESS); + } break; case VK_ERROR_DEVICE_LOST: device.ReportLoss(); @@ -305,6 +324,12 @@ void Scheduler::EndRenderPass() return; } + // Log render pass end + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_vulkan_calls.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogRenderPassEnd(); + } + query_cache->CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, false); query_cache->NotifySegment(false); diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 6ce7433240..39a43d5950 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -16,6 +16,7 @@ #include "common/settings.h" #include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/gpu_logging/gpu_logging.h" #include "video_core/engines/fermi_2d.h" #include "video_core/renderer_vulkan/blit_image.h" @@ -2304,6 +2305,11 @@ Sampler::Sampler(TextureCacheRuntime& runtime, const Tegra::Texture::TSCEntry& t const void* pnext = nullptr; if (has_custom_border_colors) { pnext = &border_ci; + // Log extension usage for custom border color + if (Settings::values.gpu_logging_enabled.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogExtensionUsage( + "VK_EXT_custom_border_color", "Sampler::Sampler"); + } } const VkSamplerReductionModeCreateInfoEXT reduction_ci{ .sType = VK_STRUCTURE_TYPE_SAMPLER_REDUCTION_MODE_CREATE_INFO_EXT, diff --git a/src/video_core/vulkan_common/vulkan_debug_callback.cpp b/src/video_core/vulkan_common/vulkan_debug_callback.cpp index 448df2d3ab..cd6653c86e 100644 --- a/src/video_core/vulkan_common/vulkan_debug_callback.cpp +++ b/src/video_core/vulkan_common/vulkan_debug_callback.cpp @@ -1,12 +1,26 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later #include #include "common/logging/log.h" +#include "common/settings.h" #include "video_core/vulkan_common/vulkan_debug_callback.h" +#include "video_core/gpu_logging/gpu_logging.h" namespace Vulkan { namespace { + +// Helper to get message type as string for GPU logging +const char* GetMessageTypeName(VkDebugUtilsMessageTypeFlagsEXT type) { + if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { + return "Validation"; + } else if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) { + return "Performance"; + } else { + return "General"; + } +} + VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, const VkDebugUtilsMessengerCallbackDataEXT* data, @@ -60,6 +74,28 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { LOG_DEBUG(Render_Vulkan, "{}", message); } + + // Route to GPU logger for tracking Vulkan validation messages + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_vulkan_calls.GetValue()) { + // Convert severity to result code for logging (negative = error) + int result_code = 0; + if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + result_code = -1; + } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + result_code = -2; + } + + // Get message ID name or use generic name + const char* call_name = data->pMessageIdName ? data->pMessageIdName : "VulkanDebug"; + + GPU::Logging::GPULogger::GetInstance().LogVulkanCall( + call_name, + std::string(GetMessageTypeName(type)) + ": " + std::string(message), + result_code + ); + } + return VK_FALSE; } diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 8c596fe347..0b3aea6d2e 100644 --- a/src/video_core/vulkan_common/vulkan_device.cpp +++ b/src/video_core/vulkan_common/vulkan_device.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include "common/assert.h" #include "common/literals.h" #include @@ -22,6 +24,7 @@ #include "video_core/vulkan_common/vma.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_wrapper.h" +#include "video_core/gpu_logging/gpu_logging.h" #if defined(ANDROID) && defined(ARCHITECTURE_arm64) #include @@ -734,9 +737,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR }; vk::Check(vmaCreateAllocator(&allocator_info, &allocator)); + + // Initialize GPU logging if enabled + InitializeGPULogging(); } Device::~Device() { + ShutdownGPULogging(); vmaDestroyAllocator(allocator); } @@ -1622,4 +1629,106 @@ std::vector Device::GetDeviceQueueCreateInfos() const { return queue_cis; } +void Device::InitializeGPULogging() { + if (!Settings::values.gpu_logging_enabled.GetValue()) { + return; + } + + // Detect driver type + const auto driver_id = GetDriverID(); + GPU::Logging::DriverType detected_driver = GPU::Logging::DriverType::Unknown; + + if (driver_id == VK_DRIVER_ID_MESA_TURNIP) { + detected_driver = GPU::Logging::DriverType::Turnip; + } else if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) { + detected_driver = GPU::Logging::DriverType::Qualcomm; + } + + // Get log level from settings + const auto log_level = static_cast( + static_cast(Settings::values.gpu_log_level.GetValue())); + + // Initialize GPU logger + GPU::Logging::GPULogger::GetInstance().Initialize(log_level, detected_driver); + + // Configure feature flags + GPU::Logging::GPULogger::GetInstance().EnableVulkanCallTracking( + Settings::values.gpu_log_vulkan_calls.GetValue()); + GPU::Logging::GPULogger::GetInstance().EnableShaderDumps( + Settings::values.gpu_log_shader_dumps.GetValue()); + GPU::Logging::GPULogger::GetInstance().EnableMemoryTracking( + Settings::values.gpu_log_memory_tracking.GetValue()); + GPU::Logging::GPULogger::GetInstance().EnableDriverDebugInfo( + Settings::values.gpu_log_driver_debug.GetValue()); + GPU::Logging::GPULogger::GetInstance().SetRingBufferSize( + Settings::values.gpu_log_ring_buffer_size.GetValue()); + + // Log comprehensive driver and extension information + if (Settings::values.gpu_log_driver_debug.GetValue()) { + std::string driver_info; + + // Device information + const auto& props = properties.properties; + driver_info += fmt::format("Device: {}\n", props.deviceName); + driver_info += fmt::format("Driver Name: {}\n", properties.driver.driverName); + driver_info += fmt::format("Driver Info: {}\n", properties.driver.driverInfo); + + // Version information + const u32 driver_version = props.driverVersion; + const u32 api_version = props.apiVersion; + driver_info += fmt::format("Driver Version: {}.{}.{}\n", + VK_API_VERSION_MAJOR(driver_version), + VK_API_VERSION_MINOR(driver_version), + VK_API_VERSION_PATCH(driver_version)); + driver_info += fmt::format("Vulkan API Version: {}.{}.{}\n", + VK_API_VERSION_MAJOR(api_version), + VK_API_VERSION_MINOR(api_version), + VK_API_VERSION_PATCH(api_version)); + driver_info += fmt::format("Driver ID: {}\n", static_cast(driver_id)); + + // Vendor and device IDs + driver_info += fmt::format("Vendor ID: 0x{:04X}\n", props.vendorID); + driver_info += fmt::format("Device ID: 0x{:04X}\n", props.deviceID); + + // Extensions - separate QCOM extensions from others + driver_info += "\n=== Loaded Vulkan Extensions ===\n"; + std::vector qcom_exts; + std::vector other_exts; + + for (const auto& ext : loaded_extensions) { + if (ext.find("QCOM") != std::string::npos || ext.find("qcom") != std::string::npos) { + qcom_exts.push_back(ext); + } else { + other_exts.push_back(ext); + } + } + + // Log QCOM extensions first + if (!qcom_exts.empty()) { + driver_info += "\nQualcomm Proprietary Extensions:\n"; + for (const auto& ext : qcom_exts) { + driver_info += fmt::format(" - {}\n", ext); + } + } + + // Log other extensions + if (!other_exts.empty()) { + driver_info += "\nStandard Extensions:\n"; + for (const auto& ext : other_exts) { + driver_info += fmt::format(" - {}\n", ext); + } + } + + driver_info += fmt::format("\nTotal Extensions Loaded: {}\n", loaded_extensions.size()); + + GPU::Logging::GPULogger::GetInstance().LogDriverDebugInfo(driver_info); + } +} + +void Device::ShutdownGPULogging() { + if (GPU::Logging::GPULogger::GetInstance().IsInitialized()) { + GPU::Logging::GPULogger::GetInstance().Shutdown(); + } +} + } // namespace Vulkan diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h index acb2954104..744b5f827e 100644 --- a/src/video_core/vulkan_common/vulkan_device.h +++ b/src/video_core/vulkan_common/vulkan_device.h @@ -929,6 +929,10 @@ public: return nvidia_arch; } + /// GPU logging integration + void InitializeGPULogging(); + void ShutdownGPULogging(); + private: /// Checks if the physical device is suitable and configures the object state /// with all necessary info about its properties. diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp index 4cd3442d97..ab93f256c0 100644 --- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp +++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -22,6 +22,8 @@ #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h" #include "video_core/vulkan_common/vulkan_wrapper.h" +#include "video_core/gpu_logging/gpu_logging.h" +#include "common/settings.h" namespace Vulkan { namespace { @@ -107,7 +109,17 @@ namespace Vulkan { MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a, const VmaAllocationInfo &info) noexcept : allocator{alloc}, allocation{a}, memory{info.deviceMemory}, - offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {} + offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} { + // Log GPU memory allocation + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_memory_tracking.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation( + reinterpret_cast(memory), + static_cast(size), + 0 // Memory property flags (not easily available from VMA) + ); + } + } MemoryCommit::~MemoryCommit() { Release(); } @@ -166,6 +178,15 @@ namespace Vulkan { void MemoryCommit::Release() { if (allocation && allocator) { + // Log GPU memory deallocation + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_memory_tracking.GetValue() && + memory != VK_NULL_HANDLE) { + GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation( + reinterpret_cast(memory) + ); + } + if (mapped_ptr) { vmaUnmapMemory(allocator, allocation); mapped_ptr = nullptr; @@ -218,7 +239,19 @@ namespace Vulkan { VkImage handle{}; VmaAllocation allocation{}; - vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr)); + VmaAllocationInfo alloc_info{}; + vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info)); + + // Log GPU memory allocation for images + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_memory_tracking.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation( + reinterpret_cast(alloc_info.deviceMemory), + static_cast(alloc_info.size), + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT + ); + } + return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation, device.GetDispatchLoader()); } @@ -245,6 +278,16 @@ namespace Vulkan { vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info)); vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags); + // Log GPU memory allocation for buffers + if (Settings::values.gpu_logging_enabled.GetValue() && + Settings::values.gpu_log_memory_tracking.GetValue()) { + GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation( + reinterpret_cast(alloc_info.deviceMemory), + static_cast(alloc_info.size), + property_flags + ); + } + u8 *data = reinterpret_cast(alloc_info.pMappedData); const std::span mapped_data = data ? std::span{data, ci.size} : std::span{}; const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;