mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-02-04 02:51:18 +01:00
[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>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() &&
|
||||
|
||||
@@ -1220,6 +1220,15 @@ class SettingsFragmentPresenter(
|
||||
add(HeaderSetting(R.string.general))
|
||||
|
||||
add(IntSetting.DEBUG_KNOBS.key)
|
||||
|
||||
add(HeaderSetting(R.string.gpu_logging_header))
|
||||
add(BooleanSetting.GPU_LOGGING_ENABLED.key)
|
||||
add(ByteSetting.GPU_LOG_LEVEL.key)
|
||||
add(BooleanSetting.GPU_LOG_VULKAN_CALLS.key)
|
||||
add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key)
|
||||
add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key)
|
||||
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key)
|
||||
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
@@ -222,6 +222,14 @@ class HomeSettingsFragment : Fragment() {
|
||||
{ shareLog() }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.share_gpu_log,
|
||||
R.string.share_gpu_log_description,
|
||||
R.drawable.ic_log,
|
||||
{ shareGpuLog() }
|
||||
)
|
||||
)
|
||||
add(
|
||||
HomeSetting(
|
||||
R.string.open_user_folder,
|
||||
@@ -408,6 +416,40 @@ class HomeSettingsFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareGpuLog() {
|
||||
val currentLog = DocumentFile.fromSingleUri(
|
||||
mainActivity,
|
||||
DocumentsContract.buildDocumentUri(
|
||||
DocumentProvider.AUTHORITY,
|
||||
"${DocumentProvider.ROOT_ID}/log/eden_gpu.log"
|
||||
)
|
||||
)!!
|
||||
val oldLog = DocumentFile.fromSingleUri(
|
||||
mainActivity,
|
||||
DocumentsContract.buildDocumentUri(
|
||||
DocumentProvider.AUTHORITY,
|
||||
"${DocumentProvider.ROOT_ID}/log/eden_gpu.log.old.txt"
|
||||
)
|
||||
)!!
|
||||
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
.setDataAndType(currentLog.uri, FileUtil.TEXT_PLAIN)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
if (!Log.gameLaunched && oldLog.exists()) {
|
||||
intent.putExtra(Intent.EXTRA_STREAM, oldLog.uri)
|
||||
startActivity(Intent.createChooser(intent, getText(R.string.share_gpu_log)))
|
||||
} else if (currentLog.exists()) {
|
||||
intent.putExtra(Intent.EXTRA_STREAM, currentLog.uri)
|
||||
startActivity(Intent.createChooser(intent, getText(R.string.share_gpu_log)))
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
getText(R.string.share_gpu_log_missing),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setInsets() =
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
|
||||
@@ -631,4 +631,21 @@
|
||||
<item>@string/error_keys_invalid_filename</item>
|
||||
<item>@string/error_keys_failed_init</item>
|
||||
</string-array>
|
||||
|
||||
<!-- GPU Logging Arrays -->
|
||||
<string-array name="gpuLogLevelEntries">
|
||||
<item>Off</item>
|
||||
<item>Errors Only</item>
|
||||
<item>Standard</item>
|
||||
<item>Verbose</item>
|
||||
<item>All</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="gpuLogLevelValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</integer-array>
|
||||
</resources>
|
||||
|
||||
@@ -325,6 +325,9 @@
|
||||
<string name="share_log">Share debug logs</string>
|
||||
<string name="share_log_description">Share Eden\'s log file to debug issues</string>
|
||||
<string name="share_log_missing">No log file found</string>
|
||||
<string name="share_gpu_log">Share GPU logs</string>
|
||||
<string name="share_gpu_log_description">Share Eden\'s GPU log file to debug graphics issues</string>
|
||||
<string name="share_gpu_log_missing">No GPU log file found</string>
|
||||
<string name="install_game_content">Install game content</string>
|
||||
<string name="install_game_content_description">Install game updates or DLC</string>
|
||||
<string name="installing_game_content">Installing content…</string>
|
||||
@@ -552,6 +555,26 @@
|
||||
<string name="flush_by_line">Flush debug logs by line</string>
|
||||
<string name="flush_by_line_description">Flushes debugging logs on each line written, making debugging easier in cases of crashing or freezing.</string>
|
||||
|
||||
<!-- GPU Logging strings -->
|
||||
<string name="gpu_logging">GPU Logging</string>
|
||||
<string name="gpu_logging_header">GPU Logging</string>
|
||||
<string name="gpu_logging_enabled">Enable GPU Logging</string>
|
||||
<string name="gpu_logging_enabled_description">Log GPU operations to eden_gpu.log for debugging Adreno drivers</string>
|
||||
<string name="gpu_log_level">Log Level</string>
|
||||
<string name="gpu_log_level_description">Detail level for GPU logs (higher = more detail, more overhead)</string>
|
||||
<string name="gpu_logging_features">Logging Features</string>
|
||||
<string name="gpu_log_vulkan_calls">Log Vulkan API Calls</string>
|
||||
<string name="gpu_log_vulkan_calls_description">Track all Vulkan API calls in ring buffer</string>
|
||||
<string name="gpu_log_shader_dumps">Dump Shaders</string>
|
||||
<string name="gpu_log_shader_dumps_description">Save compiled shader SPIR-V to files</string>
|
||||
<string name="gpu_log_memory_tracking">Track GPU Memory</string>
|
||||
<string name="gpu_log_memory_tracking_description">Monitor GPU memory allocations and deallocations</string>
|
||||
<string name="gpu_log_driver_debug">Driver Debug Info</string>
|
||||
<string name="gpu_log_driver_debug_description">Capture driver-specific debug information (Turnip breadcrumbs, etc.)</string>
|
||||
<string name="gpu_log_ring_buffer_size">Ring Buffer Size</string>
|
||||
<string name="gpu_log_ring_buffer_size_description">Number of recent Vulkan calls to track (default: 512)</string>
|
||||
<string name="gpu_log_ring_buffer_size_hint">64 to 4096 entries</string>
|
||||
|
||||
<string name="general">General</string>
|
||||
|
||||
<!-- Audio settings strings -->
|
||||
|
||||
@@ -49,6 +49,7 @@ SWITCHABLE(CpuBackend, true);
|
||||
SWITCHABLE(CpuAccuracy, true);
|
||||
SWITCHABLE(FullscreenMode, true);
|
||||
SWITCHABLE(GpuAccuracy, true);
|
||||
SWITCHABLE(GpuLogLevel, true);
|
||||
SWITCHABLE(Language, true);
|
||||
SWITCHABLE(MemoryLayout, true);
|
||||
SWITCHABLE(NvdecEmulation, false);
|
||||
|
||||
@@ -738,6 +738,18 @@ struct Values {
|
||||
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
|
||||
Setting<bool> disable_web_applet{linkage, true, "disable_web_applet", Category::Debugging};
|
||||
|
||||
// GPU Logging
|
||||
Setting<bool> gpu_logging_enabled{linkage, true, "gpu_logging_enabled", Category::Debugging};
|
||||
SwitchableSetting<GpuLogLevel> gpu_log_level{linkage, GpuLogLevel::Standard, "gpu_log_level",
|
||||
Category::Debugging};
|
||||
Setting<bool> gpu_log_vulkan_calls{linkage, true, "gpu_log_vulkan_calls", Category::Debugging};
|
||||
Setting<bool> gpu_log_shader_dumps{linkage, false, "gpu_log_shader_dumps", Category::Debugging};
|
||||
Setting<bool> gpu_log_memory_tracking{linkage, true, "gpu_log_memory_tracking",
|
||||
Category::Debugging};
|
||||
Setting<bool> gpu_log_driver_debug{linkage, true, "gpu_log_driver_debug", Category::Debugging};
|
||||
Setting<s32> gpu_log_ring_buffer_size{linkage, 512, "gpu_log_ring_buffer_size",
|
||||
Category::Debugging};
|
||||
|
||||
SwitchableSetting<u16, true> debug_knobs{linkage,
|
||||
0,
|
||||
0,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Torzu Emulator Project
|
||||
@@ -154,6 +154,7 @@ ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High)
|
||||
ENUM(TemperatureUnits, Celsius, Fahrenheit)
|
||||
ENUM(ExtendedDynamicState, Disabled, EDS1, EDS2, EDS3);
|
||||
ENUM(GpuLogLevel, Off, Errors, Standard, Verbose, All)
|
||||
|
||||
template <typename Type>
|
||||
inline std::string_view CanonicalizeEnum(Type id) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
25
src/video_core/gpu_logging/CMakeLists.txt
Normal file
25
src/video_core/gpu_logging/CMakeLists.txt
Normal 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()
|
||||
52
src/video_core/gpu_logging/freedreno_debug.cpp
Normal file
52
src/video_core/gpu_logging/freedreno_debug.cpp
Normal file
@@ -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
|
||||
32
src/video_core/gpu_logging/freedreno_debug.h
Normal file
32
src/video_core/gpu_logging/freedreno_debug.h
Normal file
@@ -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
src/video_core/gpu_logging/gpu_logging.cpp
Normal file
734
src/video_core/gpu_logging/gpu_logging.cpp
Normal 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
src/video_core/gpu_logging/gpu_logging.h
Normal file
199
src/video_core/gpu_logging/gpu_logging.h
Normal 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
|
||||
43
src/video_core/gpu_logging/gpu_state_capture.cpp
Normal file
43
src/video_core/gpu_logging/gpu_state_capture.cpp
Normal file
@@ -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
|
||||
23
src/video_core/gpu_logging/gpu_state_capture.h
Normal file
23
src/video_core/gpu_logging/gpu_state_capture.h
Normal file
@@ -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
|
||||
26
src/video_core/gpu_logging/qualcomm_debug.cpp
Normal file
26
src/video_core/gpu_logging/qualcomm_debug.cpp
Normal file
@@ -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
|
||||
22
src/video_core/gpu_logging/qualcomm_debug.h
Normal file
22
src/video_core/gpu_logging/qualcomm_debug.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace GPU::Logging::Qualcomm {
|
||||
|
||||
class QualcommDebugger {
|
||||
public:
|
||||
// Initialize Qualcomm debugging (stub for future implementation)
|
||||
static void Initialize();
|
||||
|
||||
// Get debug information from Qualcomm driver
|
||||
static std::string GetDebugInfo();
|
||||
|
||||
private:
|
||||
static bool is_initialized;
|
||||
};
|
||||
|
||||
} // namespace GPU::Logging::Qualcomm
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "video_core/renderer_vulkan/pipeline_helper.h"
|
||||
#include "video_core/renderer_vulkan/pipeline_statistics.h"
|
||||
@@ -20,6 +21,8 @@
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
@@ -82,6 +85,13 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
|
||||
},
|
||||
*pipeline_cache);
|
||||
|
||||
// Log compute pipeline creation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogPipelineStateChange(
|
||||
"ComputePipeline created"
|
||||
);
|
||||
}
|
||||
|
||||
if (pipeline_statistics) {
|
||||
pipeline_statistics->Collect(*pipeline);
|
||||
}
|
||||
@@ -207,6 +217,13 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
|
||||
build_condvar.wait(lock, [this] { return is_built.load(std::memory_order::relaxed); });
|
||||
});
|
||||
}
|
||||
|
||||
// Log compute pipeline binding
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogPipelineBind(true, "compute pipeline");
|
||||
}
|
||||
|
||||
const void* const descriptor_data{guest_descriptor_queue.UpdateData()};
|
||||
const bool is_rescaling = !info.texture_descriptors.empty() || !info.image_descriptors.empty();
|
||||
scheduler.Record([this, descriptor_data, is_rescaling,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "video_core/renderer_vulkan/pipeline_helper.h"
|
||||
|
||||
@@ -25,6 +26,8 @@
|
||||
#include "video_core/shader_notify.h"
|
||||
#include "video_core/texture_cache/texture_cache.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#if defined(_MSC_VER) && defined(NDEBUG)
|
||||
#define LAMBDA_FORCEINLINE [[msvc::forceinline]]
|
||||
@@ -513,6 +516,14 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
|
||||
const bool is_rescaling{texture_cache.IsRescaling()};
|
||||
const bool update_rescaling{scheduler.UpdateRescaling(is_rescaling)};
|
||||
const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)};
|
||||
|
||||
// Log graphics pipeline binding
|
||||
if (bind_pipeline && Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
const std::string pipeline_info = fmt::format("hash=0x{:016x}", key.Hash());
|
||||
GPU::Logging::GPULogger::GetInstance().LogPipelineBind(false, pipeline_info);
|
||||
}
|
||||
|
||||
const void* const descriptor_data{guest_descriptor_queue.UpdateData()};
|
||||
scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(),
|
||||
is_rescaling, update_rescaling,
|
||||
@@ -954,6 +965,16 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.basePipelineIndex = 0,
|
||||
},
|
||||
*pipeline_cache);
|
||||
|
||||
// Log graphics pipeline creation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
const std::string pipeline_info = fmt::format(
|
||||
"GraphicsPipeline created: stages={}, attachments={}",
|
||||
shader_stages.size(),
|
||||
color_blend_ci.attachmentCount
|
||||
);
|
||||
GPU::Logging::GPULogger::GetInstance().LogPipelineStateChange(pipeline_info);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphicsPipeline::Validate() {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <bit>
|
||||
@@ -42,6 +43,7 @@
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
@@ -724,6 +726,17 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
|
||||
const std::vector<u32> code{EmitSPIRV(profile, runtime_info, program, binding, this->optimize_spirv_output)};
|
||||
device.SaveShader(code);
|
||||
modules[stage_index] = BuildShader(device, code);
|
||||
|
||||
// Log shader compilation to GPU logger (with SPIR-V binary dump if enabled)
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
static constexpr std::array stage_names{"vertex", "tess_control", "tess_eval", "geometry", "fragment"};
|
||||
const std::string shader_name = fmt::format("shader_{:016x}_{}", key.unique_hashes[index], stage_names[stage_index]);
|
||||
const std::string shader_info = fmt::format("SPIR-V size: {} bytes, hash: {:016x}",
|
||||
code.size() * sizeof(u32), key.unique_hashes[index]);
|
||||
GPU::Logging::GPULogger::GetInstance().LogShaderCompilation(shader_name, shader_info,
|
||||
std::span<const u32>(code.data(), code.size()));
|
||||
}
|
||||
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
const std::string name{fmt::format("Shader {:016x}", key.unique_hashes[index])};
|
||||
modules[stage_index].SetObjectNameEXT(name.c_str());
|
||||
@@ -831,6 +844,16 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
|
||||
const std::vector<u32> code{EmitSPIRV(profile, program, this->optimize_spirv_output)};
|
||||
device.SaveShader(code);
|
||||
vk::ShaderModule spv_module{BuildShader(device, code)};
|
||||
|
||||
// Log compute shader compilation to GPU logger (with SPIR-V binary dump if enabled)
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
const std::string shader_name = fmt::format("shader_{:016x}_compute", key.unique_hash);
|
||||
const std::string shader_info = fmt::format("SPIR-V size: {} bytes, hash: {:016x}",
|
||||
code.size() * sizeof(u32), key.unique_hash);
|
||||
GPU::Logging::GPULogger::GetInstance().LogShaderCompilation(shader_name, shader_info,
|
||||
std::span<const u32>(code.data(), code.size()));
|
||||
}
|
||||
|
||||
if (device.HasDebuggingToolAttached()) {
|
||||
const auto name{fmt::format("Shader {:016x}", key.unique_hash)};
|
||||
spv_module.SetObjectNameEXT(name.c_str());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
@@ -16,6 +18,7 @@
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/buffer_cache/buffer_cache.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "video_core/control/channel_state.h"
|
||||
#include "video_core/engines/draw_manager.h"
|
||||
#include "video_core/engines/kepler_compute.h"
|
||||
@@ -277,6 +280,20 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Log draw call
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
const std::string params = is_indexed ?
|
||||
fmt::format("vertices={}, instances={}, firstIndex={}, baseVertex={}, baseInstance={}",
|
||||
draw_params.num_vertices, draw_params.num_instances,
|
||||
draw_params.first_index, draw_params.base_vertex, draw_params.base_instance) :
|
||||
fmt::format("vertices={}, instances={}, firstVertex={}, firstInstance={}",
|
||||
draw_params.num_vertices, draw_params.num_instances,
|
||||
draw_params.base_vertex, draw_params.base_instance);
|
||||
GPU::Logging::GPULogger::GetInstance().LogVulkanCall(
|
||||
is_indexed ? "vkCmdDrawIndexed" : "vkCmdDraw", params, VK_SUCCESS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -324,6 +341,16 @@ void RasterizerVulkan::DrawIndirect() {
|
||||
static_cast<u32>(params.stride));
|
||||
}
|
||||
});
|
||||
|
||||
// Log indirect draw call
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
const std::string log_params = fmt::format("drawCount={}, stride={}",
|
||||
params.max_draw_counts, params.stride);
|
||||
GPU::Logging::GPULogger::GetInstance().LogVulkanCall(
|
||||
params.is_indexed ? "vkCmdDrawIndexedIndirect" : "vkCmdDrawIndirect",
|
||||
log_params, VK_SUCCESS);
|
||||
}
|
||||
});
|
||||
buffer_cache.SetDrawIndirect(nullptr);
|
||||
}
|
||||
@@ -568,6 +595,15 @@ void RasterizerVulkan::DispatchCompute() {
|
||||
scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
0, READ_BARRIER); });
|
||||
scheduler.Record([dim](vk::CommandBuffer cmdbuf) { cmdbuf.Dispatch(dim[0], dim[1], dim[2]); });
|
||||
|
||||
// Log compute dispatch
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
const std::string params = fmt::format("groupCountX={}, groupCountY={}, groupCountZ={}",
|
||||
dim[0], dim[1], dim[2]);
|
||||
GPU::Logging::GPULogger::GetInstance().LogVulkanCall(
|
||||
"vkCmdDispatch", params, VK_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::ResetCounter(VideoCommon::QueryType type) {
|
||||
@@ -1066,6 +1102,11 @@ void RasterizerVulkan::HandleTransformFeedback() {
|
||||
query_cache.CounterEnable(VideoCommon::QueryType::StreamingByteCount,
|
||||
regs.transform_feedback_enabled);
|
||||
if (regs.transform_feedback_enabled != 0) {
|
||||
// Log extension usage for transform feedback
|
||||
if (Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogExtensionUsage(
|
||||
"VK_EXT_transform_feedback", "HandleTransformFeedback");
|
||||
}
|
||||
UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) ||
|
||||
regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -9,9 +9,13 @@
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_query_cache.h"
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "video_core/renderer_vulkan/vk_command_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||
@@ -114,6 +118,15 @@ void Scheduler::RequestRenderpass(const Framebuffer* framebuffer) {
|
||||
state.framebuffer = framebuffer_handle;
|
||||
state.render_area = render_area;
|
||||
|
||||
// Log render pass begin
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
const std::string render_pass_info = fmt::format(
|
||||
"renderArea={}x{}, numImages={}",
|
||||
render_area.width, render_area.height, framebuffer->NumImages());
|
||||
GPU::Logging::GPULogger::GetInstance().LogRenderPassBegin(render_pass_info);
|
||||
}
|
||||
|
||||
Record([renderpass, framebuffer_handle, render_area](vk::CommandBuffer cmdbuf) {
|
||||
const VkRenderPassBeginInfo renderpass_bi{
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
@@ -270,6 +283,12 @@ u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_se
|
||||
switch (const VkResult result = master_semaphore->SubmitQueue(
|
||||
cmdbuf, upload_cmdbuf, signal_semaphore, wait_semaphore, signal_value)) {
|
||||
case VK_SUCCESS:
|
||||
// Log successful queue submission
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogVulkanCall(
|
||||
"vkQueueSubmit", "", VK_SUCCESS);
|
||||
}
|
||||
break;
|
||||
case VK_ERROR_DEVICE_LOST:
|
||||
device.ReportLoss();
|
||||
@@ -305,6 +324,12 @@ void Scheduler::EndRenderPass()
|
||||
return;
|
||||
}
|
||||
|
||||
// Log render pass end
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogRenderPassEnd();
|
||||
}
|
||||
|
||||
query_cache->CounterEnable(VideoCommon::QueryType::ZPassPixelCount64, false);
|
||||
query_cache->NotifySegment(false);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "common/settings.h"
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_texture_cache.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/renderer_vulkan/blit_image.h"
|
||||
@@ -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,
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <string_view>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/vulkan_common/vulkan_debug_callback.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
namespace Vulkan {
|
||||
namespace {
|
||||
|
||||
// Helper to get message type as string for GPU logging
|
||||
const char* GetMessageTypeName(VkDebugUtilsMessageTypeFlagsEXT type) {
|
||||
if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) {
|
||||
return "Validation";
|
||||
} else if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) {
|
||||
return "Performance";
|
||||
} else {
|
||||
return "General";
|
||||
}
|
||||
}
|
||||
|
||||
VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT type,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* data,
|
||||
@@ -60,6 +74,28 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
} else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) {
|
||||
LOG_DEBUG(Render_Vulkan, "{}", message);
|
||||
}
|
||||
|
||||
// Route to GPU logger for tracking Vulkan validation messages
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue()) {
|
||||
// Convert severity to result code for logging (negative = error)
|
||||
int result_code = 0;
|
||||
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
||||
result_code = -1;
|
||||
} else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
|
||||
result_code = -2;
|
||||
}
|
||||
|
||||
// Get message ID name or use generic name
|
||||
const char* call_name = data->pMessageIdName ? data->pMessageIdName : "VulkanDebug";
|
||||
|
||||
GPU::Logging::GPULogger::GetInstance().LogVulkanCall(
|
||||
call_name,
|
||||
std::string(GetMessageTypeName(type)) + ": " + std::string(message),
|
||||
result_code
|
||||
);
|
||||
}
|
||||
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/literals.h"
|
||||
#include <ranges>
|
||||
@@ -22,6 +24,7 @@
|
||||
#include "video_core/vulkan_common/vma.h"
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
#include <adrenotools/bcenabler.h>
|
||||
@@ -734,9 +737,13 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
||||
};
|
||||
|
||||
vk::Check(vmaCreateAllocator(&allocator_info, &allocator));
|
||||
|
||||
// Initialize GPU logging if enabled
|
||||
InitializeGPULogging();
|
||||
}
|
||||
|
||||
Device::~Device() {
|
||||
ShutdownGPULogging();
|
||||
vmaDestroyAllocator(allocator);
|
||||
}
|
||||
|
||||
@@ -1622,4 +1629,106 @@ std::vector<VkDeviceQueueCreateInfo> Device::GetDeviceQueueCreateInfos() const {
|
||||
return queue_cis;
|
||||
}
|
||||
|
||||
void Device::InitializeGPULogging() {
|
||||
if (!Settings::values.gpu_logging_enabled.GetValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect driver type
|
||||
const auto driver_id = GetDriverID();
|
||||
GPU::Logging::DriverType detected_driver = GPU::Logging::DriverType::Unknown;
|
||||
|
||||
if (driver_id == VK_DRIVER_ID_MESA_TURNIP) {
|
||||
detected_driver = GPU::Logging::DriverType::Turnip;
|
||||
} else if (driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
|
||||
detected_driver = GPU::Logging::DriverType::Qualcomm;
|
||||
}
|
||||
|
||||
// Get log level from settings
|
||||
const auto log_level = static_cast<GPU::Logging::LogLevel>(
|
||||
static_cast<u32>(Settings::values.gpu_log_level.GetValue()));
|
||||
|
||||
// Initialize GPU logger
|
||||
GPU::Logging::GPULogger::GetInstance().Initialize(log_level, detected_driver);
|
||||
|
||||
// Configure feature flags
|
||||
GPU::Logging::GPULogger::GetInstance().EnableVulkanCallTracking(
|
||||
Settings::values.gpu_log_vulkan_calls.GetValue());
|
||||
GPU::Logging::GPULogger::GetInstance().EnableShaderDumps(
|
||||
Settings::values.gpu_log_shader_dumps.GetValue());
|
||||
GPU::Logging::GPULogger::GetInstance().EnableMemoryTracking(
|
||||
Settings::values.gpu_log_memory_tracking.GetValue());
|
||||
GPU::Logging::GPULogger::GetInstance().EnableDriverDebugInfo(
|
||||
Settings::values.gpu_log_driver_debug.GetValue());
|
||||
GPU::Logging::GPULogger::GetInstance().SetRingBufferSize(
|
||||
Settings::values.gpu_log_ring_buffer_size.GetValue());
|
||||
|
||||
// Log comprehensive driver and extension information
|
||||
if (Settings::values.gpu_log_driver_debug.GetValue()) {
|
||||
std::string driver_info;
|
||||
|
||||
// Device information
|
||||
const auto& props = properties.properties;
|
||||
driver_info += fmt::format("Device: {}\n", props.deviceName);
|
||||
driver_info += fmt::format("Driver Name: {}\n", properties.driver.driverName);
|
||||
driver_info += fmt::format("Driver Info: {}\n", properties.driver.driverInfo);
|
||||
|
||||
// Version information
|
||||
const u32 driver_version = props.driverVersion;
|
||||
const u32 api_version = props.apiVersion;
|
||||
driver_info += fmt::format("Driver Version: {}.{}.{}\n",
|
||||
VK_API_VERSION_MAJOR(driver_version),
|
||||
VK_API_VERSION_MINOR(driver_version),
|
||||
VK_API_VERSION_PATCH(driver_version));
|
||||
driver_info += fmt::format("Vulkan API Version: {}.{}.{}\n",
|
||||
VK_API_VERSION_MAJOR(api_version),
|
||||
VK_API_VERSION_MINOR(api_version),
|
||||
VK_API_VERSION_PATCH(api_version));
|
||||
driver_info += fmt::format("Driver ID: {}\n", static_cast<u32>(driver_id));
|
||||
|
||||
// Vendor and device IDs
|
||||
driver_info += fmt::format("Vendor ID: 0x{:04X}\n", props.vendorID);
|
||||
driver_info += fmt::format("Device ID: 0x{:04X}\n", props.deviceID);
|
||||
|
||||
// Extensions - separate QCOM extensions from others
|
||||
driver_info += "\n=== Loaded Vulkan Extensions ===\n";
|
||||
std::vector<std::string> qcom_exts;
|
||||
std::vector<std::string> other_exts;
|
||||
|
||||
for (const auto& ext : loaded_extensions) {
|
||||
if (ext.find("QCOM") != std::string::npos || ext.find("qcom") != std::string::npos) {
|
||||
qcom_exts.push_back(ext);
|
||||
} else {
|
||||
other_exts.push_back(ext);
|
||||
}
|
||||
}
|
||||
|
||||
// Log QCOM extensions first
|
||||
if (!qcom_exts.empty()) {
|
||||
driver_info += "\nQualcomm Proprietary Extensions:\n";
|
||||
for (const auto& ext : qcom_exts) {
|
||||
driver_info += fmt::format(" - {}\n", ext);
|
||||
}
|
||||
}
|
||||
|
||||
// Log other extensions
|
||||
if (!other_exts.empty()) {
|
||||
driver_info += "\nStandard Extensions:\n";
|
||||
for (const auto& ext : other_exts) {
|
||||
driver_info += fmt::format(" - {}\n", ext);
|
||||
}
|
||||
}
|
||||
|
||||
driver_info += fmt::format("\nTotal Extensions Loaded: {}\n", loaded_extensions.size());
|
||||
|
||||
GPU::Logging::GPULogger::GetInstance().LogDriverDebugInfo(driver_info);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::ShutdownGPULogging() {
|
||||
if (GPU::Logging::GPULogger::GetInstance().IsInitialized()) {
|
||||
GPU::Logging::GPULogger::GetInstance().Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
@@ -929,6 +929,10 @@ public:
|
||||
return nvidia_arch;
|
||||
}
|
||||
|
||||
/// GPU logging integration
|
||||
void InitializeGPULogging();
|
||||
void ShutdownGPULogging();
|
||||
|
||||
private:
|
||||
/// Checks if the physical device is suitable and configures the object state
|
||||
/// with all necessary info about its properties.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "video_core/vulkan_common/vulkan_device.h"
|
||||
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
|
||||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
namespace Vulkan {
|
||||
namespace {
|
||||
@@ -107,7 +109,17 @@ namespace Vulkan {
|
||||
MemoryCommit::MemoryCommit(VmaAllocator alloc, VmaAllocation a,
|
||||
const VmaAllocationInfo &info) noexcept
|
||||
: allocator{alloc}, allocation{a}, memory{info.deviceMemory},
|
||||
offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {}
|
||||
offset{info.offset}, size{info.size}, mapped_ptr{info.pMappedData} {
|
||||
// Log GPU memory allocation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_memory_tracking.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
|
||||
reinterpret_cast<uintptr_t>(memory),
|
||||
static_cast<u64>(size),
|
||||
0 // Memory property flags (not easily available from VMA)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryCommit::~MemoryCommit() { Release(); }
|
||||
|
||||
@@ -166,6 +178,15 @@ namespace Vulkan {
|
||||
|
||||
void MemoryCommit::Release() {
|
||||
if (allocation && allocator) {
|
||||
// Log GPU memory deallocation
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_memory_tracking.GetValue() &&
|
||||
memory != VK_NULL_HANDLE) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogMemoryDeallocation(
|
||||
reinterpret_cast<uintptr_t>(memory)
|
||||
);
|
||||
}
|
||||
|
||||
if (mapped_ptr) {
|
||||
vmaUnmapMemory(allocator, allocation);
|
||||
mapped_ptr = nullptr;
|
||||
@@ -218,7 +239,19 @@ namespace Vulkan {
|
||||
|
||||
VkImage handle{};
|
||||
VmaAllocation allocation{};
|
||||
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
|
||||
VmaAllocationInfo alloc_info{};
|
||||
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
|
||||
|
||||
// Log GPU memory allocation for images
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_memory_tracking.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
|
||||
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
|
||||
static_cast<u64>(alloc_info.size),
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
|
||||
);
|
||||
}
|
||||
|
||||
return vk::Image(handle, ci.usage, *device.GetLogical(), allocator, allocation,
|
||||
device.GetDispatchLoader());
|
||||
}
|
||||
@@ -245,6 +278,16 @@ namespace Vulkan {
|
||||
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
|
||||
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
|
||||
|
||||
// Log GPU memory allocation for buffers
|
||||
if (Settings::values.gpu_logging_enabled.GetValue() &&
|
||||
Settings::values.gpu_log_memory_tracking.GetValue()) {
|
||||
GPU::Logging::GPULogger::GetInstance().LogMemoryAllocation(
|
||||
reinterpret_cast<uintptr_t>(alloc_info.deviceMemory),
|
||||
static_cast<u64>(alloc_info.size),
|
||||
property_flags
|
||||
);
|
||||
}
|
||||
|
||||
u8 *data = reinterpret_cast<u8 *>(alloc_info.pMappedData);
|
||||
const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{};
|
||||
const bool is_coherent = (property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
|
||||
|
||||
Reference in New Issue
Block a user