[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:
MrPurple666 2026-02-01 02:02:23 +01:00 committed by crueter
parent 8118557c17
commit 6637810fe6
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
31 changed files with 1669 additions and 17 deletions

View file

@ -0,0 +1,25 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
add_library(gpu_logging STATIC
gpu_logging.cpp
gpu_logging.h
gpu_state_capture.cpp
gpu_state_capture.h
qualcomm_debug.cpp
qualcomm_debug.h
)
if(ANDROID)
target_sources(gpu_logging PRIVATE
freedreno_debug.cpp
freedreno_debug.h
)
endif()
target_link_libraries(gpu_logging PUBLIC common)
if(ANDROID)
# Link with adrenotools when available for future Qualcomm integration
# target_link_libraries(gpu_logging PUBLIC adrenotools)
endif()

View 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

View 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

View file

@ -0,0 +1,734 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "video_core/gpu_logging/gpu_logging.h"
#include <fmt/format.h>
#include <thread>
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/literals.h"
#include "common/logging/log.h"
#include "common/settings.h"
namespace GPU::Logging {
// Static instance
static GPULogger* g_instance = nullptr;
GPULogger& GPULogger::GetInstance() {
if (!g_instance) {
g_instance = new GPULogger();
}
return *g_instance;
}
GPULogger::GPULogger() = default;
GPULogger::~GPULogger() {
Shutdown();
}
void GPULogger::Initialize(LogLevel level, DriverType driver) {
if (initialized) {
LOG_WARNING(Render_Vulkan, "[GPU Logging] Already initialized");
return;
}
current_level = level;
detected_driver = driver;
if (current_level == LogLevel::Off) {
return;
}
// Create log directory
using namespace Common::FS;
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
[[maybe_unused]] const bool log_dir_created = CreateDir(log_dir);
// Create GPU crashes directory
const auto crashes_dir = log_dir / "gpu_crashes";
[[maybe_unused]] const bool crashes_dir_created = CreateDir(crashes_dir);
// Open GPU log file
const auto gpu_log_path = log_dir / "eden_gpu.log";
// Rotate old log
const auto old_log_path = log_dir / "eden_gpu.log.old.txt";
RemoveFile(old_log_path);
[[maybe_unused]] const bool log_renamed = RenameFile(gpu_log_path, old_log_path);
// Open new log file
gpu_log_file = std::make_unique<Common::FS::IOFile>(
gpu_log_path, Common::FS::FileAccessMode::Write, Common::FS::FileType::TextFile);
if (!gpu_log_file->IsOpen()) {
LOG_ERROR(Render_Vulkan, "[GPU Logging] Failed to open GPU log file");
return;
}
// Initialize ring buffer
call_ring_buffer.resize(ring_buffer_size);
// Write header
const char* driver_name = "Unknown";
switch (detected_driver) {
case DriverType::Turnip:
driver_name = "Turnip (Mesa Freedreno)";
break;
case DriverType::Qualcomm:
driver_name = "Qualcomm Proprietary";
break;
default:
driver_name = "Unknown";
break;
}
const char* level_name = "Unknown";
switch (current_level) {
case LogLevel::Off:
level_name = "Off";
break;
case LogLevel::Errors:
level_name = "Errors";
break;
case LogLevel::Standard:
level_name = "Standard";
break;
case LogLevel::Verbose:
level_name = "Verbose";
break;
case LogLevel::All:
level_name = "All";
break;
}
const auto header = fmt::format(
"=== Eden GPU Logging Started ===\n"
"Timestamp: {}\n"
"Log Level: {}\n"
"Driver: {}\n"
"Ring Buffer Size: {}\n"
"================================\n\n",
FormatTimestamp(std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())),
level_name, driver_name, ring_buffer_size);
WriteToLog(header);
// Note: Crash handler is initialized independently in EmulationSession::InitializeSystem()
// to ensure it remains active even if Vulkan device initialization fails
initialized = true;
LOG_INFO(Render_Vulkan, "[GPU Logging] Initialized with level: {}, driver: {}", level_name,
driver_name);
}
void GPULogger::Shutdown() {
if (!initialized) {
return;
}
// Write statistics
const auto stats = fmt::format(
"\n=== GPU Logging Statistics ===\n"
"Total Vulkan Calls: {}\n"
"Total Memory Allocations: {}\n"
"Total Memory Deallocations: {}\n"
"Peak Memory Usage: {}\n"
"Current Memory Usage: {}\n"
"Log Size: {} bytes\n"
"==============================\n",
total_vulkan_calls, total_allocations, total_deallocations,
FormatMemorySize(peak_allocated_bytes), FormatMemorySize(current_allocated_bytes),
bytes_written);
WriteToLog(stats);
// Close file
if (gpu_log_file) {
gpu_log_file->Flush();
gpu_log_file->Close();
gpu_log_file.reset();
}
// Note: Crash handler is NOT shut down here - it remains active throughout app lifetime
// It will be shut down when EmulationSession is destroyed
initialized = false;
LOG_INFO(Render_Vulkan, "[GPU Logging] Shutdown complete");
}
void GPULogger::LogVulkanCall(const std::string& call_name, const std::string& params,
int result) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls) {
return;
}
// Only log all calls in Verbose or All mode
if (current_level != LogLevel::Verbose && current_level != LogLevel::All) {
// In Standard mode, only log important calls
if (call_name.find("vkCmd") == std::string::npos &&
call_name.find("vkCreate") == std::string::npos &&
call_name.find("vkDestroy") == std::string::npos) {
return;
}
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto thread_id = static_cast<u32>(std::hash<std::thread::id>{}(std::this_thread::get_id()));
// Add to ring buffer
{
std::lock_guard lock(ring_buffer_mutex);
call_ring_buffer[ring_buffer_index] = {
.timestamp = timestamp,
.call_name = call_name,
.parameters = params,
.result = result,
.thread_id = thread_id,
};
ring_buffer_index = (ring_buffer_index + 1) % ring_buffer_size;
total_vulkan_calls++;
}
// Log to file
const auto log_entry =
fmt::format("[{}] [Vulkan] [Thread:{}] {}({}) -> {}\n", FormatTimestamp(timestamp),
thread_id, call_name, params, result);
WriteToLog(log_entry);
}
void GPULogger::LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_memory) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const bool is_device_local = (memory_flags & 0x1) != 0; // VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
const bool is_host_visible = (memory_flags & 0x2) != 0; // VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
{
std::lock_guard lock(memory_mutex);
memory_allocations[memory] = {
.memory_handle = memory,
.size = size,
.memory_flags = memory_flags,
.timestamp = timestamp,
.is_device_local = is_device_local,
.is_host_visible = is_host_visible,
};
total_allocations++;
current_allocated_bytes += size;
if (current_allocated_bytes > peak_allocated_bytes) {
peak_allocated_bytes = current_allocated_bytes;
}
}
const auto log_entry = fmt::format(
"[{}] [Memory] Allocated {} at 0x{:x} (Device:{}, Host:{})\n", FormatTimestamp(timestamp),
FormatMemorySize(size), memory, is_device_local ? "Yes" : "No",
is_host_visible ? "Yes" : "No");
WriteToLog(log_entry);
}
void GPULogger::LogMemoryDeallocation(uintptr_t memory) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_memory) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
u64 size = 0;
{
std::lock_guard lock(memory_mutex);
auto it = memory_allocations.find(memory);
if (it != memory_allocations.end()) {
size = it->second.size;
current_allocated_bytes -= size;
memory_allocations.erase(it);
total_deallocations++;
}
}
if (size > 0) {
const auto log_entry =
fmt::format("[{}] [Memory] Deallocated {} at 0x{:x}\n", FormatTimestamp(timestamp),
FormatMemorySize(size), memory);
WriteToLog(log_entry);
}
}
void GPULogger::LogShaderCompilation(const std::string& shader_name,
const std::string& shader_info,
std::span<const u32> spirv_code) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!dump_shaders && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Shader] Compiled: {} ({})\n",
FormatTimestamp(timestamp), shader_name, shader_info);
WriteToLog(log_entry);
// Dump SPIR-V binary if enabled and we have data
if (dump_shaders && !spirv_code.empty()) {
using namespace Common::FS;
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
const auto shaders_dir = log_dir / "shaders";
// Create directory on first dump
if (!shader_dump_dir_created) {
[[maybe_unused]] const bool created = CreateDir(shaders_dir);
shader_dump_dir_created = true;
}
// Write SPIR-V binary file
const auto shader_path = shaders_dir / fmt::format("{}.spv", shader_name);
auto shader_file = std::make_unique<Common::FS::IOFile>(
shader_path, FileAccessMode::Write, FileType::BinaryFile);
if (shader_file->IsOpen()) {
const size_t bytes_to_write = spirv_code.size() * sizeof(u32);
static_cast<void>(shader_file->WriteSpan(spirv_code));
shader_file->Close();
const auto dump_log = fmt::format("[{}] [Shader] Dumped SPIR-V: {} ({} bytes)\n",
FormatTimestamp(timestamp), shader_path.string(), bytes_to_write);
WriteToLog(dump_log);
} else {
LOG_WARNING(Render_Vulkan, "[GPU Logging] Failed to dump shader: {}", shader_path.string());
}
}
}
void GPULogger::LogPipelineStateChange(const std::string& state_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
// Store pipeline state for crash dumps
{
std::lock_guard lock(state_mutex);
stored_pipeline_state = state_info;
}
if (current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry =
fmt::format("[{}] [Pipeline] State change: {}\n", FormatTimestamp(timestamp), state_info);
WriteToLog(log_entry);
}
void GPULogger::LogDriverDebugInfo(const std::string& debug_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
// Store driver debug info for crash dumps
{
std::lock_guard lock(state_mutex);
stored_driver_debug_info = debug_info;
}
if (!capture_driver_debug) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry =
fmt::format("[{}] [Driver] {}\n", FormatTimestamp(timestamp), debug_info);
WriteToLog(log_entry);
}
void GPULogger::LogExtensionUsage(const std::string& extension_name, const std::string& function_name) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
bool is_first_use = false;
{
std::lock_guard lock(extension_mutex);
auto [iter, inserted] = used_extensions.insert(extension_name);
is_first_use = inserted;
}
if (is_first_use) {
const auto log_entry = fmt::format("[{}] [Extension] First use of {} in {}\n",
FormatTimestamp(timestamp), extension_name, function_name);
WriteToLog(log_entry);
LOG_INFO(Render_Vulkan, "[GPU Logging] First use of extension {} in {}",
extension_name, function_name);
} else if (current_level >= LogLevel::Verbose) {
const auto log_entry = fmt::format("[{}] [Extension] {} used in {}\n",
FormatTimestamp(timestamp), extension_name, function_name);
WriteToLog(log_entry);
}
}
void GPULogger::LogRenderPassBegin(const std::string& render_pass_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [RenderPass] Begin: {}\n",
FormatTimestamp(timestamp), render_pass_info);
WriteToLog(log_entry);
}
void GPULogger::LogRenderPassEnd() {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [RenderPass] End\n", FormatTimestamp(timestamp));
WriteToLog(log_entry);
}
void GPULogger::LogPipelineBind(bool is_compute, const std::string& pipeline_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const char* pipeline_type = is_compute ? "Compute" : "Graphics";
const auto log_entry = fmt::format("[{}] [Pipeline] Bind {} pipeline: {}\n",
FormatTimestamp(timestamp), pipeline_type, pipeline_info);
WriteToLog(log_entry);
}
void GPULogger::LogDescriptorSetBind(const std::string& descriptor_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Descriptor] Bind: {}\n",
FormatTimestamp(timestamp), descriptor_info);
WriteToLog(log_entry);
}
void GPULogger::LogPipelineBarrier(const std::string& barrier_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Barrier] {}\n",
FormatTimestamp(timestamp), barrier_info);
WriteToLog(log_entry);
}
void GPULogger::LogImageOperation(const std::string& operation, const std::string& image_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Image] {}: {}\n",
FormatTimestamp(timestamp), operation, image_info);
WriteToLog(log_entry);
}
void GPULogger::LogClearOperation(const std::string& clear_info) {
if (!initialized || current_level == LogLevel::Off) {
return;
}
if (!track_vulkan_calls && current_level < LogLevel::Verbose) {
return;
}
const auto timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
const auto log_entry = fmt::format("[{}] [Clear] {}\n",
FormatTimestamp(timestamp), clear_info);
WriteToLog(log_entry);
}
GPUStateSnapshot GPULogger::GetCurrentSnapshot() {
GPUStateSnapshot snapshot;
snapshot.timestamp = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch());
snapshot.driver_type = detected_driver;
// Capture recent Vulkan calls
{
std::lock_guard lock(ring_buffer_mutex);
snapshot.recent_calls.reserve(ring_buffer_size);
// Copy from current position to end
for (size_t i = ring_buffer_index; i < ring_buffer_size; ++i) {
if (!call_ring_buffer[i].call_name.empty()) {
snapshot.recent_calls.push_back(call_ring_buffer[i]);
}
}
// Copy from beginning to current position
for (size_t i = 0; i < ring_buffer_index; ++i) {
if (!call_ring_buffer[i].call_name.empty()) {
snapshot.recent_calls.push_back(call_ring_buffer[i]);
}
}
}
// Capture memory status
{
std::lock_guard lock(memory_mutex);
snapshot.memory_status = fmt::format(
"Total Allocations: {}\n"
"Current Usage: {}\n"
"Peak Usage: {}\n"
"Active Allocations: {}\n",
total_allocations, FormatMemorySize(current_allocated_bytes),
FormatMemorySize(peak_allocated_bytes), memory_allocations.size());
}
// Capture stored pipeline and driver debug info
{
std::lock_guard lock(state_mutex);
snapshot.pipeline_state = stored_pipeline_state.empty() ?
"No pipeline state logged yet" : stored_pipeline_state;
snapshot.driver_debug_info = stored_driver_debug_info.empty() ?
"No driver debug info logged yet" : stored_driver_debug_info;
}
return snapshot;
}
void GPULogger::DumpStateToFile(const std::string& crash_reason) {
using namespace Common::FS;
const auto& log_dir = GetEdenPath(EdenPath::LogDir);
const auto crashes_dir = log_dir / "gpu_crashes";
[[maybe_unused]] const bool crashes_dir_created = CreateDir(crashes_dir);
// Generate crash dump filename with timestamp
const auto now = std::chrono::system_clock::now();
const auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(
now.time_since_epoch()).count();
const auto crash_dump_path = crashes_dir / fmt::format("crash_{}.gpu-dump", timestamp);
auto crash_file =
std::make_unique<Common::FS::IOFile>(crash_dump_path, FileAccessMode::Write, FileType::TextFile);
if (!crash_file->IsOpen()) {
LOG_ERROR(Render_Vulkan, "[GPU Logging] Failed to create crash dump file");
return;
}
auto snapshot = GetCurrentSnapshot();
const char* driver_name = "Unknown";
switch (snapshot.driver_type) {
case DriverType::Turnip:
driver_name = "Turnip (Mesa Freedreno)";
break;
case DriverType::Qualcomm:
driver_name = "Qualcomm Proprietary";
break;
default:
driver_name = "Unknown";
break;
}
// Write crash dump header
const auto header = fmt::format(
"=== GPU CRASH DUMP ===\n"
"Timestamp: {}\n"
"Reason: {}\n"
"Driver: {}\n"
"\n",
FormatTimestamp(snapshot.timestamp), crash_reason, driver_name);
static_cast<void>(crash_file->WriteString(header));
// Write recent Vulkan calls
static_cast<void>(crash_file->WriteString(fmt::format("=== RECENT VULKAN API CALLS (Last {}) ===\n",
snapshot.recent_calls.size())));
for (const auto& call : snapshot.recent_calls) {
const auto call_str =
fmt::format("[{}] [Thread:{}] {}({}) -> {}\n", FormatTimestamp(call.timestamp),
call.thread_id, call.call_name, call.parameters, call.result);
static_cast<void>(crash_file->WriteString(call_str));
}
static_cast<void>(crash_file->WriteString("\n"));
// Write memory status
static_cast<void>(crash_file->WriteString("=== MEMORY STATUS ===\n"));
static_cast<void>(crash_file->WriteString(snapshot.memory_status));
static_cast<void>(crash_file->WriteString("\n"));
// Write pipeline state
static_cast<void>(crash_file->WriteString("=== PIPELINE STATE ===\n"));
static_cast<void>(crash_file->WriteString(snapshot.pipeline_state));
static_cast<void>(crash_file->WriteString("\n"));
// Write driver debug info
static_cast<void>(crash_file->WriteString("=== DRIVER DEBUG INFO ===\n"));
static_cast<void>(crash_file->WriteString(snapshot.driver_debug_info));
static_cast<void>(crash_file->WriteString("\n"));
crash_file->Flush();
crash_file->Close();
LOG_CRITICAL(Render_Vulkan, "[GPU Logging] Crash dump written to: {}",
crash_dump_path.string());
}
void GPULogger::SetLogLevel(LogLevel level) {
current_level = level;
}
void GPULogger::EnableVulkanCallTracking(bool enabled) {
track_vulkan_calls = enabled;
}
void GPULogger::EnableShaderDumps(bool enabled) {
dump_shaders = enabled;
}
void GPULogger::EnableMemoryTracking(bool enabled) {
track_memory = enabled;
}
void GPULogger::EnableDriverDebugInfo(bool enabled) {
capture_driver_debug = enabled;
}
void GPULogger::SetRingBufferSize(size_t entries) {
std::lock_guard lock(ring_buffer_mutex);
ring_buffer_size = entries;
call_ring_buffer.resize(entries);
ring_buffer_index = 0;
}
LogLevel GPULogger::GetLogLevel() const {
return current_level;
}
DriverType GPULogger::GetDriverType() const {
return detected_driver;
}
std::string GPULogger::GetStatistics() const {
std::lock_guard lock(memory_mutex);
return fmt::format(
"Vulkan Calls: {}, Allocations: {}, Deallocations: {}, "
"Current Memory: {}, Peak Memory: {}",
total_vulkan_calls, total_allocations, total_deallocations,
FormatMemorySize(current_allocated_bytes), FormatMemorySize(peak_allocated_bytes));
}
bool GPULogger::IsInitialized() const {
return initialized;
}
void GPULogger::WriteToLog(const std::string& message) {
if (!gpu_log_file || !gpu_log_file->IsOpen()) {
return;
}
std::lock_guard lock(file_mutex);
bytes_written += gpu_log_file->WriteString(message);
// Flush on errors or if we've written a lot
using namespace Common::Literals;
if (bytes_written % (1_MiB) == 0) {
gpu_log_file->Flush();
}
}
std::string GPULogger::FormatTimestamp(std::chrono::microseconds timestamp) const {
const auto seconds = timestamp.count() / 1000000;
const auto microseconds = timestamp.count() % 1000000;
return fmt::format("{:4d}.{:06d}", seconds, microseconds);
}
std::string GPULogger::FormatMemorySize(u64 bytes) const {
using namespace Common::Literals;
if (bytes >= 1_GiB) {
return fmt::format("{:.2f} GiB", static_cast<double>(bytes) / (1_GiB));
} else if (bytes >= 1_MiB) {
return fmt::format("{:.2f} MiB", static_cast<double>(bytes) / (1_MiB));
} else if (bytes >= 1_KiB) {
return fmt::format("{:.2f} KiB", static_cast<double>(bytes) / (1_KiB));
} else {
return fmt::format("{} B", bytes);
}
}
} // namespace GPU::Logging

View file

@ -0,0 +1,199 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <chrono>
#include <memory>
#include <mutex>
#include <set>
#include <span>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
// Forward declarations
namespace Common::FS {
class IOFile;
}
namespace Vulkan {
class Device;
}
namespace GPU::Logging {
enum class LogLevel : u8 {
Off = 0,
Errors = 1,
Standard = 2,
Verbose = 3,
All = 4,
};
enum class DriverType : u8 {
Unknown,
Turnip, // Mesa Turnip driver
Qualcomm, // Qualcomm proprietary driver
};
// Ring buffer entry for tracking Vulkan API calls
struct VulkanCallEntry {
std::chrono::microseconds timestamp;
std::string call_name; // e.g., "vkCmdDraw", "vkBeginRenderPass"
std::string parameters; // Serialized parameters
int result; // VkResult return code
u32 thread_id;
};
// GPU memory allocation entry
struct MemoryAllocationEntry {
uintptr_t memory_handle;
u64 size;
u32 memory_flags;
std::chrono::microseconds timestamp;
bool is_device_local;
bool is_host_visible;
};
// GPU state snapshot for crash dumps
struct GPUStateSnapshot {
std::vector<VulkanCallEntry> recent_calls; // Last N API calls
std::vector<std::string> active_shaders; // Currently bound shaders
std::string pipeline_state; // Current pipeline state
std::string memory_status; // Current memory allocations
std::string driver_debug_info; // Driver-specific debug data
std::chrono::microseconds timestamp;
DriverType driver_type;
};
/// Main GPU logging system singleton
class GPULogger {
public:
static GPULogger& GetInstance();
// Prevent copying
GPULogger(const GPULogger&) = delete;
GPULogger& operator=(const GPULogger&) = delete;
// Initialization and control
void Initialize(LogLevel level, DriverType detected_driver = DriverType::Unknown);
void Shutdown();
// Logging API
void LogVulkanCall(const std::string& call_name, const std::string& params, int result);
void LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags);
void LogMemoryDeallocation(uintptr_t memory);
void LogShaderCompilation(const std::string& shader_name, const std::string& shader_info,
std::span<const u32> spirv_code = {});
void LogPipelineStateChange(const std::string& state_info);
void LogDriverDebugInfo(const std::string& debug_info);
// Extension usage tracking
void LogExtensionUsage(const std::string& extension_name, const std::string& function_name);
// Render pass logging
void LogRenderPassBegin(const std::string& render_pass_info);
void LogRenderPassEnd();
// Pipeline binding logging
void LogPipelineBind(bool is_compute, const std::string& pipeline_info);
// Descriptor set binding logging
void LogDescriptorSetBind(const std::string& descriptor_info);
// Pipeline barrier logging
void LogPipelineBarrier(const std::string& barrier_info);
// Image operation logging
void LogImageOperation(const std::string& operation, const std::string& image_info);
// Clear operation logging
void LogClearOperation(const std::string& clear_info);
// Crash handling
GPUStateSnapshot GetCurrentSnapshot();
void DumpStateToFile(const std::string& crash_reason);
// Settings
void SetLogLevel(LogLevel level);
void EnableVulkanCallTracking(bool enabled);
void EnableShaderDumps(bool enabled);
void EnableMemoryTracking(bool enabled);
void EnableDriverDebugInfo(bool enabled);
void SetRingBufferSize(size_t entries);
// Query
LogLevel GetLogLevel() const;
DriverType GetDriverType() const;
std::string GetStatistics() const;
bool IsInitialized() const;
private:
GPULogger();
~GPULogger();
// Helper functions
void WriteToLog(const std::string& message);
void RotateLogFile();
std::string FormatTimestamp(std::chrono::microseconds timestamp) const;
std::string FormatMemorySize(u64 bytes) const;
// State
bool initialized = false;
LogLevel current_level = LogLevel::Off;
DriverType detected_driver = DriverType::Unknown;
// Ring buffer for API calls
std::vector<VulkanCallEntry> call_ring_buffer;
size_t ring_buffer_index = 0;
size_t ring_buffer_size = 512;
mutable std::mutex ring_buffer_mutex;
// Memory tracking
std::unordered_map<uintptr_t, MemoryAllocationEntry> memory_allocations;
mutable std::mutex memory_mutex;
// Statistics
u64 total_vulkan_calls = 0;
u64 total_allocations = 0;
u64 total_deallocations = 0;
u64 current_allocated_bytes = 0;
u64 peak_allocated_bytes = 0;
// File backend for GPU logs
std::unique_ptr<Common::FS::IOFile> gpu_log_file;
mutable std::mutex file_mutex;
u64 bytes_written = 0;
// Feature flags
bool track_vulkan_calls = true;
bool dump_shaders = false;
bool track_memory = false;
bool capture_driver_debug = false;
// Extension usage tracking
std::set<std::string> used_extensions;
mutable std::mutex extension_mutex;
// Shader dump directory (created on demand)
bool shader_dump_dir_created = false;
// Stored state for crash dumps
std::string stored_driver_debug_info;
std::string stored_pipeline_state;
mutable std::mutex state_mutex;
};
// Helper to get stage name from index
inline const char* GetShaderStageName(size_t stage_index) {
static constexpr std::array<const char*, 5> stage_names{
"vertex", "tess_control", "tess_eval", "geometry", "fragment"
};
return stage_index < stage_names.size() ? stage_names[stage_index] : "unknown";
}
} // namespace GPU::Logging

View 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

View 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

View 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

View 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