Browse Source
[android] gpu logs functions
[android] gpu logs functions
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...pull/3389/head
committed by
crueter
30 changed files with 1601 additions and 6 deletions
-
9src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
-
4src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/ByteSetting.kt
-
3src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt
-
56src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
-
9src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
-
17src/android/app/src/main/res/values/arrays.xml
-
20src/android/app/src/main/res/values/strings.xml
-
1src/common/settings.cpp
-
12src/common/settings.h
-
1src/common/settings_enums.h
-
3src/video_core/CMakeLists.txt
-
20src/video_core/gpu_logging/CMakeLists.txt
-
48src/video_core/gpu_logging/freedreno_debug.cpp
-
28src/video_core/gpu_logging/freedreno_debug.h
-
734src/video_core/gpu_logging/gpu_logging.cpp
-
199src/video_core/gpu_logging/gpu_logging.h
-
43src/video_core/gpu_logging/gpu_state_capture.cpp
-
23src/video_core/gpu_logging/gpu_state_capture.h
-
26src/video_core/gpu_logging/qualcomm_debug.cpp
-
22src/video_core/gpu_logging/qualcomm_debug.h
-
17src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
-
21src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
-
23src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
-
41src/video_core/renderer_vulkan/vk_rasterizer.cpp
-
25src/video_core/renderer_vulkan/vk_scheduler.cpp
-
6src/video_core/renderer_vulkan/vk_texture_cache.cpp
-
36src/video_core/vulkan_common/vulkan_debug_callback.cpp
-
109src/video_core/vulkan_common/vulkan_device.cpp
-
4src/video_core/vulkan_common/vulkan_device.h
-
47src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@ -0,0 +1,20 @@ |
|||||
|
# SPDX-FileCopyrightText: Copyright 2025 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 |
||||
|
freedreno_debug.cpp |
||||
|
freedreno_debug.h |
||||
|
qualcomm_debug.cpp |
||||
|
qualcomm_debug.h |
||||
|
) |
||||
|
|
||||
|
target_link_libraries(gpu_logging PUBLIC common) |
||||
|
|
||||
|
if(ANDROID) |
||||
|
# Link with adrenotools when available for future Qualcomm integration |
||||
|
# target_link_libraries(gpu_logging PUBLIC adrenotools) |
||||
|
endif() |
||||
@ -0,0 +1,48 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
#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
|
||||
@ -0,0 +1,28 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#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 |
||||
@ -0,0 +1,734 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 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
|
||||
@ -0,0 +1,199 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <chrono> |
||||
|
#include <memory> |
||||
|
#include <mutex> |
||||
|
#include <set> |
||||
|
#include <span> |
||||
|
#include <string> |
||||
|
#include <unordered_map> |
||||
|
#include <vector> |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
// Forward declarations |
||||
|
namespace Common::FS { |
||||
|
class IOFile; |
||||
|
} |
||||
|
|
||||
|
namespace Vulkan { |
||||
|
class Device; |
||||
|
} |
||||
|
|
||||
|
namespace GPU::Logging { |
||||
|
|
||||
|
enum class LogLevel : u8 { |
||||
|
Off = 0, |
||||
|
Errors = 1, |
||||
|
Standard = 2, |
||||
|
Verbose = 3, |
||||
|
All = 4, |
||||
|
}; |
||||
|
|
||||
|
enum class DriverType : u8 { |
||||
|
Unknown, |
||||
|
Turnip, // Mesa Turnip driver |
||||
|
Qualcomm, // Qualcomm proprietary driver |
||||
|
}; |
||||
|
|
||||
|
// Ring buffer entry for tracking Vulkan API calls |
||||
|
struct VulkanCallEntry { |
||||
|
std::chrono::microseconds timestamp; |
||||
|
std::string call_name; // e.g., "vkCmdDraw", "vkBeginRenderPass" |
||||
|
std::string parameters; // Serialized parameters |
||||
|
int result; // VkResult return code |
||||
|
u32 thread_id; |
||||
|
}; |
||||
|
|
||||
|
// GPU memory allocation entry |
||||
|
struct MemoryAllocationEntry { |
||||
|
uintptr_t memory_handle; |
||||
|
u64 size; |
||||
|
u32 memory_flags; |
||||
|
std::chrono::microseconds timestamp; |
||||
|
bool is_device_local; |
||||
|
bool is_host_visible; |
||||
|
}; |
||||
|
|
||||
|
// GPU state snapshot for crash dumps |
||||
|
struct GPUStateSnapshot { |
||||
|
std::vector<VulkanCallEntry> recent_calls; // Last N API calls |
||||
|
std::vector<std::string> active_shaders; // Currently bound shaders |
||||
|
std::string pipeline_state; // Current pipeline state |
||||
|
std::string memory_status; // Current memory allocations |
||||
|
std::string driver_debug_info; // Driver-specific debug data |
||||
|
std::chrono::microseconds timestamp; |
||||
|
DriverType driver_type; |
||||
|
}; |
||||
|
|
||||
|
/// Main GPU logging system singleton |
||||
|
class GPULogger { |
||||
|
public: |
||||
|
static GPULogger& GetInstance(); |
||||
|
|
||||
|
// Prevent copying |
||||
|
GPULogger(const GPULogger&) = delete; |
||||
|
GPULogger& operator=(const GPULogger&) = delete; |
||||
|
|
||||
|
// Initialization and control |
||||
|
void Initialize(LogLevel level, DriverType detected_driver = DriverType::Unknown); |
||||
|
void Shutdown(); |
||||
|
|
||||
|
// Logging API |
||||
|
void LogVulkanCall(const std::string& call_name, const std::string& params, int result); |
||||
|
void LogMemoryAllocation(uintptr_t memory, u64 size, u32 memory_flags); |
||||
|
void LogMemoryDeallocation(uintptr_t memory); |
||||
|
void LogShaderCompilation(const std::string& shader_name, const std::string& shader_info, |
||||
|
std::span<const u32> spirv_code = {}); |
||||
|
void LogPipelineStateChange(const std::string& state_info); |
||||
|
void LogDriverDebugInfo(const std::string& debug_info); |
||||
|
|
||||
|
// Extension usage tracking |
||||
|
void LogExtensionUsage(const std::string& extension_name, const std::string& function_name); |
||||
|
|
||||
|
// Render pass logging |
||||
|
void LogRenderPassBegin(const std::string& render_pass_info); |
||||
|
void LogRenderPassEnd(); |
||||
|
|
||||
|
// Pipeline binding logging |
||||
|
void LogPipelineBind(bool is_compute, const std::string& pipeline_info); |
||||
|
|
||||
|
// Descriptor set binding logging |
||||
|
void LogDescriptorSetBind(const std::string& descriptor_info); |
||||
|
|
||||
|
// Pipeline barrier logging |
||||
|
void LogPipelineBarrier(const std::string& barrier_info); |
||||
|
|
||||
|
// Image operation logging |
||||
|
void LogImageOperation(const std::string& operation, const std::string& image_info); |
||||
|
|
||||
|
// Clear operation logging |
||||
|
void LogClearOperation(const std::string& clear_info); |
||||
|
|
||||
|
// Crash handling |
||||
|
GPUStateSnapshot GetCurrentSnapshot(); |
||||
|
void DumpStateToFile(const std::string& crash_reason); |
||||
|
|
||||
|
// Settings |
||||
|
void SetLogLevel(LogLevel level); |
||||
|
void EnableVulkanCallTracking(bool enabled); |
||||
|
void EnableShaderDumps(bool enabled); |
||||
|
void EnableMemoryTracking(bool enabled); |
||||
|
void EnableDriverDebugInfo(bool enabled); |
||||
|
void SetRingBufferSize(size_t entries); |
||||
|
|
||||
|
// Query |
||||
|
LogLevel GetLogLevel() const; |
||||
|
DriverType GetDriverType() const; |
||||
|
std::string GetStatistics() const; |
||||
|
bool IsInitialized() const; |
||||
|
|
||||
|
private: |
||||
|
GPULogger(); |
||||
|
~GPULogger(); |
||||
|
|
||||
|
// Helper functions |
||||
|
void WriteToLog(const std::string& message); |
||||
|
void RotateLogFile(); |
||||
|
std::string FormatTimestamp(std::chrono::microseconds timestamp) const; |
||||
|
std::string FormatMemorySize(u64 bytes) const; |
||||
|
|
||||
|
// State |
||||
|
bool initialized = false; |
||||
|
LogLevel current_level = LogLevel::Off; |
||||
|
DriverType detected_driver = DriverType::Unknown; |
||||
|
|
||||
|
// Ring buffer for API calls |
||||
|
std::vector<VulkanCallEntry> call_ring_buffer; |
||||
|
size_t ring_buffer_index = 0; |
||||
|
size_t ring_buffer_size = 512; |
||||
|
mutable std::mutex ring_buffer_mutex; |
||||
|
|
||||
|
// Memory tracking |
||||
|
std::unordered_map<uintptr_t, MemoryAllocationEntry> memory_allocations; |
||||
|
mutable std::mutex memory_mutex; |
||||
|
|
||||
|
// Statistics |
||||
|
u64 total_vulkan_calls = 0; |
||||
|
u64 total_allocations = 0; |
||||
|
u64 total_deallocations = 0; |
||||
|
u64 current_allocated_bytes = 0; |
||||
|
u64 peak_allocated_bytes = 0; |
||||
|
|
||||
|
// File backend for GPU logs |
||||
|
std::unique_ptr<Common::FS::IOFile> gpu_log_file; |
||||
|
mutable std::mutex file_mutex; |
||||
|
u64 bytes_written = 0; |
||||
|
|
||||
|
// Feature flags |
||||
|
bool track_vulkan_calls = true; |
||||
|
bool dump_shaders = false; |
||||
|
bool track_memory = false; |
||||
|
bool capture_driver_debug = false; |
||||
|
|
||||
|
// Extension usage tracking |
||||
|
std::set<std::string> used_extensions; |
||||
|
mutable std::mutex extension_mutex; |
||||
|
|
||||
|
// Shader dump directory (created on demand) |
||||
|
bool shader_dump_dir_created = false; |
||||
|
|
||||
|
// Stored state for crash dumps |
||||
|
std::string stored_driver_debug_info; |
||||
|
std::string stored_pipeline_state; |
||||
|
mutable std::mutex state_mutex; |
||||
|
}; |
||||
|
|
||||
|
// Helper to get stage name from index |
||||
|
inline const char* GetShaderStageName(size_t stage_index) { |
||||
|
static constexpr std::array<const char*, 5> stage_names{ |
||||
|
"vertex", "tess_control", "tess_eval", "geometry", "fragment" |
||||
|
}; |
||||
|
return stage_index < stage_names.size() ? stage_names[stage_index] : "unknown"; |
||||
|
} |
||||
|
|
||||
|
} // namespace GPU::Logging |
||||
@ -0,0 +1,43 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
#include "video_core/gpu_logging/gpu_state_capture.h"
|
||||
|
#include <fmt/format.h>
|
||||
|
|
||||
|
namespace GPU::Logging { |
||||
|
|
||||
|
GPUStateSnapshot GPUStateCapture::CaptureState() { |
||||
|
return GPULogger::GetInstance().GetCurrentSnapshot(); |
||||
|
} |
||||
|
|
||||
|
std::string GPUStateCapture::SerializeState(const GPUStateSnapshot& snapshot) { |
||||
|
std::string result; |
||||
|
|
||||
|
result += "=== GPU STATE SNAPSHOT ===\n\n"; |
||||
|
|
||||
|
result += fmt::format("Driver: {}\n", static_cast<int>(snapshot.driver_type)); |
||||
|
result += fmt::format("Recent Calls: {}\n\n", snapshot.recent_calls.size()); |
||||
|
|
||||
|
result += "=== RECENT VULKAN CALLS ===\n"; |
||||
|
for (const auto& call : snapshot.recent_calls) { |
||||
|
result += fmt::format("{}: {}({}) -> {}\n", call.timestamp.count(), call.call_name, |
||||
|
call.parameters, call.result); |
||||
|
} |
||||
|
|
||||
|
result += "\n=== MEMORY STATUS ===\n"; |
||||
|
result += snapshot.memory_status; |
||||
|
|
||||
|
result += "\n=== PIPELINE STATE ===\n"; |
||||
|
result += snapshot.pipeline_state; |
||||
|
|
||||
|
result += "\n=== DRIVER DEBUG INFO ===\n"; |
||||
|
result += snapshot.driver_debug_info; |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
void GPUStateCapture::WriteCrashDump(const std::string& crash_reason) { |
||||
|
GPULogger::GetInstance().DumpStateToFile(crash_reason); |
||||
|
} |
||||
|
|
||||
|
} // namespace GPU::Logging
|
||||
@ -0,0 +1,23 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
#include "video_core/gpu_logging/gpu_logging.h" |
||||
|
|
||||
|
namespace GPU::Logging { |
||||
|
|
||||
|
class GPUStateCapture { |
||||
|
public: |
||||
|
// Capture current GPU state from logging system |
||||
|
static GPUStateSnapshot CaptureState(); |
||||
|
|
||||
|
// Serialize state to human-readable format |
||||
|
static std::string SerializeState(const GPUStateSnapshot& snapshot); |
||||
|
|
||||
|
// Write detailed crash dump (implemented in GPULogger) |
||||
|
static void WriteCrashDump(const std::string& crash_reason); |
||||
|
}; |
||||
|
|
||||
|
} // namespace GPU::Logging |
||||
@ -0,0 +1,26 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
#include "video_core/gpu_logging/qualcomm_debug.h"
|
||||
|
#include "common/logging/log.h"
|
||||
|
|
||||
|
namespace GPU::Logging::Qualcomm { |
||||
|
|
||||
|
bool QualcommDebugger::is_initialized = false; |
||||
|
|
||||
|
void QualcommDebugger::Initialize() { |
||||
|
if (is_initialized) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
is_initialized = true; |
||||
|
LOG_INFO(Render_Vulkan, "[Qualcomm Debug] Initialized (stub)"); |
||||
|
} |
||||
|
|
||||
|
std::string QualcommDebugger::GetDebugInfo() { |
||||
|
// Stub for future Qualcomm proprietary driver debug extension support
|
||||
|
// This requires libadrenotools integration and Qualcomm-specific APIs
|
||||
|
return "Qualcomm debug info not yet implemented"; |
||||
|
} |
||||
|
|
||||
|
} // namespace GPU::Logging::Qualcomm
|
||||
@ -0,0 +1,22 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 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 |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue