VideoBackends:Vulkan: Add GPU timeline breadcrumbs for debugging

This commit is contained in:
Robin Kertels 2023-09-20 23:48:19 +02:00
parent 6aacbc4c35
commit 3aa2ef2fe1
No known key found for this signature in database
GPG Key ID: 3824904F14D40757
21 changed files with 475 additions and 29 deletions

View File

@ -89,6 +89,7 @@ const Info<bool> GFX_BORDERLESS_FULLSCREEN{{System::GFX, "Settings", "Borderless
false};
const Info<bool> GFX_ENABLE_VALIDATION_LAYER{{System::GFX, "Settings", "EnableValidationLayer"},
false};
const Info<bool> GFX_ENABLE_BREADCRUMBS{{System::GFX, "Settings", "EnableBreadcrumbs"}, false};
const Info<bool> GFX_BACKEND_MULTITHREADING{{System::GFX, "Settings", "BackendMultithreading"},
true};

View File

@ -82,6 +82,7 @@ extern const Info<bool> GFX_ENABLE_WIREFRAME;
extern const Info<bool> GFX_DISABLE_FOG;
extern const Info<bool> GFX_BORDERLESS_FULLSCREEN;
extern const Info<bool> GFX_ENABLE_VALIDATION_LAYER;
extern const Info<bool> GFX_ENABLE_BREADCRUMBS;
extern const Info<bool> GFX_BACKEND_MULTITHREADING;
extern const Info<int> GFX_COMMAND_BUFFER_EXECUTE_INTERVAL;
extern const Info<bool> GFX_SHADER_CACHE;

View File

@ -1279,6 +1279,7 @@
<ClCompile Include="VideoBackends\Vulkan\VKTexture.cpp" />
<ClCompile Include="VideoBackends\Vulkan\VKVertexFormat.cpp" />
<ClCompile Include="VideoBackends\Vulkan\VKVertexManager.cpp" />
<ClCompile Include="VideoBackends\Vulkan\VKDebug.cpp" />
<ClCompile Include="VideoBackends\Vulkan\VulkanContext.cpp" />
<ClCompile Include="VideoBackends\Vulkan\VulkanLoader.cpp" />
<ClCompile Include="VideoCommon\AbstractFramebuffer.cpp" />

View File

@ -31,6 +31,8 @@ add_library(videovulkan
VKVertexFormat.h
VKVertexManager.cpp
VKVertexManager.h
VKDebug.cpp
VKDebug.h
VulkanContext.cpp
VulkanContext.h
VulkanLoader.cpp

View File

@ -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<VkDebug>(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<int>(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<int>(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<CommandBufferManager> g_command_buffer_mgr;
} // namespace Vulkan

View File

@ -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<VkDescriptorPool> descriptor_pools;
u32 current_descriptor_pool_index = 0;
std::unique_ptr<VkDebug> debug = nullptr;
};
FrameResources& GetCurrentFrameResources() { return m_frame_resources[m_current_frame]; }
@ -164,4 +170,11 @@ private:
extern std::unique_ptr<CommandBufferManager> 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

View File

@ -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 <cstddef>
#include <string>
#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<SPIRVCodeVector> CompileVertexShader(std::string_view source_code)
std::optional<CompiledSPIRV> 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<const u8*>(code.c_str()), u32(code.size()), 128);
std::optional<SPIRVCodeVector> 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>(CompiledSPIRV{std::move(spirv.value()), hash});
}
return std::nullopt;
}
std::optional<SPIRVCodeVector> CompileGeometryShader(std::string_view source_code)
std::optional<CompiledSPIRV> 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<const u8*>(code.c_str()), u32(code.size()), 128);
std::optional<SPIRVCodeVector> 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>(CompiledSPIRV{std::move(spirv.value()), hash});
}
return std::nullopt;
}
std::optional<SPIRVCodeVector> CompileFragmentShader(std::string_view source_code)
std::optional<CompiledSPIRV> 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<const u8*>(code.c_str()), u32(code.size()), 128);
std::optional<SPIRVCodeVector> 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>(CompiledSPIRV{std::move(spirv.value()), hash});
}
return std::nullopt;
}
std::optional<SPIRVCodeVector> CompileComputeShader(std::string_view source_code)
std::optional<CompiledSPIRV> 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<const u8*>(code.c_str()), u32(code.size()), 128);
std::optional<SPIRVCodeVector> 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>(CompiledSPIRV{std::move(spirv.value()), hash});
}
return std::nullopt;
}
} // namespace Vulkan::ShaderCompiler

View File

