mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 13:50:03 +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
					
				
					 18 changed files with 1576 additions and 54 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue