diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp index 4d18bd17ac..4ab8955f5f 100644 --- a/Source/Core/Core/Config/GraphicsSettings.cpp +++ b/Source/Core/Core/Config/GraphicsSettings.cpp @@ -89,6 +89,7 @@ const Info GFX_BORDERLESS_FULLSCREEN{{System::GFX, "Settings", "Borderless false}; const Info GFX_ENABLE_VALIDATION_LAYER{{System::GFX, "Settings", "EnableValidationLayer"}, false}; +const Info GFX_ENABLE_BREADCRUMBS{{System::GFX, "Settings", "EnableBreadcrumbs"}, false}; const Info GFX_BACKEND_MULTITHREADING{{System::GFX, "Settings", "BackendMultithreading"}, true}; diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h index d641ce4aaa..3c5044ab58 100644 --- a/Source/Core/Core/Config/GraphicsSettings.h +++ b/Source/Core/Core/Config/GraphicsSettings.h @@ -82,6 +82,7 @@ extern const Info GFX_ENABLE_WIREFRAME; extern const Info GFX_DISABLE_FOG; extern const Info GFX_BORDERLESS_FULLSCREEN; extern const Info GFX_ENABLE_VALIDATION_LAYER; +extern const Info GFX_ENABLE_BREADCRUMBS; extern const Info GFX_BACKEND_MULTITHREADING; extern const Info GFX_COMMAND_BUFFER_EXECUTE_INTERVAL; extern const Info GFX_SHADER_CACHE; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 06b1a88eee..916baf6dcd 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -1279,6 +1279,7 @@ + diff --git a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt index ec9e1b51cb..f7d804f555 100644 --- a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt +++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt @@ -31,6 +31,8 @@ add_library(videovulkan VKVertexFormat.h VKVertexManager.cpp VKVertexManager.h + VKDebug.cpp + VKDebug.h VulkanContext.cpp VulkanContext.h VulkanLoader.cpp diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp index 3aa05dbaf0..6343aca621 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp @@ -18,6 +18,10 @@ namespace Vulkan CommandBufferManager::CommandBufferManager(bool use_threaded_submission) : m_use_threaded_submission(use_threaded_submission) { + for (auto& frame_resource : m_frame_resources) + { + frame_resource.debug = std::make_unique(g_ActiveConfig.bEnableBreadcrumbs); + } } CommandBufferManager::~CommandBufferManager() @@ -29,6 +33,11 @@ CommandBufferManager::~CommandBufferManager() m_submit_thread.Shutdown(); } + for (auto& frame_resource : m_frame_resources) + { + frame_resource.debug.reset(); + } + DestroyCommandBuffers(); } @@ -62,6 +71,7 @@ bool CommandBufferManager::CreateCommandBuffers() &resources.command_pool); if (res != VK_SUCCESS) { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkCreateCommandPool failed: "); return false; } @@ -73,6 +83,7 @@ bool CommandBufferManager::CreateCommandBuffers() res = vkAllocateCommandBuffers(device, &buffer_info, resources.command_buffers.data()); if (res != VK_SUCCESS) { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkAllocateCommandBuffers failed: "); return false; } @@ -83,6 +94,7 @@ bool CommandBufferManager::CreateCommandBuffers() res = vkCreateFence(device, &fence_info, nullptr, &resources.fence); if (res != VK_SUCCESS) { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkCreateFence failed: "); return false; } @@ -90,6 +102,7 @@ bool CommandBufferManager::CreateCommandBuffers() res = vkCreateSemaphore(device, &semaphore_create_info, nullptr, &resources.semaphore); if (res != VK_SUCCESS) { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: "); return false; } @@ -98,6 +111,7 @@ bool CommandBufferManager::CreateCommandBuffers() res = vkCreateSemaphore(device, &semaphore_create_info, nullptr, &m_present_semaphore); if (res != VK_SUCCESS) { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: "); return false; } @@ -175,6 +189,7 @@ VkDescriptorPool CommandBufferManager::CreateDescriptorPool(u32 max_descriptor_s VkResult res = vkCreateDescriptorPool(device, &pool_create_info, nullptr, &descriptor_pool); if (res != VK_SUCCESS) { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkCreateDescriptorPool failed: "); return VK_NULL_HANDLE; } @@ -274,7 +289,10 @@ void CommandBufferManager::WaitForCommandBufferCompletion(u32 index) VkResult res = vkWaitForFences(g_vulkan_context->GetDevice(), 1, &resources.fence, VK_TRUE, UINT64_MAX); if (res != VK_SUCCESS) + { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkWaitForFences failed: "); + } // Clean up any resources for command buffers between the last known completed buffer and this // now-completed command buffer. If we use >2 buffers, this may be more than one buffer. @@ -311,6 +329,7 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread, VkResult res = vkEndCommandBuffer(command_buffer); if (res != VK_SUCCESS) { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkEndCommandBuffer failed: "); PanicAlertFmt("Failed to end command buffer: {} ({})", VkResultToString(res), static_cast(res)); @@ -354,12 +373,17 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread, // Reset the descriptor pools FrameResources& frame_resources = GetCurrentFrameResources(); + frame_resources.debug->Reset(); + if (frame_resources.descriptor_pools.size() == 1) [[likely]] { VkResult res = vkResetDescriptorPool(g_vulkan_context->GetDevice(), frame_resources.descriptor_pools[0], 0); if (res != VK_SUCCESS) + { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkResetDescriptorPool failed: "); + } } else [[unlikely]] { @@ -421,6 +445,7 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index, vkQueueSubmit(g_vulkan_context->GetGraphicsQueue(), 1, &submit_info, resources.fence); if (res != VK_SUCCESS) { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: "); PanicAlertFmt("Failed to submit command buffer: {} ({})", VkResultToString(res), static_cast(res)); @@ -448,6 +473,7 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index, m_last_present_result != VK_SUBOPTIMAL_KHR && m_last_present_result != VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT) { + PrintFaults(); LOG_VULKAN_ERROR(m_last_present_result, "vkQueuePresentKHR failed: "); } @@ -476,12 +502,18 @@ void CommandBufferManager::BeginCommandBuffer() // Reset fence to unsignaled before starting. VkResult res = vkResetFences(g_vulkan_context->GetDevice(), 1, &resources.fence); if (res != VK_SUCCESS) + { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkResetFences failed: "); + } // Reset command pools to beginning since we can re-use the memory now res = vkResetCommandPool(g_vulkan_context->GetDevice(), resources.command_pool, 0); if (res != VK_SUCCESS) + { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkResetCommandPool failed: "); + } // Enable commands to be recorded to the two buffers again. VkCommandBufferBeginInfo begin_info = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, @@ -490,7 +522,10 @@ void CommandBufferManager::BeginCommandBuffer() { res = vkBeginCommandBuffer(command_buffer, &begin_info); if (res != VK_SUCCESS) + { + PrintFaults(); LOG_VULKAN_ERROR(res, "vkBeginCommandBuffer failed: "); + } } // Reset upload command buffer state @@ -537,5 +572,13 @@ void CommandBufferManager::DeferImageViewDestruction(VkImageView object) [object]() { vkDestroyImageView(g_vulkan_context->GetDevice(), object, nullptr); }); } +void CommandBufferManager::PrintFaults() +{ + for (auto& frame_resource : m_frame_resources) + { + frame_resource.debug->PrintFault(); + } +} + std::unique_ptr g_command_buffer_mgr; } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h index 0249097423..1e828fe36d 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h @@ -20,6 +20,7 @@ #include "Common/Semaphore.h" #include "VideoBackends/Vulkan/Constants.h" +#include "VideoBackends/Vulkan/VKDebug.h" namespace Vulkan { @@ -47,6 +48,8 @@ public: // Allocates a descriptors set from the pool reserved for the current frame. VkDescriptorSet AllocateDescriptorSet(VkDescriptorSetLayout set_layout); + VkDebug& GetDebug() { return *GetCurrentFrameResources().debug; } + // Fence "counters" are used to track which commands have been completed by the GPU. // If the last completed fence counter is greater or equal to N, it means that the work // associated counter N has been completed by the GPU. The value of N to associate with @@ -94,6 +97,8 @@ public: void DeferImageDestruction(VkImage object, VmaAllocation alloc); void DeferImageViewDestruction(VkImageView object); + void PrintFaults(); + private: bool CreateCommandBuffers(); void DestroyCommandBuffers(); @@ -129,6 +134,7 @@ private: { std::vector descriptor_pools; u32 current_descriptor_pool_index = 0; + std::unique_ptr debug = nullptr; }; FrameResources& GetCurrentFrameResources() { return m_frame_resources[m_current_frame]; } @@ -164,4 +170,11 @@ private: extern std::unique_ptr g_command_buffer_mgr; +DOLPHIN_FORCE_INLINE void EmitDebugMarker(VkDebugCommand command, u64 aux0 = 0, u64 aux1 = 0, + u64 aux2 = 0, u64 aux3 = 0) +{ + if (g_command_buffer_mgr->GetDebug().IsEnabled()) [[unlikely]] + g_command_buffer_mgr->GetDebug().EmitMarker(command, aux0, aux1, aux2, aux3); +} + } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp b/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp index ab9b015cec..4a2591407b 100644 --- a/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp +++ b/Source/Core/VideoBackends/Vulkan/ShaderCompiler.cpp @@ -1,11 +1,16 @@ // Copyright 2016 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "Common/Hash.h" +#include "Common/Logging/Log.h" + #include "VideoBackends/Vulkan/ShaderCompiler.h" #include #include +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/VKDebug.h" #include "VideoBackends/Vulkan/VulkanContext.h" #include "VideoCommon/Spirv.h" @@ -113,27 +118,75 @@ static glslang::EShTargetLanguageVersion GetLanguageVersion() return glslang::EShTargetSpv_1_0; } -std::optional CompileVertexShader(std::string_view source_code) +std::optional CompileVertexShader(std::string_view source_code) { - return SPIRV::CompileVertexShader(GetShaderCode(source_code, SHADER_HEADER), APIType::Vulkan, - GetLanguageVersion()); + std::string code = GetShaderCode(source_code, SHADER_HEADER); + u64 hash = Common::GetHash64(reinterpret_cast(code.c_str()), u32(code.size()), 128); + std::optional spirv = + SPIRV::CompileVertexShader(code, APIType::Vulkan, GetLanguageVersion()); + + if (spirv != std::nullopt) + { + if (g_command_buffer_mgr->GetDebug().IsEnabled()) + { + INFO_LOG_FMT(HOST_GPU, "Vertex Shader: HASH: {}; Code: {}", hash, code); + } + return std::make_optional(CompiledSPIRV{std::move(spirv.value()), hash}); + } + return std::nullopt; } -std::optional CompileGeometryShader(std::string_view source_code) +std::optional CompileGeometryShader(std::string_view source_code) { - return SPIRV::CompileGeometryShader(GetShaderCode(source_code, SHADER_HEADER), APIType::Vulkan, - GetLanguageVersion()); + std::string code = GetShaderCode(source_code, SHADER_HEADER); + u64 hash = Common::GetHash64(reinterpret_cast(code.c_str()), u32(code.size()), 128); + std::optional spirv = + SPIRV::CompileGeometryShader(code, APIType::Vulkan, GetLanguageVersion()); + + if (spirv != std::nullopt) + { + if (g_command_buffer_mgr->GetDebug().IsEnabled()) + { + INFO_LOG_FMT(HOST_GPU, "Geometry Shader: HASH: {}; Code: {}", hash, code); + } + return std::make_optional(CompiledSPIRV{std::move(spirv.value()), hash}); + } + return std::nullopt; } -std::optional CompileFragmentShader(std::string_view source_code) +std::optional CompileFragmentShader(std::string_view source_code) { - return SPIRV::CompileFragmentShader(GetShaderCode(source_code, SHADER_HEADER), APIType::Vulkan, - GetLanguageVersion()); + std::string code = GetShaderCode(source_code, SHADER_HEADER); + u64 hash = Common::GetHash64(reinterpret_cast(code.c_str()), u32(code.size()), 128); + std::optional spirv = + SPIRV::CompileFragmentShader(code, APIType::Vulkan, GetLanguageVersion()); + + if (spirv != std::nullopt) + { + if (g_command_buffer_mgr->GetDebug().IsEnabled()) + { + INFO_LOG_FMT(HOST_GPU, "Fragment Shader: HASH: {}; Code: {}", hash, code); + } + return std::make_optional(CompiledSPIRV{std::move(spirv.value()), hash}); + } + return std::nullopt; } -std::optional CompileComputeShader(std::string_view source_code) +std::optional CompileComputeShader(std::string_view source_code) { - return SPIRV::CompileComputeShader(GetShaderCode(source_code, COMPUTE_SHADER_HEADER), - APIType::Vulkan, GetLanguageVersion()); + std::string code = GetShaderCode(source_code, SHADER_HEADER); + u64 hash = Common::GetHash64(reinterpret_cast(code.c_str()), u32(code.size()), 128); + std::optional spirv = + SPIRV::CompileComputeShader(code, APIType::Vulkan, GetLanguageVersion()); + + if (spirv != std::nullopt) + { + if (g_command_buffer_mgr->GetDebug().IsEnabled()) + { + INFO_LOG_FMT(HOST_GPU, "Compute Shader: HASH: {}; Code: {}", hash, code); + } + return std::make_optional(CompiledSPIRV{std::move(spirv.value()), hash}); + } + return std::nullopt; } } // namespace Vulkan::ShaderCompiler diff --git a/Source/Core/VideoBackends/Vulkan/ShaderCompiler.h b/Source/Core/VideoBackends/Vulkan/ShaderCompiler.h index ff1ea64b97..d77333b927 100644 --- a/Source/Core/VideoBackends/Vulkan/ShaderCompiler.h +++ b/Source/Core/VideoBackends/Vulkan/ShaderCompiler.h @@ -16,15 +16,21 @@ namespace Vulkan::ShaderCompiler using SPIRVCodeType = u32; using SPIRVCodeVector = std::vector; +struct CompiledSPIRV +{ + SPIRVCodeVector code; + u64 hash; +}; + // Compile a vertex shader to SPIR-V. -std::optional CompileVertexShader(std::string_view source_code); +std::optional CompileVertexShader(std::string_view source_code); // Compile a geometry shader to SPIR-V. -std::optional CompileGeometryShader(std::string_view source_code); +std::optional CompileGeometryShader(std::string_view source_code); // Compile a fragment shader to SPIR-V. -std::optional CompileFragmentShader(std::string_view source_code); +std::optional CompileFragmentShader(std::string_view source_code); // Compile a compute shader to SPIR-V. -std::optional CompileComputeShader(std::string_view source_code); +std::optional CompileComputeShader(std::string_view source_code); } // namespace Vulkan::ShaderCompiler diff --git a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp index af51293500..d74c79c322 100644 --- a/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKBoundingBox.cpp @@ -11,6 +11,7 @@ #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/StagingBuffer.h" #include "VideoBackends/Vulkan/StateTracker.h" +#include "VideoBackends/Vulkan/VKDebug.h" #include "VideoBackends/Vulkan/VKGfx.h" #include "VideoBackends/Vulkan/VulkanContext.h" @@ -64,6 +65,8 @@ std::vector VKBoundingBox::Read(u32 index, u32 length) m_readback_buffer->FlushGPUCache(g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); + EmitDebugMarker(VkDebugCommand::BoundingBoxRead); + // Wait until these commands complete. VKGfx::GetInstance()->ExecuteCommandBuffer(false, true); @@ -98,6 +101,8 @@ void VKBoundingBox::Write(u32 index, std::span values) g_command_buffer_mgr->GetCurrentCommandBuffer(), m_gpu_buffer, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, 0, BUFFER_SIZE, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + + EmitDebugMarker(VkDebugCommand::BoundingBoxWrite); } bool VKBoundingBox::CreateGPUBuffer() diff --git a/Source/Core/VideoBackends/Vulkan/VKDebug.cpp b/Source/Core/VideoBackends/Vulkan/VKDebug.cpp new file mode 100644 index 0000000000..d3dddd8404 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKDebug.cpp @@ -0,0 +1,195 @@ +// Copyright 2016 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VKDebug.h" + +#include "Common/Assert.h" +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/StateTracker.h" + +namespace Vulkan +{ +// TODO: Add better implementation using VK_NV_device_diagnostic_checkpoints and +// VK_AMD_buffer_marker + +constexpr u64 STAGING_BUFFER_SIZE = 4 << 20; + +VkDebug::VkDebug(bool enabled) : m_enabled(enabled) +{ + m_readback_buffer = StagingBuffer::Create(STAGING_BUFFER_TYPE_READBACK, sizeof(u64), + VK_BUFFER_USAGE_TRANSFER_DST_BIT); + m_readback_buffer->Map(); + u64* ptr = reinterpret_cast(m_readback_buffer->GetMapPointer()); + *ptr = 0; + m_readback_buffer->Unmap(); +} + +void VkDebug::Reset() +{ + m_staging_buffer_index = 0; + m_staging_buffer_offset = 0ul; + m_markers.clear(); +} + +void VkDebug::EnsureStagingBufferCapacity() +{ + if (m_buffers.size() != 0 && m_staging_buffer_offset < STAGING_BUFFER_SIZE - sizeof(u64)) + [[likely]] + { + return; + } + + if (m_buffers.size() != 0) + { + m_buffers[m_staging_buffer_index]->Unmap(); + } + + std::unique_ptr buffer = StagingBuffer::Create( + STAGING_BUFFER_TYPE_UPLOAD, STAGING_BUFFER_SIZE, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + buffer->Map(); + m_buffers.push_back(std::move(buffer)); + m_staging_buffer_offset = 0ul; + + if (m_buffers.size() != 1) + { + m_staging_buffer_index++; + } +} + +void VkDebug::EmitMarker(VkDebugCommand cmd, u64 aux0, u64 aux1, u64 aux2, u64 aux3) +{ + bool is_in_renderpass = StateTracker::GetInstance()->InRenderPass(); + StateTracker::GetInstance()->EndRenderPass(); + + EnsureStagingBufferCapacity(); + std::unique_ptr& buffer = m_buffers[m_staging_buffer_index]; + u64* ptr = reinterpret_cast(buffer->GetMapPointer() + m_staging_buffer_offset); + uint64_t seq = m_sequence_number++; + *ptr = seq; + std::array aux = {aux0, aux1, aux2, aux3}; + VkDebugMarker marker = {aux, seq, cmd}; + m_markers.emplace_back(marker); + + VkMemoryBarrier memory_barrier = {VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, + VK_ACCESS_MEMORY_WRITE_BIT, + VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT}; + vkCmdPipelineBarrier(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, + &memory_barrier, 0, nullptr, 0, nullptr); + VkBufferCopy copy = {m_staging_buffer_offset, 0, sizeof(u64)}; + vkCmdCopyBuffer(g_command_buffer_mgr->GetCurrentCommandBuffer(), buffer->GetBuffer(), + m_readback_buffer->GetBuffer(), 1, ©); + m_readback_buffer->FlushGPUCache(g_command_buffer_mgr->GetCurrentCommandBuffer(), + VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + sizeof(u64)); + + m_staging_buffer_offset += sizeof(u64); + + if (is_in_renderpass) + StateTracker::GetInstance()->BeginRenderPass(); +} + +const VkDebugMarker* VkDebug::FindFault() +{ + m_readback_buffer->InvalidateCPUCache(0, sizeof(u64)); + m_readback_buffer->Map(); + u64* ptr = reinterpret_cast(m_readback_buffer->GetMapPointer()); + u64 seq = *ptr; + m_readback_buffer->Unmap(); + + auto result = m_markers.end(); + for (auto iter = m_markers.begin(); iter != m_markers.end(); iter++) + { + if (iter->sequence_number == seq) + { + result = iter; + break; + } + } + if (result == m_markers.end()) + { + return nullptr; + } + result++; + if (result == m_markers.end()) + { + return nullptr; + } + return &*result; +} + +void VkDebug::PrintFault() +{ + const VkDebugMarker* fault = FindFault(); + if (fault == nullptr) + { + return; + } + + std::string command_name; + switch (fault->cmd) + { + case VkDebugCommand::Draw: + command_name = "Draw"; + break; + + case VkDebugCommand::DrawIndexed: + command_name = "DrawIndexed"; + break; + + case VkDebugCommand::Dispatch: + command_name = "Dispatch"; + break; + + case VkDebugCommand::BoundingBoxRead: + command_name = "BoundingBoxRead"; + break; + + case VkDebugCommand::ClearRegion: + command_name = "ClearRegion"; + break; + + case VkDebugCommand::DisableQuery: + command_name = "DisableQuery"; + break; + + case VkDebugCommand::ResetQuery: + command_name = "ResetQuery"; + break; + + case VkDebugCommand::StagingCopyFromTexture: + command_name = "StagingCopyFromTexture"; + break; + + case VkDebugCommand::StagingCopyToTexture: + command_name = "StagingCopyToTexture"; + break; + + case VkDebugCommand::StagingCopyRectFromTexture: + command_name = "StagingCopyRectFromTexture"; + break; + + case VkDebugCommand::ReadbackQuery: + command_name = "ReadbackQuery"; + break; + + case VkDebugCommand::TransitionToComputeLayout: + command_name = "TransitionToComputeLayout"; + break; + + case VkDebugCommand::TransitionToLayout: + command_name = "TransitionToLayout"; + break; + + default: + command_name = fmt::format("{}", uint32_t(fault->cmd)); + break; + } + + ERROR_LOG_FMT(HOST_GPU, "Vulkan fault in {}, aux0: {}, aux1: {}, aux2: {}, aux3: {}", + command_name, fault->aux[0], fault->aux[1], fault->aux[2], fault->aux[3]); +} +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKDebug.h b/Source/Core/VideoBackends/Vulkan/VKDebug.h new file mode 100644 index 0000000000..97ab2dd1d3 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/VKDebug.h @@ -0,0 +1,66 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "VideoBackends/Vulkan/Constants.h" +#include "VideoBackends/Vulkan/StagingBuffer.h" +#include "VideoCommon/AbstractGfx.h" +#include "VideoCommon/Constants.h" + +namespace Vulkan +{ +enum class VkDebugCommand +{ + Draw, + DrawIndexed, + Dispatch, + BoundingBoxRead, + BoundingBoxWrite, + DisableQuery, + ResetQuery, + ReadbackQuery, + ClearRegion, + TransitionToLayout, + TransitionToComputeLayout, + StagingCopyFromTexture, + StagingCopyToTexture, + StagingCopyRectFromTexture, +}; + +struct VkDebugMarker +{ + std::array aux; + u64 sequence_number; + VkDebugCommand cmd; +}; + +class VkDebug +{ +public: + VkDebug(bool enabled); + void EmitMarker(VkDebugCommand cmd, u64 aux0, u64 aux1, u64 aux2, u64 aux3); + void Reset(); + void PrintFault(); + bool IsEnabled() const { return m_enabled; } + +private: + void EnsureStagingBufferCapacity(); + const VkDebugMarker* FindFault(); + +private: + bool m_enabled; + u64 m_sequence_number = 0; + u64 m_staging_buffer_index = 0; + u64 m_staging_buffer_offset = 0; + std::vector> m_buffers; + std::vector m_markers; + std::unique_ptr m_readback_buffer; +}; +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKGfx.cpp b/Source/Core/VideoBackends/Vulkan/VKGfx.cpp index ea4d972864..d9487a63cd 100644 --- a/Source/Core/VideoBackends/Vulkan/VKGfx.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKGfx.cpp @@ -209,6 +209,8 @@ void VKGfx::ClearRegion(const MathUtil::Rectangle& target_rc, bool color_en return; AbstractGfx::ClearRegion(target_rc, color_enable, alpha_enable, z_enable, color, z); + + EmitDebugMarker(VkDebugCommand::ClearRegion); } void VKGfx::Flush() @@ -581,6 +583,10 @@ void VKGfx::Draw(u32 base_vertex, u32 num_vertices) return; vkCmdDraw(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_vertices, 1, base_vertex, 0); + + const VKPipeline* pipeline = StateTracker::GetInstance()->GetPipeline(); + EmitDebugMarker(VkDebugCommand::Draw, pipeline->GetVSHash(), pipeline->GetPSHash(), + pipeline->GetGSHash(), num_vertices); } void VKGfx::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) @@ -590,6 +596,10 @@ void VKGfx::DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) vkCmdDrawIndexed(g_command_buffer_mgr->GetCurrentCommandBuffer(), num_indices, 1, base_index, base_vertex, 0); + + const VKPipeline* pipeline = StateTracker::GetInstance()->GetPipeline(); + EmitDebugMarker(VkDebugCommand::DrawIndexed, pipeline->GetVSHash(), pipeline->GetPSHash(), + pipeline->GetGSHash(), num_indices); } void VKGfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, @@ -598,6 +608,8 @@ void VKGfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, StateTracker::GetInstance()->SetComputeShader(static_cast(shader)); if (StateTracker::GetInstance()->BindCompute()) vkCmdDispatch(g_command_buffer_mgr->GetCurrentCommandBuffer(), groups_x, groups_y, groups_z); + + EmitDebugMarker(VkDebugCommand::Dispatch, 0, groups_x, groups_y, groups_z); } SurfaceInfo VKGfx::GetSurfaceInfo() const diff --git a/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp b/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp index c6aee79327..1282e86994 100644 --- a/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKPerfQuery.cpp @@ -84,6 +84,7 @@ void PerfQuery::DisableQuery(PerfQueryGroup group) m_query_next_pos = (m_query_next_pos + 1) % PERF_QUERY_BUFFER_SIZE; m_query_count.fetch_add(1, std::memory_order_relaxed); } + EmitDebugMarker(VkDebugCommand::DisableQuery); } void PerfQuery::ResetQuery() @@ -100,6 +101,7 @@ void PerfQuery::ResetQuery() PERF_QUERY_BUFFER_SIZE); std::memset(m_query_buffer.data(), 0, sizeof(ActiveQuery) * m_query_buffer.size()); + EmitDebugMarker(VkDebugCommand::ResetQuery); } u32 PerfQuery::GetQueryResult(PerfQueryType type) @@ -230,6 +232,8 @@ void PerfQuery::ReadbackQueries(u32 query_count) m_query_readback_pos = (m_query_readback_pos + query_count) % PERF_QUERY_BUFFER_SIZE; m_query_count.fetch_sub(query_count, std::memory_order_relaxed); + + EmitDebugMarker(VkDebugCommand::ReadbackQuery); } void PerfQuery::PartialFlush(bool blocking) diff --git a/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp b/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp index 83cd9367b5..5318a639ce 100644 --- a/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp @@ -24,6 +24,14 @@ VKPipeline::VKPipeline(const AbstractPipelineConfig& config, VkPipeline pipeline : AbstractPipeline(config), m_pipeline(pipeline), m_pipeline_layout(pipeline_layout), m_usage(usage) { + if (config.pixel_shader) + m_psHash = static_cast(config.pixel_shader)->GetHash(); + + if (config.vertex_shader) + m_vsHash = static_cast(config.vertex_shader)->GetHash(); + + if (config.geometry_shader) + m_gsHash = static_cast(config.geometry_shader)->GetHash(); } VKPipeline::~VKPipeline() diff --git a/Source/Core/VideoBackends/Vulkan/VKPipeline.h b/Source/Core/VideoBackends/Vulkan/VKPipeline.h index 82bf1f22c7..839e6a8315 100644 --- a/Source/Core/VideoBackends/Vulkan/VKPipeline.h +++ b/Source/Core/VideoBackends/Vulkan/VKPipeline.h @@ -22,10 +22,17 @@ public: AbstractPipelineUsage GetUsage() const { return m_usage; } static std::unique_ptr Create(const AbstractPipelineConfig& config); + u64 GetVSHash() const { return m_vsHash; } + u64 GetPSHash() const { return m_psHash; } + u64 GetGSHash() const { return m_gsHash; } + private: VkPipeline m_pipeline; VkPipelineLayout m_pipeline_layout; AbstractPipelineUsage m_usage; + u64 m_vsHash = 0; + u64 m_psHash = 0; + u64 m_gsHash = 0; }; } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKShader.cpp b/Source/Core/VideoBackends/Vulkan/VKShader.cpp index 81d7c23789..46767659ce 100644 --- a/Source/Core/VideoBackends/Vulkan/VKShader.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKShader.cpp @@ -5,6 +5,7 @@ #include "Common/Align.h" #include "Common/Assert.h" +#include "Common/Hash.h" #include "VideoBackends/Vulkan/ObjectCache.h" #include "VideoBackends/Vulkan/ShaderCompiler.h" @@ -15,9 +16,9 @@ namespace Vulkan { VKShader::VKShader(ShaderStage stage, std::vector spv, VkShaderModule mod, - std::string_view name) + std::string_view name, u64 hash) : AbstractShader(stage), m_spv(std::move(spv)), m_module(mod), - m_compute_pipeline(VK_NULL_HANDLE), m_name(name) + m_compute_pipeline(VK_NULL_HANDLE), m_name(name), m_hash(hash) { if (!m_name.empty() && g_ActiveConfig.backend_info.bSupportsSettingObjectNames) { @@ -30,9 +31,10 @@ VKShader::VKShader(ShaderStage stage, std::vector spv, VkShaderModule mod, } } -VKShader::VKShader(std::vector spv, VkPipeline compute_pipeline, std::string_view name) +VKShader::VKShader(std::vector spv, VkPipeline compute_pipeline, std::string_view name, + u64 hash) : AbstractShader(ShaderStage::Compute), m_spv(std::move(spv)), m_module(VK_NULL_HANDLE), - m_compute_pipeline(compute_pipeline), m_name(name) + m_compute_pipeline(compute_pipeline), m_name(name), m_hash(hash) { if (!m_name.empty() && g_ActiveConfig.backend_info.bSupportsSettingObjectNames) { @@ -61,12 +63,12 @@ AbstractShader::BinaryData VKShader::GetBinary() const } static std::unique_ptr -CreateShaderObject(ShaderStage stage, ShaderCompiler::SPIRVCodeVector spv, std::string_view name) +CreateShaderObject(ShaderStage stage, ShaderCompiler::CompiledSPIRV spv, std::string_view name) { VkShaderModuleCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - info.codeSize = spv.size() * sizeof(u32); - info.pCode = spv.data(); + info.codeSize = spv.code.size() * sizeof(u32); + info.pCode = spv.code.data(); VkShaderModule mod; VkResult res = vkCreateShaderModule(g_vulkan_context->GetDevice(), &info, nullptr, &mod); @@ -78,7 +80,7 @@ CreateShaderObject(ShaderStage stage, ShaderCompiler::SPIRVCodeVector spv, std:: // If it's a graphics shader, we defer pipeline creation. if (stage != ShaderStage::Compute) - return std::make_unique(stage, std::move(spv), mod, name); + return std::make_unique(stage, std::move(spv.code), mod, name, spv.hash); // If it's a compute shader, we create the pipeline straight away. const VkComputePipelineCreateInfo pipeline_info = { @@ -104,13 +106,13 @@ CreateShaderObject(ShaderStage stage, ShaderCompiler::SPIRVCodeVector spv, std:: return nullptr; } - return std::make_unique(std::move(spv), pipeline, name); + return std::make_unique(std::move(spv.code), pipeline, name, spv.hash); } std::unique_ptr VKShader::CreateFromSource(ShaderStage stage, std::string_view source, std::string_view name) { - std::optional spv; + std::optional spv; switch (stage) { case ShaderStage::Vertex: @@ -132,6 +134,7 @@ std::unique_ptr VKShader::CreateFromSource(ShaderStage stage, std::str if (!spv) return nullptr; + INFO_LOG_FMT(HOST_GPU, "Shader: {}", source); return CreateShaderObject(stage, std::move(*spv), name); } @@ -144,7 +147,9 @@ std::unique_ptr VKShader::CreateFromBinary(ShaderStage stage, const vo if (length > 0) std::memcpy(spv.data(), data, length); - return CreateShaderObject(stage, std::move(spv), name); + ShaderCompiler::CompiledSPIRV compiled_spv = {std::move(spv), 0ul}; + + return CreateShaderObject(stage, std::move(compiled_spv), name); } } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKShader.h b/Source/Core/VideoBackends/Vulkan/VKShader.h index 8414983aaa..85ab502c10 100644 --- a/Source/Core/VideoBackends/Vulkan/VKShader.h +++ b/Source/Core/VideoBackends/Vulkan/VKShader.h @@ -18,14 +18,17 @@ namespace Vulkan class VKShader final : public AbstractShader { public: - VKShader(ShaderStage stage, std::vector spv, VkShaderModule mod, std::string_view name); - VKShader(std::vector spv, VkPipeline compute_pipeline, std::string_view name); + VKShader(ShaderStage stage, std::vector spv, VkShaderModule mod, std::string_view name, + u64 hash); + VKShader(std::vector spv, VkPipeline compute_pipeline, std::string_view name, u64 hash); ~VKShader() override; VkShaderModule GetShaderModule() const { return m_module; } VkPipeline GetComputePipeline() const { return m_compute_pipeline; } BinaryData GetBinary() const override; + u64 GetHash() const { return m_hash; } + static std::unique_ptr CreateFromSource(ShaderStage stage, std::string_view source, std::string_view name); static std::unique_ptr CreateFromBinary(ShaderStage stage, const void* data, @@ -36,6 +39,7 @@ private: VkShaderModule m_module; VkPipeline m_compute_pipeline; std::string m_name; + u64 m_hash = 0; }; } // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp index 6756305d1e..a261bc3ecb 100644 --- a/Source/Core/VideoBackends/Vulkan/VKTexture.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKTexture.cpp @@ -316,6 +316,8 @@ void VKTexture::CopyRectangleFromTexture(const AbstractTexture* src, // Only restore the source layout. Destination is restored by FinishedRendering(). src_texture->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(), old_src_layout); + + EmitDebugMarker(VkDebugCommand::StagingCopyRectFromTexture); } void VKTexture::ResolveFromTexture(const AbstractTexture* src, const MathUtil::Rectangle& rect, @@ -604,6 +606,8 @@ void VKTexture::TransitionToLayout(VkCommandBuffer command_buffer, VkImageLayout &barrier); m_layout = new_layout; + + EmitDebugMarker(VkDebugCommand::TransitionToLayout); } void VKTexture::TransitionToLayout(VkCommandBuffer command_buffer, @@ -707,6 +711,8 @@ void VKTexture::TransitionToLayout(VkCommandBuffer command_buffer, vkCmdPipelineBarrier(command_buffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier); + + EmitDebugMarker(VkDebugCommand::TransitionToComputeLayout); } VKStagingTexture::VKStagingTexture(PrivateTag, StagingTextureType type, const TextureConfig& config, @@ -880,6 +886,8 @@ void VKStagingTexture::CopyFromTexture(const AbstractTexture* src, m_needs_flush = true; m_flush_fence_counter = g_command_buffer_mgr->GetCurrentFenceCounter(); + + EmitDebugMarker(VkDebugCommand::StagingCopyFromTexture); } void VKStagingTexture::CopyFromTextureToLinearImage(const VKTexture* src_tex, @@ -972,6 +980,8 @@ void VKStagingTexture::CopyToTexture(const MathUtil::Rectangle& src_rect, A m_needs_flush = true; m_flush_fence_counter = g_command_buffer_mgr->GetCurrentFenceCounter(); + + EmitDebugMarker(VkDebugCommand::StagingCopyToTexture); } bool VKStagingTexture::Map() diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index 7d11645949..7c4e43398b 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -351,6 +351,12 @@ void ShaderCache::ClearPipelineCache(T& cache, Y& disk_cache) void ShaderCache::LoadCaches() { + if (g_ActiveConfig.bEnableBreadcrumbs) + { + // We want to print all shaders to the log when using breadcrumbs. + return; + } + // Ubershader caches, if present. if (g_ActiveConfig.backend_info.bSupportsShaderBinaries) { diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 51ab90fb7e..a8ec833775 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -142,6 +142,7 @@ void VideoConfig::Refresh() bDisableFog = Config::Get(Config::GFX_DISABLE_FOG); bBorderlessFullscreen = Config::Get(Config::GFX_BORDERLESS_FULLSCREEN); bEnableValidationLayer = Config::Get(Config::GFX_ENABLE_VALIDATION_LAYER); + bEnableBreadcrumbs = Config::Get(Config::GFX_ENABLE_BREADCRUMBS); bBackendMultithreading = Config::Get(Config::GFX_BACKEND_MULTITHREADING); iCommandBufferExecuteInterval = Config::Get(Config::GFX_COMMAND_BUFFER_EXECUTE_INTERVAL); bShaderCache = Config::Get(Config::GFX_SHADER_CACHE); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 31c10059d5..85d38fd00d 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -245,6 +245,9 @@ struct VideoConfig final // Enable API validation layers, currently only supported with Vulkan. bool bEnableValidationLayer = false; + // Enable GPU breadcrumbs, currently only supported with Vulkan. + bool bEnableBreadcrumbs = false; + // Multithreaded submission, currently only supported with Vulkan. bool bBackendMultithreading = true;