mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-15 01:20:06 +00:00
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
This commit is contained in:
parent
70225e92f7
commit
d735f5c458
@ -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) {
|
||||
|
@ -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<void*>(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<LPSTR>(&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<void*>(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<LPSTR>(&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<void*>(GetProcAddress(reinterpret_cast<HMODULE>(handle), name.data()));
|
||||
#else
|
||||
@ -84,4 +91,4 @@ std::string DynamicLibrary::GetLibraryName(std::string_view name, int major, int
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace DynamicLibrary
|
||||
} // namespace Common
|
||||
|
@ -5,35 +5,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#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 <typename T>
|
||||
T GetSymbol(std::string_view name) {
|
||||
[[nodiscard]] T GetSymbol(std::string_view name) const {
|
||||
return reinterpret_cast<T>(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
|
||||
|
@ -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<DynamicLibrary> fdk_aac;
|
||||
static std::unique_ptr<Common::DynamicLibrary> fdk_aac;
|
||||
|
||||
#define LOAD_SYMBOL(library, name) \
|
||||
any_failed = any_failed || (name = library->GetSymbol<name##_func>(#name)) == nullptr
|
||||
@ -25,7 +26,7 @@ bool LoadFdkAac() {
|
||||
return true;
|
||||
}
|
||||
|
||||
fdk_aac = std::make_unique<DynamicLibrary>("fdk-aac", 2);
|
||||
fdk_aac = std::make_unique<Common::DynamicLibrary>("fdk-aac", 2);
|
||||
if (!fdk_aac->IsLoaded()) {
|
||||
LOG_WARNING(Common, "Could not dynamically load libfdk-aac: {}", fdk_aac->GetLoadError());
|
||||
fdk_aac.reset();
|
||||
|
@ -8,9 +8,6 @@ extern "C" {
|
||||
#include <fdk-aac/aacdecoder_lib.h>
|
||||
}
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/dynamic_library/dynamic_library.h"
|
||||
|
||||
namespace DynamicLibrary::FdkAac {
|
||||
|
||||
typedef INT (*aacDecoder_GetLibInfo_func)(LIB_INFO* info);
|
||||
|
@ -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<DynamicLibrary> avutil;
|
||||
static std::unique_ptr<DynamicLibrary> avcodec;
|
||||
static std::unique_ptr<DynamicLibrary> avfilter;
|
||||
static std::unique_ptr<DynamicLibrary> avformat;
|
||||
static std::unique_ptr<DynamicLibrary> swresample;
|
||||
static std::unique_ptr<Common::DynamicLibrary> avutil;
|
||||
static std::unique_ptr<Common::DynamicLibrary> avcodec;
|
||||
static std::unique_ptr<Common::DynamicLibrary> avfilter;
|
||||
static std::unique_ptr<Common::DynamicLibrary> avformat;
|
||||
static std::unique_ptr<Common::DynamicLibrary> swresample;
|
||||
|
||||
#define LOAD_SYMBOL(library, name) \
|
||||
any_failed = any_failed || (name = library->GetSymbol<name##_func>(#name)) == nullptr
|
||||
@ -124,7 +125,7 @@ static bool LoadAVUtil() {
|
||||
return true;
|
||||
}
|
||||
|
||||
avutil = std::make_unique<DynamicLibrary>("avutil", LIBAVUTIL_VERSION_MAJOR);
|
||||
avutil = std::make_unique<Common::DynamicLibrary>("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<DynamicLibrary>("avcodec", LIBAVCODEC_VERSION_MAJOR);
|
||||
avcodec = std::make_unique<Common::DynamicLibrary>("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<DynamicLibrary>("avfilter", LIBAVFILTER_VERSION_MAJOR);
|
||||
avfilter = std::make_unique<Common::DynamicLibrary>("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<DynamicLibrary>("avformat", LIBAVFORMAT_VERSION_MAJOR);
|
||||
avformat = std::make_unique<Common::DynamicLibrary>("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<DynamicLibrary>("swresample", LIBSWRESAMPLE_VERSION_MAJOR);
|
||||
swresample =
|
||||
std::make_unique<Common::DynamicLibrary>("swresample", LIBSWRESAMPLE_VERSION_MAJOR);
|
||||
if (!swresample->IsLoaded()) {
|
||||
LOG_WARNING(Common, "Could not dynamically load libswresample: {}",
|
||||
swresample->GetLoadError());
|
||||
|
@ -15,9 +15,6 @@ extern "C" {
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/dynamic_library/dynamic_library.h"
|
||||
|
||||
namespace DynamicLibrary::FFmpeg {
|
||||
|
||||
// avutil
|
||||
|
@ -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) \
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <array>
|
||||
#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
|
||||
|
@ -432,6 +432,7 @@ struct Values {
|
||||
"graphics_api"};
|
||||
Setting<bool> use_gles{false, "use_gles"};
|
||||
Setting<bool> renderer_debug{false, "renderer_debug"};
|
||||
Setting<bool> dump_command_buffers{false, "dump_command_buffers"};
|
||||
SwitchableSetting<bool> use_hw_shader{true, "use_hw_shader"};
|
||||
SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
|
||||
SwitchableSetting<bool> shaders_accurate_mul{true, "shaders_accurate_mul"};
|
||||
|
@ -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)
|
||||
|
198
src/video_core/renderer_vulkan/pica_to_vk.h
Normal file
198
src/video_core/renderer_vulkan/pica_to_vk.h
Normal file
@ -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<vk::SamplerAddressMode, 8> 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<std::size_t>(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<u32>(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<vk::BlendOp, 5> blend_equation_table{{
|
||||
vk::BlendOp::eAdd,
|
||||
vk::BlendOp::eSubtract,
|
||||
vk::BlendOp::eReverseSubtract,
|
||||
vk::BlendOp::eMin,
|
||||
vk::BlendOp::eMax,
|
||||
}};
|
||||
|
||||
const auto index = static_cast<std::size_t>(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<vk::BlendFactor, 15> 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<std::size_t>(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<vk::LogicOp, 16> 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<std::size_t>(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<vk::CompareOp, 8> 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<std::size_t>(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<vk::StencilOp, 8> 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<std::size_t>(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
|
12
src/video_core/renderer_vulkan/vk_common.cpp
Normal file
12
src/video_core/renderer_vulkan/vk_common.cpp
Normal file
@ -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 <vk_mem_alloc.h>
|
||||
|
||||
// Store the dispatch loader here
|
||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
16
src/video_core/renderer_vulkan/vk_common.h
Normal file
16
src/video_core/renderer_vulkan/vk_common.h
Normal file
@ -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 <vulkan/vulkan.hpp>
|
||||
|
||||
#define VMA_STATIC_VULKAN_FUNCTIONS 0
|
||||
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
|
581
src/video_core/renderer_vulkan/vk_instance.cpp
Normal file
581
src/video_core/renderer_vulkan/vk_instance.cpp
Normal file
@ -0,0 +1,581 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <span>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
||||
#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 <vk_mem_alloc.h>
|
||||
|
||||
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<u32>(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<std::string> GetSupportedExtensions(vk::PhysicalDevice physical) {
|
||||
const std::vector extensions = physical.enumerateDeviceExtensionProperties();
|
||||
std::vector<std::string> 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<u16>(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<u32>(pixel_format)];
|
||||
}
|
||||
|
||||
const FormatTraits& Instance::GetTraits(VideoCore::CustomPixelFormat pixel_format) const {
|
||||
return custom_format_table[static_cast<u32>(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<u32>(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<u32>(pixel_format);
|
||||
format_table[index] = traits;
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::CreateCustomFormatTable() {
|
||||
// The traits are the same for RGBA8
|
||||
custom_format_table[0] = format_table[static_cast<u32>(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<u32>(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<u32>(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<vk::PhysicalDeviceProperties2,
|
||||
vk::PhysicalDevicePortabilitySubsetPropertiesKHR>();
|
||||
|
||||
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<const char*, 12> 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<u32>(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<f32, 1> queue_priorities = {1.0f};
|
||||
|
||||
const vk::DeviceQueueCreateInfo queue_info = {
|
||||
.queueFamilyIndex = queue_family_index,
|
||||
.queueCount = static_cast<u32>(queue_priorities.size()),
|
||||
.pQueuePriorities = queue_priorities.data(),
|
||||
};
|
||||
|
||||
vk::StructureChain device_chain = {
|
||||
vk::DeviceCreateInfo{
|
||||
.queueCreateInfoCount = 1u,
|
||||
.pQueueCreateInfos = &queue_info,
|
||||
.enabledExtensionCount = static_cast<u32>(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<structName>().prop;
|
||||
|
||||
#define FEAT_SET(structName, feature, property) \
|
||||
if (feature_chain.get<structName>().feature) { \
|
||||
property = true; \
|
||||
device_chain.get<structName>().feature = true; \
|
||||
} else { \
|
||||
property = false; \
|
||||
device_chain.get<structName>().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<vk::PhysicalDevicePortabilitySubsetFeaturesKHR>();
|
||||
}
|
||||
|
||||
if (has_timeline_semaphores) {
|
||||
FEAT_SET(vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR, timelineSemaphore,
|
||||
timeline_semaphores)
|
||||
} else {
|
||||
device_chain.unlink<vk::PhysicalDeviceTimelineSemaphoreFeaturesKHR>();
|
||||
}
|
||||
|
||||
if (has_index_type_uint8) {
|
||||
FEAT_SET(vk::PhysicalDeviceIndexTypeUint8FeaturesEXT, indexTypeUint8, index_type_uint8)
|
||||
} else {
|
||||
device_chain.unlink<vk::PhysicalDeviceIndexTypeUint8FeaturesEXT>();
|
||||
}
|
||||
|
||||
if (has_extended_dynamic_state) {
|
||||
FEAT_SET(vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT, extendedDynamicState,
|
||||
extended_dynamic_state)
|
||||
} else {
|
||||
device_chain.unlink<vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT>();
|
||||
}
|
||||
|
||||
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<vk::PhysicalDeviceCustomBorderColorFeaturesEXT>();
|
||||
}
|
||||
|
||||
if (has_pipeline_creation_cache_control) {
|
||||
FEAT_SET(vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT,
|
||||
pipelineCreationCacheControl, pipeline_creation_cache_control)
|
||||
} else {
|
||||
device_chain.unlink<vk::PhysicalDevicePipelineCreationCacheControlFeaturesEXT>();
|
||||
}
|
||||
|
||||
#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<vk::PhysicalDeviceProperties2, vk::PhysicalDeviceDriverProperties>();
|
||||
const vk::PhysicalDeviceDriverProperties driver =
|
||||
property_chain.get<vk::PhysicalDeviceDriverProperties>();
|
||||
|
||||
driver_id = driver.driverID;
|
||||
vendor_name = driver.driverName.data();
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
287
src/video_core/renderer_vulkan/vk_instance.h
Normal file
287
src/video_core/renderer_vulkan/vk_instance.h
Normal file
@ -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 <span>
|
||||
|
||||
#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<const vk::PhysicalDevice> 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<std::string>& 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<Common::DynamicLibrary> 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<vk::PhysicalDevice> physical_devices;
|
||||
FormatTraits null_traits;
|
||||
std::array<FormatTraits, VideoCore::PIXEL_FORMAT_COUNT> format_table;
|
||||
std::array<FormatTraits, 10> custom_format_table;
|
||||
std::array<FormatTraits, 16> attrib_table;
|
||||
std::vector<std::string> 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
|
366
src/video_core/renderer_vulkan/vk_platform.cpp
Normal file
366
src/video_core/renderer_vulkan/vk_platform.cpp
Normal file
@ -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 <memory>
|
||||
#include <vector>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
||||
#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 : "<null>",
|
||||
callback_data->pMessage ? callback_data->pMessage : "<null>");
|
||||
|
||||
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<VkDebugReportFlagBitsEXT>(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<vk::DebugReportObjectTypeEXT>(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<Common::DynamicLibrary> OpenLibrary() {
|
||||
auto library = std::make_shared<Common::DynamicLibrary>();
|
||||
#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<HWND>(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<Display*>(window_info.display_connection),
|
||||
.window = reinterpret_cast<Window>(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<wl_display*>(window_info.display_connection),
|
||||
.surface = static_cast<wl_surface*>(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<const CAMetalLayer*>(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<ANativeWindow*>(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<const char*> 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<const char*> 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<vk::InstanceCreateFlags>(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<PFN_vkGetInstanceProcAddr>("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<const char*, 2> 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<u32>(layers.size()),
|
||||
.ppEnabledLayerNames = layers.data(),
|
||||
.enabledExtensionCount = static_cast<u32>(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
|
34
src/video_core/renderer_vulkan/vk_platform.h
Normal file
34
src/video_core/renderer_vulkan/vk_platform.h
Normal file
@ -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 <memory>
|
||||
#include <variant>
|
||||
|
||||
#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<vk::UniqueDebugUtilsMessengerEXT, vk::UniqueDebugReportCallbackEXT>;
|
||||
|
||||
std::shared_ptr<Common::DynamicLibrary> 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
|
Loading…
Reference in New Issue
Block a user