@ -16,15 +16,21 @@ namespace Vulkan::ShaderCompiler
using SPIRVCodeType = u32;
using SPIRVCodeVector = std::vector<SPIRVCodeType>;
struct CompiledSPIRV
{
SPIRVCodeVector code;
u64 hash;
};
// Compile a vertex shader to SPIR-V.
std::optional<SPIRVCodeVector> CompileVertexShader(std::string_view source_code);
std::optional<CompiledSPIRV> CompileVertexShader(std::string_view source_code);
// Compile a geometry shader to SPIR-V.
std::optional<SPIRVCodeVector> CompileGeometryShader(std::string_view source_code);
std::optional<CompiledSPIRV> CompileGeometryShader(std::string_view source_code);
// Compile a fragment shader to SPIR-V.
std::optional<SPIRVCodeVector> CompileFragmentShader(std::string_view source_code);
std::optional<CompiledSPIRV> CompileFragmentShader(std::string_view source_code);
// Compile a compute shader to SPIR-V.
std::optional<SPIRVCodeVector> CompileComputeShader(std::string_view source_code);
std::optional<CompiledSPIRV> CompileComputeShader(std::string_view source_code);
} // namespace Vulkan::ShaderCompiler

View File

@ -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<BBoxType> 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<const BBoxType> 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()

View File

@ -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<u64*>(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<StagingBuffer> 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<StagingBuffer>& buffer = m_buffers[m_staging_buffer_index];
u64* ptr = reinterpret_cast<u64*>(buffer->GetMapPointer() + m_staging_buffer_offset);
uint64_t seq = m_sequence_number++;
*ptr = seq;
std::array<u64, 4> 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, &copy);
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<u64*>(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

View File

@ -0,0 +1,66 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include <optional>
#include <string_view>
#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<u64, 4> 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<std::unique_ptr<StagingBuffer>> m_buffers;
std::vector<VkDebugMarker> m_markers;
std::unique_ptr<StagingBuffer> m_readback_buffer;
};
} // namespace Vulkan

View File

@ -209,6 +209,8 @@ void VKGfx::ClearRegion(const MathUtil::Rectangle<int>& 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<const VKShader*>(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

View File

@ -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)

View File

@ -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<const VKShader*>(config.pixel_shader)->GetHash();
if (config.vertex_shader)
m_vsHash = static_cast<const VKShader*>(config.vertex_shader)->GetHash();
if (config.geometry_shader)
m_gsHash = static_cast<const VKShader*>(config.geometry_shader)->GetHash();
}
VKPipeline::~VKPipeline()

View File

@ -22,10 +22,17 @@ public:
AbstractPipelineUsage GetUsage() const { return m_usage; }
static std::unique_ptr<VKPipeline> 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

View File

@ -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<u32> 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<u32> spv, VkShaderModule mod,
}
}
VKShader::VKShader(std::vector<u32> spv, VkPipeline compute_pipeline, std::string_view name)
VKShader::VKShader(std::vector<u32> 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<VKShader>
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<VKShader>(stage, std::move(spv), mod, name);
return std::make_unique<VKShader>(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<VKShader>(std::move(spv), pipeline, name);
return std::make_unique<VKShader>(std::move(spv.code), pipeline, name, spv.hash);
}
std::unique_ptr<VKShader> VKShader::CreateFromSource(ShaderStage stage, std::string_view source,
std::string_view name)
{
std::optional<ShaderCompiler::SPIRVCodeVector> spv;
std::optional<ShaderCompiler::CompiledSPIRV> spv;
switch (stage)
{
case ShaderStage::Vertex:
@ -132,6 +134,7 @@ std::unique_ptr<VKShader> 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> 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

View File

@ -18,14 +18,17 @@ namespace Vulkan
class VKShader final : public AbstractShader
{
public:
VKShader(ShaderStage stage, std::vector<u32> spv, VkShaderModule mod, std::string_view name);
VKShader(std::vector<u32> spv, VkPipeline compute_pipeline, std::string_view name);
VKShader(ShaderStage stage, std::vector<u32> spv, VkShaderModule mod, std::string_view name,
u64 hash);
VKShader(std::vector<u32> 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<VKShader> CreateFromSource(ShaderStage stage, std::string_view source,
std::string_view name);
static std::unique_ptr<VKShader> 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

View File

@ -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<int>& 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<int>& src_rect, A
m_needs_flush = true;
m_flush_fence_counter = g_command_buffer_mgr->GetCurrentFenceCounter();
EmitDebugMarker(VkDebugCommand::StagingCopyToTexture);
}
bool VKStagingTexture::Map()

View File

@ -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)
{

View File

@ -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);

View File

@ -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;