From d735f5c4580b2ce1b2cc3d6bbcd86901b02a83ef Mon Sep 17 00:00:00 2001 From: GPUCode <47210458+GPUCode@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:24:24 +0300 Subject: [PATCH] renderer_vulkan: Add vulkan initialization code (#6620) * common: Move dynamic library to common * This is so that video_core can use it * logging: Add vulkan log target * common: Allow defered library loading * Also add some comments to the functions * renderer_vulkan: Add vulkan initialization code * renderer_vulkan: Address feedback --- src/citra_qt/main.cpp | 11 +- .../dynamic_library/dynamic_library.cpp | 51 +- src/common/dynamic_library/dynamic_library.h | 27 +- src/common/dynamic_library/fdk-aac.cpp | 5 +- src/common/dynamic_library/fdk-aac.h | 3 - src/common/dynamic_library/ffmpeg.cpp | 22 +- src/common/dynamic_library/ffmpeg.h | 3 - src/common/logging/backend.cpp | 1 + src/common/logging/log.h | 2 + src/common/settings.h | 1 + src/video_core/CMakeLists.txt | 10 +- src/video_core/renderer_vulkan/pica_to_vk.h | 198 ++++++ src/video_core/renderer_vulkan/vk_common.cpp | 12 + src/video_core/renderer_vulkan/vk_common.h | 16 + .../renderer_vulkan/vk_instance.cpp | 581 ++++++++++++++++++ src/video_core/renderer_vulkan/vk_instance.h | 287 +++++++++ .../renderer_vulkan/vk_platform.cpp | 366 +++++++++++ src/video_core/renderer_vulkan/vk_platform.h | 34 + 18 files changed, 1576 insertions(+), 54 deletions(-) create mode 100644 src/video_core/renderer_vulkan/pica_to_vk.h create mode 100644 src/video_core/renderer_vulkan/vk_common.cpp create mode 100644 src/video_core/renderer_vulkan/vk_common.h create mode 100644 src/video_core/renderer_vulkan/vk_instance.cpp create mode 100644 src/video_core/renderer_vulkan/vk_instance.h create mode 100644 src/video_core/renderer_vulkan/vk_platform.cpp create mode 100644 src/video_core/renderer_vulkan/vk_platform.h diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 7c857761f..ea019d825 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -64,6 +64,7 @@ #include "common/arch.h" #include "common/common_paths.h" #include "common/detached_tasks.h" +#include "common/dynamic_library/dynamic_library.h" #include "common/file_util.h" #include "common/literals.h" #include "common/logging/backend.h" @@ -2242,11 +2243,11 @@ void GMainWindow::OnOpenFFmpeg() { } static const std::array library_names = { - DynamicLibrary::DynamicLibrary::GetLibraryName("avcodec", LIBAVCODEC_VERSION_MAJOR), - DynamicLibrary::DynamicLibrary::GetLibraryName("avfilter", LIBAVFILTER_VERSION_MAJOR), - DynamicLibrary::DynamicLibrary::GetLibraryName("avformat", LIBAVFORMAT_VERSION_MAJOR), - DynamicLibrary::DynamicLibrary::GetLibraryName("avutil", LIBAVUTIL_VERSION_MAJOR), - DynamicLibrary::DynamicLibrary::GetLibraryName("swresample", LIBSWRESAMPLE_VERSION_MAJOR), + Common::DynamicLibrary::GetLibraryName("avcodec", LIBAVCODEC_VERSION_MAJOR), + Common::DynamicLibrary::GetLibraryName("avfilter", LIBAVFILTER_VERSION_MAJOR), + Common::DynamicLibrary::GetLibraryName("avformat", LIBAVFORMAT_VERSION_MAJOR), + Common::DynamicLibrary::GetLibraryName("avutil", LIBAVUTIL_VERSION_MAJOR), + Common::DynamicLibrary::GetLibraryName("swresample", LIBSWRESAMPLE_VERSION_MAJOR), }; for (auto& library_name : library_names) { diff --git a/src/common/dynamic_library/dynamic_library.cpp b/src/common/dynamic_library/dynamic_library.cpp index b90cb1424..f5b348537 100644 --- a/src/common/dynamic_library/dynamic_library.cpp +++ b/src/common/dynamic_library/dynamic_library.cpp @@ -10,29 +10,11 @@ #endif #include "dynamic_library.h" -namespace DynamicLibrary { +namespace Common { DynamicLibrary::DynamicLibrary(std::string_view name, int major, int minor) { auto full_name = GetLibraryName(name, major, minor); -#if defined(_WIN32) - handle = reinterpret_cast(LoadLibraryA(full_name.c_str())); - if (!handle) { - DWORD error_message_id = GetLastError(); - LPSTR message_buffer = nullptr; - size_t size = - FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - reinterpret_cast(&message_buffer), 0, nullptr); - std::string message(message_buffer, size); - load_error = message; - } -#else - handle = dlopen(full_name.c_str(), RTLD_LAZY); - if (!handle) { - load_error = dlerror(); - } -#endif // defined(_WIN32) + void(Load(full_name)); } DynamicLibrary::~DynamicLibrary() { @@ -46,7 +28,32 @@ DynamicLibrary::~DynamicLibrary() { } } -void* DynamicLibrary::GetRawSymbol(std::string_view name) { +bool DynamicLibrary::Load(std::string_view filename) { +#if defined(_WIN32) + handle = reinterpret_cast(LoadLibraryA(filename.data())); + if (!handle) { + DWORD error_message_id = GetLastError(); + LPSTR message_buffer = nullptr; + size_t size = + FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&message_buffer), 0, nullptr); + std::string message(message_buffer, size); + load_error = message; + return false; + } +#else + handle = dlopen(filename.data(), RTLD_LAZY); + if (!handle) { + load_error = dlerror(); + return false; + } +#endif // defined(_WIN32) + return true; +} + +void* DynamicLibrary::GetRawSymbol(std::string_view name) const { #if defined(_WIN32) return reinterpret_cast(GetProcAddress(reinterpret_cast(handle), name.data())); #else @@ -84,4 +91,4 @@ std::string DynamicLibrary::GetLibraryName(std::string_view name, int major, int #endif } -} // namespace DynamicLibrary +} // namespace Common diff --git a/src/common/dynamic_library/dynamic_library.h b/src/common/dynamic_library/dynamic_library.h index 9846c8326..2fbb7a1b8 100644 --- a/src/common/dynamic_library/dynamic_library.h +++ b/src/common/dynamic_library/dynamic_library.h @@ -5,35 +5,46 @@ #pragma once #include -#include "common/common_types.h" -namespace DynamicLibrary { +namespace Common { class DynamicLibrary { public: + explicit DynamicLibrary(); explicit DynamicLibrary(std::string_view name, int major = -1, int minor = -1); ~DynamicLibrary(); - bool IsLoaded() { + /// Returns true if the library is loaded, otherwise false. + [[nodiscard]] bool IsLoaded() { return handle != nullptr; } - std::string_view GetLoadError() { + /// Loads (or replaces) the handle with the specified library file name. + /// Returns true if the library was loaded and can be used. + [[nodiscard]] bool Load(std::string_view filename); + + /// Returns a string containing the last generated load error, if it occured. + [[nodiscard]] std::string_view GetLoadError() const { return load_error; } + /// Obtains the address of the specified symbol, automatically casting to the correct type. template - T GetSymbol(std::string_view name) { + [[nodiscard]] T GetSymbol(std::string_view name) const { return reinterpret_cast(GetRawSymbol(name)); } - static std::string GetLibraryName(std::string_view name, int major = -1, int minor = -1); + /// Returns the specified library name in platform-specific format. + /// Major/minor versions will not be included if set to -1. + /// If libname already contains the "lib" prefix, it will not be added again. + [[nodiscard]] static std::string GetLibraryName(std::string_view name, int major = -1, + int minor = -1); private: - void* GetRawSymbol(std::string_view name); + void* GetRawSymbol(std::string_view name) const; void* handle; std::string load_error; }; -} // namespace DynamicLibrary +} // namespace Common diff --git a/src/common/dynamic_library/fdk-aac.cpp b/src/common/dynamic_library/fdk-aac.cpp index d4b0bcd62..20dedfd07 100644 --- a/src/common/dynamic_library/fdk-aac.cpp +++ b/src/common/dynamic_library/fdk-aac.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/dynamic_library/dynamic_library.h" #include "common/dynamic_library/fdk-aac.h" #include "common/logging/log.h" @@ -15,7 +16,7 @@ aacDecoder_GetStreamInfo_func aacDecoder_GetStreamInfo; aacDecoder_DecodeFrame_func aacDecoder_DecodeFrame; aacDecoder_Fill_func aacDecoder_Fill; -static std::unique_ptr fdk_aac; +static std::unique_ptr fdk_aac; #define LOAD_SYMBOL(library, name) \ any_failed = any_failed || (name = library->GetSymbol(#name)) == nullptr @@ -25,7 +26,7 @@ bool LoadFdkAac() { return true; } - fdk_aac = std::make_unique("fdk-aac", 2); + fdk_aac = std::make_unique("fdk-aac", 2); if (!fdk_aac->IsLoaded()) { LOG_WARNING(Common, "Could not dynamically load libfdk-aac: {}", fdk_aac->GetLoadError()); fdk_aac.reset(); diff --git a/src/common/dynamic_library/fdk-aac.h b/src/common/dynamic_library/fdk-aac.h index 4c1dca4b8..a80e237eb 100644 --- a/src/common/dynamic_library/fdk-aac.h +++ b/src/common/dynamic_library/fdk-aac.h @@ -8,9 +8,6 @@ extern "C" { #include } -#include "common/common_types.h" -#include "common/dynamic_library/dynamic_library.h" - namespace DynamicLibrary::FdkAac { typedef INT (*aacDecoder_GetLibInfo_func)(LIB_INFO* info); diff --git a/src/common/dynamic_library/ffmpeg.cpp b/src/common/dynamic_library/ffmpeg.cpp index 1da17faf4..c9c1af33a 100644 --- a/src/common/dynamic_library/ffmpeg.cpp +++ b/src/common/dynamic_library/ffmpeg.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/dynamic_library/dynamic_library.h" #include "common/dynamic_library/ffmpeg.h" #include "common/logging/log.h" @@ -110,11 +111,11 @@ swr_free_func swr_free; swr_init_func swr_init; swresample_version_func swresample_version; -static std::unique_ptr avutil; -static std::unique_ptr avcodec; -static std::unique_ptr avfilter; -static std::unique_ptr avformat; -static std::unique_ptr swresample; +static std::unique_ptr avutil; +static std::unique_ptr avcodec; +static std::unique_ptr avfilter; +static std::unique_ptr avformat; +static std::unique_ptr swresample; #define LOAD_SYMBOL(library, name) \ any_failed = any_failed || (name = library->GetSymbol(#name)) == nullptr @@ -124,7 +125,7 @@ static bool LoadAVUtil() { return true; } - avutil = std::make_unique("avutil", LIBAVUTIL_VERSION_MAJOR); + avutil = std::make_unique("avutil", LIBAVUTIL_VERSION_MAJOR); if (!avutil->IsLoaded()) { LOG_WARNING(Common, "Could not dynamically load libavutil: {}", avutil->GetLoadError()); avutil.reset(); @@ -194,7 +195,7 @@ static bool LoadAVCodec() { return true; } - avcodec = std::make_unique("avcodec", LIBAVCODEC_VERSION_MAJOR); + avcodec = std::make_unique("avcodec", LIBAVCODEC_VERSION_MAJOR); if (!avcodec->IsLoaded()) { LOG_WARNING(Common, "Could not dynamically load libavcodec: {}", avcodec->GetLoadError()); avcodec.reset(); @@ -251,7 +252,7 @@ static bool LoadAVFilter() { return true; } - avfilter = std::make_unique("avfilter", LIBAVFILTER_VERSION_MAJOR); + avfilter = std::make_unique("avfilter", LIBAVFILTER_VERSION_MAJOR); if (!avfilter->IsLoaded()) { LOG_WARNING(Common, "Could not dynamically load libavfilter: {}", avfilter->GetLoadError()); avfilter.reset(); @@ -296,7 +297,7 @@ static bool LoadAVFormat() { return true; } - avformat = std::make_unique("avformat", LIBAVFORMAT_VERSION_MAJOR); + avformat = std::make_unique("avformat", LIBAVFORMAT_VERSION_MAJOR); if (!avformat->IsLoaded()) { LOG_WARNING(Common, "Could not dynamically load libavformat: {}", avformat->GetLoadError()); avformat.reset(); @@ -344,7 +345,8 @@ static bool LoadSWResample() { return true; } - swresample = std::make_unique("swresample", LIBSWRESAMPLE_VERSION_MAJOR); + swresample = + std::make_unique("swresample", LIBSWRESAMPLE_VERSION_MAJOR); if (!swresample->IsLoaded()) { LOG_WARNING(Common, "Could not dynamically load libswresample: {}", swresample->GetLoadError()); diff --git a/src/common/dynamic_library/ffmpeg.h b/src/common/dynamic_library/ffmpeg.h index 67eac1787..f79fef8da 100644 --- a/src/common/dynamic_library/ffmpeg.h +++ b/src/common/dynamic_library/ffmpeg.h @@ -15,9 +15,6 @@ extern "C" { #include } -#include "common/common_types.h" -#include "common/dynamic_library/dynamic_library.h" - namespace DynamicLibrary::FFmpeg { // avutil diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index bbf0b48f9..de5ce7b41 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -234,6 +234,7 @@ void DebuggerBackend::Write(const Entry& entry) { CLS(Render) \ SUB(Render, Software) \ SUB(Render, OpenGL) \ + SUB(Render, Vulkan) \ CLS(Audio) \ SUB(Audio, DSP) \ SUB(Audio, Sink) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index cf3cf0dea..8cd98db14 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -8,6 +8,7 @@ #include #include "common/common_types.h" #include "common/logging/formatter.h" + namespace Log { // trims up to and including the last of ../, ..\, src/, src\ in a string @@ -103,6 +104,7 @@ enum class Class : ClassType { Render, ///< Emulator video output and hardware acceleration Render_Software, ///< Software renderer backend Render_OpenGL, ///< OpenGL backend + Render_Vulkan, ///< Vulkan backend Audio, ///< Audio emulation Audio_DSP, ///< The HLE and LLE implementations of the DSP Audio_Sink, ///< Emulator audio output backend diff --git a/src/common/settings.h b/src/common/settings.h index f80205ba9..37754b495 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -432,6 +432,7 @@ struct Values { "graphics_api"}; Setting use_gles{false, "use_gles"}; Setting renderer_debug{false, "renderer_debug"}; + Setting dump_command_buffers{false, "dump_command_buffers"}; SwitchableSetting use_hw_shader{true, "use_hw_shader"}; SwitchableSetting use_disk_shader_cache{true, "use_disk_shader_cache"}; SwitchableSetting shaders_accurate_mul{true, "shaders_accurate_mul"}; diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 547ed2d22..025274e03 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -99,6 +99,13 @@ add_library(video_core STATIC renderer_software/sw_rasterizer.h renderer_software/sw_texturing.cpp renderer_software/sw_texturing.h + renderer_vulkan/pica_to_vk.h + renderer_vulkan/vk_common.cpp + renderer_vulkan/vk_common.h + renderer_vulkan/vk_instance.cpp + renderer_vulkan/vk_instance.h + renderer_vulkan/vk_platform.cpp + renderer_vulkan/vk_platform.h shader/debug_data.h shader/shader.cpp shader/shader.h @@ -127,7 +134,8 @@ target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE}) create_target_directory_groups(video_core) target_link_libraries(video_core PUBLIC citra_common citra_core) -target_link_libraries(video_core PRIVATE Boost::serialization dds-ktx glad json-headers nihstro-headers tsl::robin_map) +target_link_libraries(video_core PRIVATE Boost::serialization dds-ktx json-headers nihstro-headers tsl::robin_map) +target_link_libraries(video_core PRIVATE vulkan-headers vma glad) set_target_properties(video_core PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO}) if ("x86_64" IN_LIST ARCHITECTURE) diff --git a/src/video_core/renderer_vulkan/pica_to_vk.h b/src/video_core/renderer_vulkan/pica_to_vk.h new file mode 100644 index 000000000..a5546f1dd --- /dev/null +++ b/src/video_core/renderer_vulkan/pica_to_vk.h @@ -0,0 +1,198 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/logging/log.h" +#include "core/core.h" +#include "video_core/regs.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace PicaToVK { + +using TextureFilter = Pica::TexturingRegs::TextureConfig::TextureFilter; + +inline vk::Filter TextureFilterMode(TextureFilter mode) { + switch (mode) { + case TextureFilter::Linear: + return vk::Filter::eLinear; + case TextureFilter::Nearest: + return vk::Filter::eNearest; + default: + UNIMPLEMENTED_MSG("Unknown texture filtering mode {}", mode); + } + + return vk::Filter::eLinear; +} + +inline vk::SamplerMipmapMode TextureMipFilterMode(TextureFilter mip) { + switch (mip) { + case TextureFilter::Linear: + return vk::SamplerMipmapMode::eLinear; + case TextureFilter::Nearest: + return vk::SamplerMipmapMode::eNearest; + default: + UNIMPLEMENTED_MSG("Unknown texture mipmap filtering mode {}", mip); + } + + return vk::SamplerMipmapMode::eLinear; +} + +inline vk::SamplerAddressMode WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) { + static constexpr std::array wrap_mode_table{{ + vk::SamplerAddressMode::eClampToEdge, + vk::SamplerAddressMode::eClampToBorder, + vk::SamplerAddressMode::eRepeat, + vk::SamplerAddressMode::eMirroredRepeat, + // TODO(wwylele): ClampToEdge2 and ClampToBorder2 are not properly implemented here. See the + // comments in enum WrapMode. + vk::SamplerAddressMode::eClampToEdge, + vk::SamplerAddressMode::eClampToBorder, + vk::SamplerAddressMode::eRepeat, + vk::SamplerAddressMode::eRepeat, + }}; + + const auto index = static_cast(mode); + ASSERT_MSG(index < wrap_mode_table.size(), "Unknown texture wrap mode {}", index); + + if (index > 3) { + Core::System::GetInstance().TelemetrySession().AddField( + Common::Telemetry::FieldType::Session, "VideoCore_Pica_UnsupportedTextureWrapMode", + static_cast(index)); + LOG_WARNING(Render_Vulkan, "Using texture wrap mode {}", index); + } + + return wrap_mode_table[index]; +} + +inline vk::BlendOp BlendEquation(Pica::FramebufferRegs::BlendEquation equation) { + static constexpr std::array blend_equation_table{{ + vk::BlendOp::eAdd, + vk::BlendOp::eSubtract, + vk::BlendOp::eReverseSubtract, + vk::BlendOp::eMin, + vk::BlendOp::eMax, + }}; + + const auto index = static_cast(equation); + ASSERT_MSG(index < blend_equation_table.size(), "Unknown blend equation {}", index); + return blend_equation_table[index]; +} + +inline vk::BlendFactor BlendFunc(Pica::FramebufferRegs::BlendFactor factor) { + static constexpr std::array blend_func_table{{ + vk::BlendFactor::eZero, // BlendFactor::Zero + vk::BlendFactor::eOne, // BlendFactor::One + vk::BlendFactor::eSrcColor, // BlendFactor::SourceColor + vk::BlendFactor::eOneMinusSrcColor, // BlendFactor::OneMinusSourceColor + vk::BlendFactor::eDstColor, // BlendFactor::DestColor + vk::BlendFactor::eOneMinusDstColor, // BlendFactor::OneMinusDestColor + vk::BlendFactor::eSrcAlpha, // BlendFactor::SourceAlpha + vk::BlendFactor::eOneMinusSrcAlpha, // BlendFactor::OneMinusSourceAlpha + vk::BlendFactor::eDstAlpha, // BlendFactor::DestAlpha + vk::BlendFactor::eOneMinusDstAlpha, // BlendFactor::OneMinusDestAlpha + vk::BlendFactor::eConstantColor, // BlendFactor::ConstantColor + vk::BlendFactor::eOneMinusConstantColor, // BlendFactor::OneMinusConstantColor + vk::BlendFactor::eConstantAlpha, // BlendFactor::ConstantAlpha + vk::BlendFactor::eOneMinusConstantAlpha, // BlendFactor::OneMinusConstantAlpha + vk::BlendFactor::eSrcAlphaSaturate, // BlendFactor::SourceAlphaSaturate + }}; + + const auto index = static_cast(factor); + ASSERT_MSG(index < blend_func_table.size(), "Unknown blend factor {}", index); + return blend_func_table[index]; +} + +inline vk::LogicOp LogicOp(Pica::FramebufferRegs::LogicOp op) { + static constexpr std::array logic_op_table{{ + vk::LogicOp::eClear, // Clear + vk::LogicOp::eAnd, // And + vk::LogicOp::eAndReverse, // AndReverse + vk::LogicOp::eCopy, // Copy + vk::LogicOp::eSet, // Set + vk::LogicOp::eCopyInverted, // CopyInverted + vk::LogicOp::eNoOp, // NoOp + vk::LogicOp::eInvert, // Invert + vk::LogicOp::eNand, // Nand + vk::LogicOp::eOr, // Or + vk::LogicOp::eNor, // Nor + vk::LogicOp::eXor, // Xor + vk::LogicOp::eEquivalent, // Equiv + vk::LogicOp::eAndInverted, // AndInverted + vk::LogicOp::eOrReverse, // OrReverse + vk::LogicOp::eOrInverted, // OrInverted + }}; + + const auto index = static_cast(op); + ASSERT_MSG(index < logic_op_table.size(), "Unknown logic op {}", index); + return logic_op_table[index]; +} + +inline vk::CompareOp CompareFunc(Pica::FramebufferRegs::CompareFunc func) { + static constexpr std::array compare_func_table{{ + vk::CompareOp::eNever, // CompareFunc::Never + vk::CompareOp::eAlways, // CompareFunc::Always + vk::CompareOp::eEqual, // CompareFunc::Equal + vk::CompareOp::eNotEqual, // CompareFunc::NotEqual + vk::CompareOp::eLess, // CompareFunc::LessThan + vk::CompareOp::eLessOrEqual, // CompareFunc::LessThanOrEqual + vk::CompareOp::eGreater, // CompareFunc::GreaterThan + vk::CompareOp::eGreaterOrEqual, // CompareFunc::GreaterThanOrEqual + }}; + + const auto index = static_cast(func); + ASSERT_MSG(index < compare_func_table.size(), "Unknown compare function {}", index); + return compare_func_table[index]; +} + +inline vk::StencilOp StencilOp(Pica::FramebufferRegs::StencilAction action) { + static constexpr std::array stencil_op_table{{ + vk::StencilOp::eKeep, // StencilAction::Keep + vk::StencilOp::eZero, // StencilAction::Zero + vk::StencilOp::eReplace, // StencilAction::Replace + vk::StencilOp::eIncrementAndClamp, // StencilAction::Increment + vk::StencilOp::eDecrementAndClamp, // StencilAction::Decrement + vk::StencilOp::eInvert, // StencilAction::Invert + vk::StencilOp::eIncrementAndWrap, // StencilAction::IncrementWrap + vk::StencilOp::eDecrementAndWrap, // StencilAction::DecrementWrap + }}; + + const auto index = static_cast(action); + ASSERT_MSG(index < stencil_op_table.size(), "Unknown stencil op {}", index); + return stencil_op_table[index]; +} + +inline vk::PrimitiveTopology PrimitiveTopology(Pica::PipelineRegs::TriangleTopology topology) { + switch (topology) { + case Pica::PipelineRegs::TriangleTopology::Fan: + return vk::PrimitiveTopology::eTriangleFan; + case Pica::PipelineRegs::TriangleTopology::List: + case Pica::PipelineRegs::TriangleTopology::Shader: + return vk::PrimitiveTopology::eTriangleList; + case Pica::PipelineRegs::TriangleTopology::Strip: + return vk::PrimitiveTopology::eTriangleStrip; + } +} + +inline vk::CullModeFlags CullMode(Pica::RasterizerRegs::CullMode mode) { + switch (mode) { + case Pica::RasterizerRegs::CullMode::KeepAll: + return vk::CullModeFlagBits::eNone; + case Pica::RasterizerRegs::CullMode::KeepClockWise: + case Pica::RasterizerRegs::CullMode::KeepCounterClockWise: + return vk::CullModeFlagBits::eBack; + } +} + +inline vk::FrontFace FrontFace(Pica::RasterizerRegs::CullMode mode) { + switch (mode) { + case Pica::RasterizerRegs::CullMode::KeepAll: + case Pica::RasterizerRegs::CullMode::KeepClockWise: + return vk::FrontFace::eCounterClockwise; + case Pica::RasterizerRegs::CullMode::KeepCounterClockWise: + return vk::FrontFace::eClockwise; + } +} + +} // namespace PicaToVK diff --git a/src/video_core/renderer_vulkan/vk_common.cpp b/src/video_core/renderer_vulkan/vk_common.cpp new file mode 100644 index 000000000..ebec21566 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_common.cpp @@ -0,0 +1,12 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "video_core/renderer_vulkan/vk_common.h" + +// Implement vma functions +#define VMA_IMPLEMENTATION +#include + +// Store the dispatch loader here +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE diff --git a/src/video_core/renderer_vulkan/vk_common.h b/src/video_core/renderer_vulkan/vk_common.h new file mode 100644 index 000000000..a8147acbe --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_common.h @@ -0,0 +1,16 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +// Include vulkan-hpp header +#define VK_ENABLE_BETA_EXTENSIONS +#define VK_NO_PROTOTYPES +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 +#define VULKAN_HPP_NO_CONSTRUCTORS +#define VULKAN_HPP_NO_STRUCT_SETTERS +#include + +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1 diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp new file mode 100644 index 000000000..2388032a8 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -0,0 +1,581 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#include "common/assert.h" +#include "common/settings.h" +#include "core/frontend/emu_window.h" +#include "video_core/custom_textures/custom_format.h" +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_platform.h" + +#include + +namespace Vulkan { + +namespace { +vk::Format MakeFormat(VideoCore::PixelFormat format) { + switch (format) { + case VideoCore::PixelFormat::RGBA8: + return vk::Format::eR8G8B8A8Unorm; + case VideoCore::PixelFormat::RGB8: + return vk::Format::eB8G8R8Unorm; + case VideoCore::PixelFormat::RGB5A1: + return vk::Format::eR5G5B5A1UnormPack16; + case VideoCore::PixelFormat::RGB565: + return vk::Format::eR5G6B5UnormPack16; + case VideoCore::PixelFormat::RGBA4: + return vk::Format::eR4G4B4A4UnormPack16; + case VideoCore::PixelFormat::D16: + return vk::Format::eD16Unorm; + case VideoCore::PixelFormat::D24: + return vk::Format::eX8D24UnormPack32; + case VideoCore::PixelFormat::D24S8: + return vk::Format::eD24UnormS8Uint; + case VideoCore::PixelFormat::Invalid: + LOG_ERROR(Render_Vulkan, "Unknown texture format {}!", format); + return vk::Format::eUndefined; + default: + return vk::Format::eR8G8B8A8Unorm; ///< Use default case for the texture formats + } +} + +vk::Format MakeCustomFormat(VideoCore::CustomPixelFormat format) { + switch (format) { + case VideoCore::CustomPixelFormat::RGBA8: + return vk::Format::eR8G8B8A8Unorm; + case VideoCore::CustomPixelFormat::BC1: + return vk::Format::eBc1RgbaUnormBlock; + case VideoCore::CustomPixelFormat::BC3: + return vk::Format::eBc3UnormBlock; + case VideoCore::CustomPixelFormat::BC5: + return vk::Format::eBc5UnormBlock; + case VideoCore::CustomPixelFormat::BC7: + return vk::Format::eBc7UnormBlock; + case VideoCore::CustomPixelFormat::ASTC4: + return vk::Format::eAstc4x4UnormBlock; + case VideoCore::CustomPixelFormat::ASTC6: + return vk::Format::eAstc6x6UnormBlock; + case VideoCore::CustomPixelFormat::ASTC8: + return vk::Format::eAstc8x6UnormBlock; + default: + LOG_ERROR(Render_Vulkan, "Unknown custom format {}", format); + } + return vk::Format::eR8G8B8A8Unorm; +} + +vk::Format MakeAttributeFormat(Pica::PipelineRegs::VertexAttributeFormat format, u32 count, + bool scaled = true) { + static constexpr std::array attrib_formats_scaled = { + vk::Format::eR8Sscaled, vk::Format::eR8G8Sscaled, + vk::Format::eR8G8B8Sscaled, vk::Format::eR8G8B8A8Sscaled, + vk::Format::eR8Uscaled, vk::Format::eR8G8Uscaled, + vk::Format::eR8G8B8Uscaled, vk::Format::eR8G8B8A8Uscaled, + vk::Format::eR16Sscaled, vk::Format::eR16G16Sscaled, + vk::Format::eR16G16B16Sscaled, vk::Format::eR16G16B16A16Sscaled, + vk::Format::eR32Sfloat, vk::Format::eR32G32Sfloat, + vk::Format::eR32G32B32Sfloat, vk::Format::eR32G32B32A32Sfloat, + }; + static constexpr std::array attrib_formats_int = { + vk::Format::eR8Sint, vk::Format::eR8G8Sint, + vk::Format::eR8G8B8Sint, vk::Format::eR8G8B8A8Sint, + vk::Format::eR8Uint, vk::Format::eR8G8Uint, + vk::Format::eR8G8B8Uint, vk::Format::eR8G8B8A8Uint, + vk::Format::eR16Sint, vk::Format::eR16G16Sint, + vk::Format::eR16G16B16Sint, vk::Format::eR16G16B16A16Sint, + vk::Format::eR32Sfloat, vk::Format::eR32G32Sfloat, + vk::Format::eR32G32B32Sfloat, vk::Format::eR32G32B32A32Sfloat, + }; + + const u32 index = static_cast(format); + return (scaled ? attrib_formats_scaled : attrib_formats_int)[index * 4 + count - 1]; +} + +vk::ImageAspectFlags MakeAspect(VideoCore::SurfaceType type) { + switch (type) { + case VideoCore::SurfaceType::Color: + case VideoCore::SurfaceType::Texture: + case VideoCore::SurfaceType::Fill: + return vk::ImageAspectFlagBits::eColor; + case VideoCore::SurfaceType::Depth: + return vk::ImageAspectFlagBits::eDepth; + case VideoCore::SurfaceType::DepthStencil: + return vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil; + default: + LOG_CRITICAL(Render_Vulkan, "Invalid surface type {}", type); + UNREACHABLE(); + } + + return vk::ImageAspectFlagBits::eColor; +} + +std::vector GetSupportedExtensions(vk::PhysicalDevice physical) { + const std::vector extensions = physical.enumerateDeviceExtensionProperties(); + std::vector supported_extensions; + supported_extensions.reserve(extensions.size()); + for (const auto& extension : extensions) { + supported_extensions.emplace_back(extension.extensionName.data()); + } + return supported_extensions; +} +} // Anonymous namespace + +Instance::Instance(bool enable_validation, bool dump_command_buffers) + : library{OpenLibrary()}, instance{CreateInstance(*library, + Frontend::WindowSystemType::Headless, + enable_validation, dump_command_buffers)}, + physical_devices{instance->enumeratePhysicalDevices()} {} + +Instance::Instance(Frontend::EmuWindow& window, u32 physical_device_index) + : library{OpenLibrary()}, instance{CreateInstance( + *library, window.GetWindowInfo().type, + Settings::values.renderer_debug.GetValue(), + Settings::values.dump_command_buffers.GetValue())}, + debug_callback{CreateDebugCallback(*instance)}, physical_devices{ + instance->enumeratePhysicalDevices()} { + const std::size_t num_physical_devices = static_cast(physical_devices.size()); + ASSERT_MSG(physical_device_index < num_physical_devices, + "Invalid physical device index {} provided when only {} devices exist", + physical_device_index, num_physical_devices); + + physical_device = physical_devices[physical_device_index]; + properties = physical_device.getProperties(); + + CollectTelemetryParameters(); + CreateDevice(); + CreateFormatTable(); + CreateCustomFormatTable(); + CreateAttribTable(); +} + +Instance::~Instance() { + vmaDestroyAllocator(allocator); +} + +const FormatTraits& Instance::GetTraits(VideoCore::PixelFormat pixel_format) const { + if (pixel_format == VideoCore::PixelFormat::Invalid) [[unlikely]] { + return null_traits; + } + return format_table[static_cast(pixel_format)]; +} + +const FormatTraits& Instance::GetTraits(VideoCore::CustomPixelFormat pixel_format) const { + return custom_format_table[static_cast(pixel_format)]; +} + +const FormatTraits& Instance::GetTraits(Pica::PipelineRegs::VertexAttributeFormat format, + u32 count) const { + if (count == 0) [[unlikely]] { + ASSERT_MSG(false, "Unable to retrieve traits for invalid attribute component count"); + } + const u32 index = static_cast(format); + return attrib_table[index * 4 + count - 1]; +} + +FormatTraits Instance::DetermineTraits(VideoCore::PixelFormat pixel_format, vk::Format format) { + const vk::ImageAspectFlags format_aspect = MakeAspect(VideoCore::GetFormatType(pixel_format)); + const vk::FormatProperties format_properties = physical_device.getFormatProperties(format); + + const vk::FormatFeatureFlagBits attachment_usage = + (format_aspect & vk::ImageAspectFlagBits::eDepth) + ? vk::FormatFeatureFlagBits::eDepthStencilAttachment + : vk::FormatFeatureFlagBits::eColorAttachmentBlend; + + const vk::FormatFeatureFlags storage_usage = vk::FormatFeatureFlagBits::eStorageImage; + const vk::FormatFeatureFlags transfer_usage = vk::FormatFeatureFlagBits::eSampledImage; + const vk::FormatFeatureFlags blit_usage = + vk::FormatFeatureFlagBits::eBlitSrc | vk::FormatFeatureFlagBits::eBlitDst; + + const bool supports_transfer = + (format_properties.optimalTilingFeatures & transfer_usage) == transfer_usage; + const bool supports_blit = (format_properties.optimalTilingFeatures & blit_usage) == blit_usage; + const bool supports_attachment = + (format_properties.optimalTilingFeatures & attachment_usage) == attachment_usage && + pixel_format != VideoCore::PixelFormat::RGB8; + const bool supports_storage = + (format_properties.optimalTilingFeatures & storage_usage) == storage_usage; + const bool needs_conversion = + // Requires component flip. + pixel_format == VideoCore::PixelFormat::RGBA8 || + // Requires (de)interleaving. + pixel_format == VideoCore::PixelFormat::D24S8; + + // Find the most inclusive usage flags for this format + vk::ImageUsageFlags best_usage{}; + if (supports_blit || supports_transfer) { + best_usage |= vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst | + vk::ImageUsageFlagBits::eTransferSrc; + } + if (supports_attachment) { + best_usage |= (format_aspect & vk::ImageAspectFlagBits::eDepth) + ? vk::ImageUsageFlagBits::eDepthStencilAttachment + : vk::ImageUsageFlagBits::eColorAttachment; + } + if (supports_storage) { + best_usage |= vk::ImageUsageFlagBits::eStorage; + } + + return FormatTraits{ + .transfer_support = supports_transfer, + .blit_support = supports_blit, + .attachment_support = supports_attachment, + .storage_support = supports_storage, + .needs_conversion = needs_conversion, + .usage = best_usage, + .aspect = format_aspect, + .native = format, + }; +} + +void Instance::CreateFormatTable() { + constexpr std::array pixel_formats = { + VideoCore::PixelFormat::RGBA8, VideoCore::PixelFormat::RGB8, + VideoCore::PixelFormat::RGB5A1, VideoCore::PixelFormat::RGB565, + VideoCore::PixelFormat::RGBA4, VideoCore::PixelFormat::IA8, + VideoCore::PixelFormat::RG8, VideoCore::PixelFormat::I8, + VideoCore::PixelFormat::A8, VideoCore::PixelFormat::IA4, + VideoCore::PixelFormat::I4, VideoCore::PixelFormat::A4, + VideoCore::PixelFormat::ETC1, VideoCore::PixelFormat::ETC1A4, + VideoCore::PixelFormat::D16, VideoCore::PixelFormat::D24, + VideoCore::PixelFormat::D24S8, + }; + + for (const auto& pixel_format : pixel_formats) { + const vk::Format format = MakeFormat(pixel_format); + FormatTraits traits = DetermineTraits(pixel_format, format); + + const bool is_suitable = + traits.transfer_support && traits.attachment_support && + (traits.blit_support || traits.aspect & vk::ImageAspectFlagBits::eDepth); + + // Fall back if the native format is not suitable. + if (!is_suitable) { + // Always fallback to RGBA8 or D32(S8) for convenience + auto fallback = vk::Format::eR8G8B8A8Unorm; + if (traits.aspect & vk::ImageAspectFlagBits::eDepth) { + fallback = vk::Format::eD32Sfloat; + if (traits.aspect & vk::ImageAspectFlagBits::eStencil) { + fallback = vk::Format::eD32SfloatS8Uint; + } + } + LOG_WARNING(Render_Vulkan, "Format {} unsupported, falling back unconditionally to {}", + vk::to_string(format), vk::to_string(fallback)); + traits = DetermineTraits(pixel_format, fallback); + // Always requires conversion if backing format does not match. + traits.needs_conversion = true; + } + + const u32 index = static_cast(pixel_format); + format_table[index] = traits; + } +} + +void Instance::CreateCustomFormatTable() { + // The traits are the same for RGBA8 + custom_format_table[0] = format_table[static_cast(VideoCore::PixelFormat::RGBA8)]; + + constexpr std::array custom_formats = { + VideoCore::CustomPixelFormat::BC1, VideoCore::CustomPixelFormat::BC3, + VideoCore::CustomPixelFormat::BC5, VideoCore::CustomPixelFormat::BC7, + VideoCore::CustomPixelFormat::ASTC4, VideoCore::CustomPixelFormat::ASTC6, + VideoCore::CustomPixelFormat::ASTC8, + }; + + for (const auto& custom_format : custom_formats) { + const vk::Format format = MakeCustomFormat(custom_format); + const vk::FormatProperties format_properties = physical_device.getFormatProperties(format); + + // Compressed formats don't support blit_dst in general so just check for transfer + const vk::FormatFeatureFlags transfer_usage = vk::FormatFeatureFlagBits::eSampledImage; + const bool supports_transfer = + (format_properties.optimalTilingFeatures & transfer_usage) == transfer_usage; + + vk::ImageUsageFlags best_usage{}; + if (supports_transfer) { + best_usage |= vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eTransferDst | + vk::ImageUsageFlagBits::eTransferSrc; + } + + const u32 index = static_cast(custom_format); + custom_format_table[index] = FormatTraits{ + .transfer_support = supports_transfer, + .usage = best_usage, + .aspect = vk::ImageAspectFlagBits::eColor, + .native = format, + }; + } +} + +void Instance::DetermineEmulation(Pica::PipelineRegs::VertexAttributeFormat format, + bool& needs_cast) { + // Check if (u)scaled formats can be used to emulate the 3 component format + vk::Format four_comp_format = MakeAttributeFormat(format, 4); + vk::FormatProperties format_properties = physical_device.getFormatProperties(four_comp_format); + needs_cast = !(format_properties.bufferFeatures & vk::FormatFeatureFlagBits::eVertexBuffer); +} + +void Instance::CreateAttribTable() { + constexpr std::array attrib_formats = { + Pica::PipelineRegs::VertexAttributeFormat::BYTE, + Pica::PipelineRegs::VertexAttributeFormat::UBYTE, + Pica::PipelineRegs::VertexAttributeFormat::SHORT, + Pica::PipelineRegs::VertexAttributeFormat::FLOAT, + }; + + for (const auto& format : attrib_formats) { + for (u32 count = 1; count <= 4; count++) { + bool needs_cast{false}; + bool needs_emulation{false}; + vk::Format attrib_format = MakeAttributeFormat(format, count); + vk::FormatProperties format_properties = + physical_device.getFormatProperties(attrib_format); + if (!(format_properties.bufferFeatures & vk::FormatFeatureFlagBits::eVertexBuffer)) { + needs_cast = true; + attrib_format = MakeAttributeFormat(format, count, false); + format_properties = physical_device.getFormatProperties(attrib_format); + if (!(format_properties.bufferFeatures & + vk::FormatFeatureFlagBits::eVertexBuffer)) { + ASSERT_MSG( + count == 3, + "Vertex attribute emulation is only supported for 3 component formats"); + DetermineEmulation(format, needs_cast); + needs_emulation = true; + } + } + + const u32 index = static_cast(format) * 4 + count - 1; + attrib_table[index] = FormatTraits{ + .needs_conversion = needs_cast, + .needs_emulation = needs_emulation, + .native = attrib_format, + }; + } + } +} + +bool Instance::CreateDevice() { + const vk::StructureChain feature_chain = physical_device.getFeatures2< + vk::PhysicalDeviceFeatures2, vk::PhysicalDevicePortabilitySubsetFeaturesKHR, + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT, + vk::PhysicalDeviceExtendedDynamicState2FeaturesEXT, + vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT, + vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR, + vk::PhysicalDeviceCustomBorderColorFeaturesEXT, vk::PhysicalDeviceIndexTypeUint8FeaturesEXT, + vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT>(); + const vk::StructureChain properties_chain = + physical_device.getProperties2(); + + features = feature_chain.get().features; + available_extensions = GetSupportedExtensions(physical_device); + if (available_extensions.empty()) { + LOG_CRITICAL(Render_Vulkan, "No extensions supported by device."); + return false; + } + + boost::container::static_vector enabled_extensions; + const auto add_extension = [&](std::string_view extension, bool blacklist = false, + std::string_view reason = "") -> bool { + const auto result = + std::find_if(available_extensions.begin(), available_extensions.end(), + [&](const std::string& name) { return name == extension; }); + + if (result != available_extensions.end() && !blacklist) { + LOG_INFO(Render_Vulkan, "Enabling extension: {}", extension); + enabled_extensions.push_back(extension.data()); + return true; + } else if (blacklist) { + LOG_WARNING(Render_Vulkan, "Extension {} has been blacklisted because {}", extension, + reason); + return false; + } + + LOG_WARNING(Render_Vulkan, "Extension {} unavailable.", extension); + return false; + }; + + const bool is_arm = driver_id == vk::DriverIdKHR::eArmProprietary; + const bool is_qualcomm = driver_id == vk::DriverIdKHR::eQualcommProprietary; + + add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + image_format_list = add_extension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME); + shader_stencil_export = add_extension(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME); + const bool has_timeline_semaphores = add_extension( + VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, is_qualcomm, "it is broken on Qualcomm drivers"); + const bool has_portability_subset = add_extension(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); + const bool has_extended_dynamic_state = + add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, is_arm || is_qualcomm, + "it is broken on Qualcomm and ARM drivers"); + const bool has_custom_border_color = add_extension(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME); + const bool has_index_type_uint8 = add_extension(VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME); + const bool has_pipeline_creation_cache_control = + add_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME); + + const auto family_properties = physical_device.getQueueFamilyProperties(); + if (family_properties.empty()) { + LOG_CRITICAL(Render_Vulkan, "Physical device reported no queues."); + return false; + } + + bool graphics_queue_found = false; + for (std::size_t i = 0; i < family_properties.size(); i++) { + const u32 index = static_cast(i); + if (family_properties[i].queueFlags & vk::QueueFlagBits::eGraphics) { + queue_family_index = index; + graphics_queue_found = true; + } + } + + if (!graphics_queue_found) { + LOG_CRITICAL(Render_Vulkan, "Unable to find graphics and/or present queues."); + return false; + } + + static constexpr std::array queue_priorities = {1.0f}; + + const vk::DeviceQueueCreateInfo queue_info = { + .queueFamilyIndex = queue_family_index, + .queueCount = static_cast(queue_priorities.size()), + .pQueuePriorities = queue_priorities.data(), + }; + + vk::StructureChain device_chain = { + vk::DeviceCreateInfo{ + .queueCreateInfoCount = 1u, + .pQueueCreateInfos = &queue_info, + .enabledExtensionCount = static_cast(enabled_extensions.size()), + .ppEnabledExtensionNames = enabled_extensions.data(), + }, + vk::PhysicalDeviceFeatures2{ + .features{ + .geometryShader = features.geometryShader, + .logicOp = features.logicOp, + .depthClamp = features.depthClamp, + .largePoints = features.largePoints, + .samplerAnisotropy = features.samplerAnisotropy, + .fragmentStoresAndAtomics = features.fragmentStoresAndAtomics, + .shaderClipDistance = features.shaderClipDistance, + }, + }, + vk::PhysicalDevicePortabilitySubsetFeaturesKHR{}, + vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR{}, + vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT{}, + vk::PhysicalDeviceExtendedDynamicState2FeaturesEXT{}, + vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT{}, + vk::PhysicalDeviceCustomBorderColorFeaturesEXT{}, + vk::PhysicalDeviceIndexTypeUint8FeaturesEXT{}, + vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT{}, + }; + +#define PROP_GET(structName, prop, property) property = properties_chain.get().prop; + +#define FEAT_SET(structName, feature, property) \ + if (feature_chain.get().feature) { \ + property = true; \ + device_chain.get().feature = true; \ + } else { \ + property = false; \ + device_chain.get().feature = false; \ + } + + if (has_portability_subset) { + FEAT_SET(vk::PhysicalDevicePortabilitySubsetFeaturesKHR, triangleFans, + triangle_fan_supported) + FEAT_SET(vk::PhysicalDevicePortabilitySubsetFeaturesKHR, imageViewFormatReinterpretation, + image_view_reinterpretation) + PROP_GET(vk::PhysicalDevicePortabilitySubsetPropertiesKHR, + minVertexInputBindingStrideAlignment, min_vertex_stride_alignment) + } else { + device_chain.unlink(); + } + + if (has_timeline_semaphores) { + FEAT_SET(vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR, timelineSemaphore, + timeline_semaphores) + } else { + device_chain.unlink(); + } + + if (has_index_type_uint8) { + FEAT_SET(vk::PhysicalDeviceIndexTypeUint8FeaturesEXT, indexTypeUint8, index_type_uint8) + } else { + device_chain.unlink(); + } + + if (has_extended_dynamic_state) { + FEAT_SET(vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT, extendedDynamicState, + extended_dynamic_state) + } else { + device_chain.unlink(); + } + + if (has_custom_border_color) { + FEAT_SET(vk::PhysicalDeviceCustomBorderColorFeaturesEXT, customBorderColors, + custom_border_color) + FEAT_SET(vk::PhysicalDeviceCustomBorderColorFeaturesEXT, customBorderColorWithoutFormat, + custom_border_color) + } else { + device_chain.unlink(); + } + + if (has_pipeline_creation_cache_control) { + FEAT_SET(vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT, + pipelineCreationCacheControl, pipeline_creation_cache_control) + } else { + device_chain.unlink(); + } + +#undef PROP_GET +#undef FEAT_SET + + try { + device = physical_device.createDeviceUnique(device_chain.get()); + } catch (vk::ExtensionNotPresentError& err) { + LOG_CRITICAL(Render_Vulkan, "Some required extensions are not available {}", err.what()); + return false; + } + + VULKAN_HPP_DEFAULT_DISPATCHER.init(*device); + + graphics_queue = device->getQueue(queue_family_index, 0); + present_queue = device->getQueue(queue_family_index, 0); + + CreateAllocator(); + return true; +} + +void Instance::CreateAllocator() { + const VmaVulkanFunctions functions = { + .vkGetInstanceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr, + .vkGetDeviceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetDeviceProcAddr, + }; + + const VmaAllocatorCreateInfo allocator_info = { + .physicalDevice = physical_device, + .device = *device, + .pVulkanFunctions = &functions, + .instance = *instance, + .vulkanApiVersion = vk::enumerateInstanceVersion(), + }; + + const VkResult result = vmaCreateAllocator(&allocator_info, &allocator); + if (result != VK_SUCCESS) { + UNREACHABLE_MSG("Failed to initialize VMA with error {}", result); + } +} + +void Instance::CollectTelemetryParameters() { + const vk::StructureChain property_chain = + physical_device + .getProperties2(); + const vk::PhysicalDeviceDriverProperties driver = + property_chain.get(); + + driver_id = driver.driverID; + vendor_name = driver.driverName.data(); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h new file mode 100644 index 000000000..dc5c07d3d --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -0,0 +1,287 @@ +// Copyright 2022 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "video_core/rasterizer_cache/pixel_format.h" +#include "video_core/regs_pipeline.h" +#include "video_core/renderer_vulkan/vk_platform.h" + +namespace Frontend { +class EmuWindow; +} + +namespace VideoCore { +enum class CustomPixelFormat : u32; +} + +VK_DEFINE_HANDLE(VmaAllocator) + +namespace Vulkan { + +struct FormatTraits { + bool transfer_support = false; + bool blit_support = false; + bool attachment_support = false; + bool storage_support = false; + bool needs_conversion = false; + bool needs_emulation = false; + vk::ImageUsageFlags usage{}; + vk::ImageAspectFlags aspect; + vk::Format native = vk::Format::eUndefined; +}; + +class Instance { +public: + explicit Instance(bool validation = false, bool dump_command_buffers = false); + explicit Instance(Frontend::EmuWindow& window, u32 physical_device_index); + ~Instance(); + + /// Returns the FormatTraits struct for the provided pixel format + const FormatTraits& GetTraits(VideoCore::PixelFormat pixel_format) const; + const FormatTraits& GetTraits(VideoCore::CustomPixelFormat pixel_format) const; + + /// Returns the FormatTraits struct for the provided attribute format and count + const FormatTraits& GetTraits(Pica::PipelineRegs::VertexAttributeFormat format, + u32 count) const; + + /// Returns the Vulkan instance + vk::Instance GetInstance() const { + return *instance; + } + + /// Returns the current physical device + vk::PhysicalDevice GetPhysicalDevice() const { + return physical_device; + } + + /// Returns the Vulkan device + vk::Device GetDevice() const { + return *device; + } + + /// Returns the VMA allocator handle + VmaAllocator GetAllocator() const { + return allocator; + } + + /// Returns a list of the available physical devices + std::span GetPhysicalDevices() const { + return physical_devices; + } + + /// Retrieve queue information + u32 GetGraphicsQueueFamilyIndex() const { + return queue_family_index; + } + + u32 GetPresentQueueFamilyIndex() const { + return queue_family_index; + } + + vk::Queue GetGraphicsQueue() const { + return graphics_queue; + } + + vk::Queue GetPresentQueue() const { + return present_queue; + } + + /// Returns true if logic operations need shader emulation + bool NeedsLogicOpEmulation() const { + return !features.logicOp; + } + + bool UseGeometryShaders() const { +#ifdef __ANDROID__ + // Geometry shaders are extremely expensive on tilers to avoid them at all + // cost even if it hurts accuracy somewhat. TODO: Make this an option + return false; +#else + return features.geometryShader; +#endif + } + + /// Returns true if anisotropic filtering is supported + bool IsAnisotropicFilteringSupported() const { + return features.samplerAnisotropy; + } + + /// Returns true when VK_KHR_timeline_semaphore is supported + bool IsTimelineSemaphoreSupported() const { + return timeline_semaphores; + } + + /// Returns true when VK_EXT_extended_dynamic_state is supported + bool IsExtendedDynamicStateSupported() const { + return extended_dynamic_state; + } + + /// Returns true when VK_EXT_custom_border_color is supported + bool IsCustomBorderColorSupported() const { + return custom_border_color; + } + + /// Returns true when VK_EXT_index_type_uint8 is supported + bool IsIndexTypeUint8Supported() const { + return index_type_uint8; + } + + /// Returns true when VK_KHR_image_format_list is supported + bool IsImageFormatListSupported() const { + return image_format_list; + } + + /// Returns true when VK_EXT_pipeline_creation_cache_control is supported + bool IsPipelineCreationCacheControlSupported() const { + return pipeline_creation_cache_control; + } + + /// Returns true when VK_EXT_shader_stencil_export is supported + bool IsShaderStencilExportSupported() const { + return shader_stencil_export; + } + + /// Returns true if VK_EXT_debug_utils is supported + bool IsExtDebugUtilsSupported() const { + return debug_messenger_supported; + } + + /// Returns the vendor ID of the physical device + u32 GetVendorID() const { + return properties.vendorID; + } + + /// Returns the device ID of the physical device + u32 GetDeviceID() const { + return properties.deviceID; + } + + /// Returns the driver ID. + vk::DriverId GetDriverID() const { + return driver_id; + } + + /// Returns the current driver version provided in Vulkan-formatted version numbers. + u32 GetDriverVersion() const { + return properties.driverVersion; + } + + /// Returns the current Vulkan API version provided in Vulkan-formatted version numbers. + u32 ApiVersion() const { + return properties.apiVersion; + } + + /// Returns the vendor name reported from Vulkan. + std::string_view GetVendorName() const { + return vendor_name; + } + + /// Returns the list of available extensions. + const std::vector& GetAvailableExtensions() const { + return available_extensions; + } + + /// Returns the device name. + std::string_view GetModelName() const { + return properties.deviceName; + } + + /// Returns the pipeline cache unique identifier + const auto GetPipelineCacheUUID() const { + return properties.pipelineCacheUUID; + } + + /// Returns the minimum required alignment for uniforms + vk::DeviceSize UniformMinAlignment() const { + return properties.limits.minUniformBufferOffsetAlignment; + } + + /// Returns the maximum supported elements in a texel buffer + u32 MaxTexelBufferElements() const { + return properties.limits.maxTexelBufferElements; + } + + /// Returns true if shaders can declare the ClipDistance attribute + bool IsShaderClipDistanceSupported() const { + return features.shaderClipDistance; + } + + /// Returns true if triangle fan is an accepted primitive topology + bool IsTriangleFanSupported() const { + return triangle_fan_supported; + } + + /// Returns the minimum vertex stride alignment + u32 GetMinVertexStrideAlignment() const { + return min_vertex_stride_alignment; + } + + /// Returns true if commands should be flushed at the end of each major renderpass + bool ShouldFlush() const { + return driver_id == vk::DriverIdKHR::eArmProprietary || + driver_id == vk::DriverIdKHR::eQualcommProprietary; + } + +private: + /// Returns the optimal supported usage for the requested format + [[nodiscard]] FormatTraits DetermineTraits(VideoCore::PixelFormat pixel_format, + vk::Format format); + + /// Determines the best available vertex attribute format emulation + void DetermineEmulation(Pica::PipelineRegs::VertexAttributeFormat format, bool& needs_cast); + + /// Creates the format compatibility table for the current device + void CreateFormatTable(); + void CreateCustomFormatTable(); + + /// Creates the attribute format table for the current device + void CreateAttribTable(); + + /// Creates the logical device opportunistically enabling extensions + bool CreateDevice(); + + /// Creates the VMA allocator handle + void CreateAllocator(); + + /// Collects telemetry information from the device. + void CollectTelemetryParameters(); + +private: + std::shared_ptr library; + vk::UniqueInstance instance; + vk::PhysicalDevice physical_device; + vk::UniqueDevice device; + vk::PhysicalDeviceProperties properties; + vk::PhysicalDeviceFeatures features; + vk::DriverIdKHR driver_id; + DebugCallback debug_callback; + std::string vendor_name; + VmaAllocator allocator{}; + vk::Queue present_queue; + vk::Queue graphics_queue; + std::vector physical_devices; + FormatTraits null_traits; + std::array format_table; + std::array custom_format_table; + std::array attrib_table; + std::vector available_extensions; + u32 queue_family_index{0}; + bool triangle_fan_supported{true}; + bool image_view_reinterpretation{true}; + u32 min_vertex_stride_alignment{1}; + bool timeline_semaphores{}; + bool extended_dynamic_state{}; + bool custom_border_color{}; + bool index_type_uint8{}; + bool image_format_list{}; + bool pipeline_creation_cache_control{}; + bool shader_stencil_export{}; + bool debug_messenger_supported{}; + bool debug_report_supported{}; +}; + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp new file mode 100644 index 000000000..dafb86a8d --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -0,0 +1,366 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// Include the vulkan platform specific header +#if defined(ANDROID) +#define VK_USE_PLATFORM_ANDROID_KHR +#elif defined(WIN32) +#define VK_USE_PLATFORM_WIN32_KHR +#elif defined(__APPLE__) +#define VK_USE_PLATFORM_METAL_EXT +#else +#define VK_USE_PLATFORM_WAYLAND_KHR +#define VK_USE_PLATFORM_XLIB_KHR +#endif + +#include +#include +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "core/frontend/emu_window.h" +#include "video_core/renderer_vulkan/vk_platform.h" + +namespace Vulkan { + +namespace { +static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { + + switch (callback_data->messageIdNumber) { + case 0x609a13b: // Vertex attribute at location not consumed by shader + return VK_FALSE; + default: + break; + } + + Log::Level level{}; + switch (severity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + level = Log::Level::Error; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + level = Log::Level::Info; + break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + level = Log::Level::Debug; + break; + default: + level = Log::Level::Info; + } + + LOG_GENERIC(Log::Class::Render_Vulkan, level, "{}: {}", + callback_data->pMessageIdName ? callback_data->pMessageIdName : "", + callback_data->pMessage ? callback_data->pMessage : ""); + + return VK_FALSE; +} + +static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objectType, + uint64_t object, size_t location, + int32_t messageCode, + const char* pLayerPrefix, + const char* pMessage, void* pUserData) { + + const VkDebugReportFlagBitsEXT severity = static_cast(flags); + Log::Level level{}; + switch (severity) { + case VK_DEBUG_REPORT_ERROR_BIT_EXT: + level = Log::Level::Error; + break; + case VK_DEBUG_REPORT_INFORMATION_BIT_EXT: + level = Log::Level::Warning; + break; + case VK_DEBUG_REPORT_DEBUG_BIT_EXT: + case VK_DEBUG_REPORT_WARNING_BIT_EXT: + case VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT: + level = Log::Level::Debug; + break; + default: + level = Log::Level::Info; + } + + const vk::DebugReportObjectTypeEXT type = static_cast(objectType); + LOG_GENERIC(Log::Class::Render_Vulkan, level, + "type = {}, object = {} | MessageCode = {:#x}, LayerPrefix = {} | {}", + vk::to_string(type), object, messageCode, pLayerPrefix, pMessage); + + return VK_FALSE; +} +} // Anonymous namespace + +std::shared_ptr OpenLibrary() { + auto library = std::make_shared(); +#ifdef __APPLE__ + const std::string filename = Common::DynamicLibrary::GetLibraryName("vulkan"); + library->Load(filename); + if (!library->IsLoaded()) { + // Fall back to directly loading bundled MoltenVK library. + library->Load("libMoltenVK.dylib"); + } +#else + std::string filename = Common::DynamicLibrary::GetLibraryName("vulkan", 1); + LOG_DEBUG(Render_Vulkan, "Trying Vulkan library: {}", filename); + if (!library->Load(filename)) { + // Android devices may not have libvulkan.so.1, only libvulkan.so. + filename = Common::DynamicLibrary::GetLibraryName("vulkan"); + LOG_DEBUG(Render_Vulkan, "Trying Vulkan library (second attempt): {}", filename); + void(library->Load(filename)); + } +#endif + return library; +} + +vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::EmuWindow& emu_window) { + const auto& window_info = emu_window.GetWindowInfo(); + vk::SurfaceKHR surface{}; + +#if defined(VK_USE_PLATFORM_WIN32_KHR) + if (window_info.type == Frontend::WindowSystemType::Windows) { + const vk::Win32SurfaceCreateInfoKHR win32_ci = { + .hinstance = nullptr, + .hwnd = static_cast(window_info.render_surface), + }; + + if (instance.createWin32SurfaceKHR(&win32_ci, nullptr, &surface) != vk::Result::eSuccess) { + LOG_CRITICAL(Render_Vulkan, "Failed to initialize Win32 surface"); + UNREACHABLE(); + } + } +#elif defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (window_info.type == Frontend::WindowSystemType::X11) { + const vk::XlibSurfaceCreateInfoKHR xlib_ci = { + .dpy = static_cast(window_info.display_connection), + .window = reinterpret_cast(window_info.render_surface), + }; + + if (instance.createXlibSurfaceKHR(&xlib_ci, nullptr, &surface) != vk::Result::eSuccess) { + LOG_ERROR(Render_Vulkan, "Failed to initialize Xlib surface"); + UNREACHABLE(); + } + } else if (window_info.type == Frontend::WindowSystemType::Wayland) { + const vk::WaylandSurfaceCreateInfoKHR wayland_ci = { + .display = static_cast(window_info.display_connection), + .surface = static_cast(window_info.render_surface), + }; + + if (instance.createWaylandSurfaceKHR(&wayland_ci, nullptr, &surface) != + vk::Result::eSuccess) { + LOG_ERROR(Render_Vulkan, "Failed to initialize Wayland surface"); + UNREACHABLE(); + } + } +#elif defined(VK_USE_PLATFORM_METAL_EXT) + if (window_info.type == Frontend::WindowSystemType::MacOS) { + const vk::MetalSurfaceCreateInfoEXT macos_ci = { + .pLayer = static_cast(window_info.render_surface), + }; + + if (instance.createMetalSurfaceEXT(&macos_ci, nullptr, &surface) != vk::Result::eSuccess) { + LOG_CRITICAL(Render_Vulkan, "Failed to initialize MacOS surface"); + UNREACHABLE(); + } + } +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + if (window_info.type == Frontend::WindowSystemType::Android) { + vk::AndroidSurfaceCreateInfoKHR android_ci = { + .window = reinterpret_cast(window_info.render_surface), + }; + + if (instance.createAndroidSurfaceKHR(&android_ci, nullptr, &surface) != + vk::Result::eSuccess) { + LOG_CRITICAL(Render_Vulkan, "Failed to initialize Android surface"); + UNREACHABLE(); + } + } +#endif + + if (!surface) { + LOG_CRITICAL(Render_Vulkan, "Presentation not supported on this platform"); + UNREACHABLE(); + } + + return surface; +} + +std::vector GetInstanceExtensions(Frontend::WindowSystemType window_type, + bool enable_debug_utils) { + const auto properties = vk::enumerateInstanceExtensionProperties(); + if (properties.empty()) { + LOG_ERROR(Render_Vulkan, "Failed to query extension properties"); + return {}; + } + + // Add the windowing system specific extension + std::vector extensions; + extensions.reserve(6); + +#if defined(__APPLE__) + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); +#endif + + switch (window_type) { + case Frontend::WindowSystemType::Headless: + break; +#if defined(VK_USE_PLATFORM_WIN32_KHR) + case Frontend::WindowSystemType::Windows: + extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + break; +#elif defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) + case Frontend::WindowSystemType::X11: + extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + break; + case Frontend::WindowSystemType::Wayland: + extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); + break; +#elif defined(VK_USE_PLATFORM_METAL_EXT) + case Frontend::WindowSystemType::MacOS: + extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); + break; +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + case Frontend::WindowSystemType::Android: + extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); + break; +#endif + default: + LOG_ERROR(Render_Vulkan, "Presentation not supported on this platform"); + break; + } + + if (window_type != Frontend::WindowSystemType::Headless) { + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + } + + if (enable_debug_utils) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + } + + // Sanitize extension list + std::erase_if(extensions, [&](const char* extension) -> bool { + const auto it = + std::find_if(properties.begin(), properties.end(), [extension](const auto& prop) { + return std::strcmp(extension, prop.extensionName) == 0; + }); + + if (it == properties.end()) { + LOG_INFO(Render_Vulkan, "Candidate instance extension {} is not available", extension); + return true; + } + return false; + }); + + return extensions; +} + +vk::InstanceCreateFlags GetInstanceFlags() { +#if defined(__APPLE__) + return vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR; +#else + return static_cast(0); +#endif +} + +vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library, + Frontend::WindowSystemType window_type, bool enable_validation, + bool dump_command_buffers) { + const auto vkGetInstanceProcAddr = + library.GetSymbol("vkGetInstanceProcAddr"); + if (!vkGetInstanceProcAddr) { + LOG_CRITICAL(Render_Vulkan, "Failed GetSymbol vkGetInstanceProcAddr"); + return {}; + } + VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); + + const auto extensions = GetInstanceExtensions(window_type, enable_validation); + const u32 available_version = vk::enumerateInstanceVersion(); + if (available_version < VK_API_VERSION_1_1) { + LOG_CRITICAL(Render_Vulkan, "Vulkan 1.0 is not supported, 1.1 is required!"); + return {}; + } + + const vk::ApplicationInfo application_info = { + .pApplicationName = "Citra", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "Citra Vulkan", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = available_version, + }; + + boost::container::static_vector layers; + if (enable_validation) { + layers.push_back("VK_LAYER_KHRONOS_validation"); + } + if (dump_command_buffers) { + layers.push_back("VK_LAYER_LUNARG_api_dump"); + } + + const vk::InstanceCreateInfo instance_ci = { + .flags = GetInstanceFlags(), + .pApplicationInfo = &application_info, + .enabledLayerCount = static_cast(layers.size()), + .ppEnabledLayerNames = layers.data(), + .enabledExtensionCount = static_cast(extensions.size()), + .ppEnabledExtensionNames = extensions.data(), + }; + + auto instance = vk::createInstanceUnique(instance_ci); + + VULKAN_HPP_DEFAULT_DISPATCHER.init(*instance); + + return instance; +} + +vk::UniqueDebugUtilsMessengerEXT CreateDebugMessenger(vk::Instance instance) { + const vk::DebugUtilsMessengerCreateInfoEXT msg_ci = { + .messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | + vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose, + .messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | + vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | + vk::DebugUtilsMessageTypeFlagBitsEXT::eDeviceAddressBinding | + vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance, + .pfnUserCallback = DebugUtilsCallback, + }; + return instance.createDebugUtilsMessengerEXTUnique(msg_ci); +} + +vk::UniqueDebugReportCallbackEXT CreateDebugReportCallback(vk::Instance instance) { + const vk::DebugReportCallbackCreateInfoEXT callback_ci = { + .flags = vk::DebugReportFlagBitsEXT::eDebug | vk::DebugReportFlagBitsEXT::eInformation | + vk::DebugReportFlagBitsEXT::eError | + vk::DebugReportFlagBitsEXT::ePerformanceWarning | + vk::DebugReportFlagBitsEXT::eWarning, + .pfnCallback = DebugReportCallback, + }; + return instance.createDebugReportCallbackEXTUnique(callback_ci); +} + +DebugCallback CreateDebugCallback(vk::Instance instance) { + if (!Settings::values.renderer_debug) { + return {}; + } + const auto properties = vk::enumerateInstanceExtensionProperties(); + if (properties.empty()) { + LOG_ERROR(Render_Vulkan, "Failed to query extension properties"); + return {}; + } + const auto it = std::find_if(properties.begin(), properties.end(), [](const auto& prop) { + return std::strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, prop.extensionName) == 0; + }); + // Prefer debug util messenger if available. + if (it != properties.end()) { + return CreateDebugMessenger(instance); + } + // Otherwise fallback to debug report callback. + return CreateDebugReportCallback(instance); +} + +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_platform.h b/src/video_core/renderer_vulkan/vk_platform.h new file mode 100644 index 000000000..f4b69e6c2 --- /dev/null +++ b/src/video_core/renderer_vulkan/vk_platform.h @@ -0,0 +1,34 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "common/common_types.h" +#include "common/dynamic_library/dynamic_library.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Frontend { +class EmuWindow; +enum class WindowSystemType : u8; +} // namespace Frontend + +namespace Vulkan { + +using DebugCallback = + std::variant; + +std::shared_ptr OpenLibrary(); + +vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::EmuWindow& emu_window); + +vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library, + Frontend::WindowSystemType window_type, bool enable_validation, + bool dump_command_buffers); + +DebugCallback CreateDebugCallback(vk::Instance instance); + +} // namespace Vulkan