Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
WickedEngine/WickedEngine/wiGraphicsDevice_Vulkan.cpp
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
8313 lines (7475 sloc)
291 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "wiGraphicsDevice_Vulkan.h" | |
#ifdef WICKEDENGINE_BUILD_VULKAN | |
#include "wiHelper.h" | |
#include "wiBacklog.h" | |
#include "wiVersion.h" | |
#include "wiTimer.h" | |
#include "wiUnorderedSet.h" | |
#define VOLK_IMPLEMENTATION | |
#include "Utility/volk.h" | |
#include "Utility/spirv_reflect.h" | |
#define VMA_IMPLEMENTATION | |
#include "Utility/vk_mem_alloc.h" | |
#ifdef SDL2 | |
#include <SDL2/SDL_vulkan.h> | |
#include "sdl2.h" | |
#endif | |
#include <string> | |
#include <cstring> | |
#include <iostream> | |
#include <algorithm> | |
// These shifts are made so that Vulkan resource bindings slots don't interfere with each other across shader stages: | |
// These are also defined in wi::shadercompiler.cpp as hard coded compiler arguments for SPIRV, so they need to be the same | |
#define VULKAN_BINDING_SHIFT_B 0 | |
#define VULKAN_BINDING_SHIFT_T 1000 | |
#define VULKAN_BINDING_SHIFT_U 2000 | |
#define VULKAN_BINDING_SHIFT_S 3000 | |
namespace wi::graphics | |
{ | |
namespace vulkan_internal | |
{ | |
// Converters: | |
constexpr VkFormat _ConvertFormat(Format value) | |
{ | |
switch (value) | |
{ | |
case Format::UNKNOWN: | |
return VK_FORMAT_UNDEFINED; | |
case Format::R32G32B32A32_FLOAT: | |
return VK_FORMAT_R32G32B32A32_SFLOAT; | |
case Format::R32G32B32A32_UINT: | |
return VK_FORMAT_R32G32B32A32_UINT; | |
case Format::R32G32B32A32_SINT: | |
return VK_FORMAT_R32G32B32A32_SINT; | |
case Format::R32G32B32_FLOAT: | |
return VK_FORMAT_R32G32B32_SFLOAT; | |
case Format::R32G32B32_UINT: | |
return VK_FORMAT_R32G32B32_UINT; | |
case Format::R32G32B32_SINT: | |
return VK_FORMAT_R32G32B32_SINT; | |
case Format::R16G16B16A16_FLOAT: | |
return VK_FORMAT_R16G16B16A16_SFLOAT; | |
case Format::R16G16B16A16_UNORM: | |
return VK_FORMAT_R16G16B16A16_UNORM; | |
case Format::R16G16B16A16_UINT: | |
return VK_FORMAT_R16G16B16A16_UINT; | |
case Format::R16G16B16A16_SNORM: | |
return VK_FORMAT_R16G16B16A16_SNORM; | |
case Format::R16G16B16A16_SINT: | |
return VK_FORMAT_R16G16B16A16_SINT; | |
case Format::R32G32_FLOAT: | |
return VK_FORMAT_R32G32_SFLOAT; | |
case Format::R32G32_UINT: | |
return VK_FORMAT_R32G32_UINT; | |
case Format::R32G32_SINT: | |
return VK_FORMAT_R32G32_SINT; | |
case Format::D32_FLOAT_S8X24_UINT: | |
return VK_FORMAT_D32_SFLOAT_S8_UINT; | |
case Format::R10G10B10A2_UNORM: | |
return VK_FORMAT_A2B10G10R10_UNORM_PACK32; | |
case Format::R10G10B10A2_UINT: | |
return VK_FORMAT_A2B10G10R10_UINT_PACK32; | |
case Format::R11G11B10_FLOAT: | |
return VK_FORMAT_B10G11R11_UFLOAT_PACK32; | |
case Format::R8G8B8A8_UNORM: | |
return VK_FORMAT_R8G8B8A8_UNORM; | |
case Format::R8G8B8A8_UNORM_SRGB: | |
return VK_FORMAT_R8G8B8A8_SRGB; | |
case Format::R8G8B8A8_UINT: | |
return VK_FORMAT_R8G8B8A8_UINT; | |
case Format::R8G8B8A8_SNORM: | |
return VK_FORMAT_R8G8B8A8_SNORM; | |
case Format::R8G8B8A8_SINT: | |
return VK_FORMAT_R8G8B8A8_SINT; | |
case Format::R16G16_FLOAT: | |
return VK_FORMAT_R16G16_SFLOAT; | |
case Format::R16G16_UNORM: | |
return VK_FORMAT_R16G16_UNORM; | |
case Format::R16G16_UINT: | |
return VK_FORMAT_R16G16_UINT; | |
case Format::R16G16_SNORM: | |
return VK_FORMAT_R16G16_SNORM; | |
case Format::R16G16_SINT: | |
return VK_FORMAT_R16G16_SINT; | |
case Format::D32_FLOAT: | |
return VK_FORMAT_D32_SFLOAT; | |
case Format::R32_FLOAT: | |
return VK_FORMAT_R32_SFLOAT; | |
case Format::R32_UINT: | |
return VK_FORMAT_R32_UINT; | |
case Format::R32_SINT: | |
return VK_FORMAT_R32_SINT; | |
case Format::D24_UNORM_S8_UINT: | |
return VK_FORMAT_D24_UNORM_S8_UINT; | |
case Format::R9G9B9E5_SHAREDEXP: | |
return VK_FORMAT_E5B9G9R9_UFLOAT_PACK32; | |
case Format::R8G8_UNORM: | |
return VK_FORMAT_R8G8_UNORM; | |
case Format::R8G8_UINT: | |
return VK_FORMAT_R8G8_UINT; | |
case Format::R8G8_SNORM: | |
return VK_FORMAT_R8G8_SNORM; | |
case Format::R8G8_SINT: | |
return VK_FORMAT_R8G8_SINT; | |
case Format::R16_FLOAT: | |
return VK_FORMAT_R16_SFLOAT; | |
case Format::D16_UNORM: | |
return VK_FORMAT_D16_UNORM; | |
case Format::R16_UNORM: | |
return VK_FORMAT_R16_UNORM; | |
case Format::R16_UINT: | |
return VK_FORMAT_R16_UINT; | |
case Format::R16_SNORM: | |
return VK_FORMAT_R16_SNORM; | |
case Format::R16_SINT: | |
return VK_FORMAT_R16_SINT; | |
case Format::R8_UNORM: | |
return VK_FORMAT_R8_UNORM; | |
case Format::R8_UINT: | |
return VK_FORMAT_R8_UINT; | |
case Format::R8_SNORM: | |
return VK_FORMAT_R8_SNORM; | |
case Format::R8_SINT: | |
return VK_FORMAT_R8_SINT; | |
case Format::BC1_UNORM: | |
return VK_FORMAT_BC1_RGBA_UNORM_BLOCK; | |
case Format::BC1_UNORM_SRGB: | |
return VK_FORMAT_BC1_RGBA_SRGB_BLOCK; | |
case Format::BC2_UNORM: | |
return VK_FORMAT_BC2_UNORM_BLOCK; | |
case Format::BC2_UNORM_SRGB: | |
return VK_FORMAT_BC2_SRGB_BLOCK; | |
case Format::BC3_UNORM: | |
return VK_FORMAT_BC3_UNORM_BLOCK; | |
case Format::BC3_UNORM_SRGB: | |
return VK_FORMAT_BC3_SRGB_BLOCK; | |
case Format::BC4_UNORM: | |
return VK_FORMAT_BC4_UNORM_BLOCK; | |
case Format::BC4_SNORM: | |
return VK_FORMAT_BC4_SNORM_BLOCK; | |
case Format::BC5_UNORM: | |
return VK_FORMAT_BC5_UNORM_BLOCK; | |
case Format::BC5_SNORM: | |
return VK_FORMAT_BC5_SNORM_BLOCK; | |
case Format::B8G8R8A8_UNORM: | |
return VK_FORMAT_B8G8R8A8_UNORM; | |
case Format::B8G8R8A8_UNORM_SRGB: | |
return VK_FORMAT_B8G8R8A8_SRGB; | |
case Format::BC6H_UF16: | |
return VK_FORMAT_BC6H_UFLOAT_BLOCK; | |
case Format::BC6H_SF16: | |
return VK_FORMAT_BC6H_SFLOAT_BLOCK; | |
case Format::BC7_UNORM: | |
return VK_FORMAT_BC7_UNORM_BLOCK; | |
case Format::BC7_UNORM_SRGB: | |
return VK_FORMAT_BC7_SRGB_BLOCK; | |
} | |
return VK_FORMAT_UNDEFINED; | |
} | |
constexpr VkCompareOp _ConvertComparisonFunc(ComparisonFunc value) | |
{ | |
switch (value) | |
{ | |
case ComparisonFunc::NEVER: | |
return VK_COMPARE_OP_NEVER; | |
case ComparisonFunc::LESS: | |
return VK_COMPARE_OP_LESS; | |
case ComparisonFunc::EQUAL: | |
return VK_COMPARE_OP_EQUAL; | |
case ComparisonFunc::LESS_EQUAL: | |
return VK_COMPARE_OP_LESS_OR_EQUAL; | |
case ComparisonFunc::GREATER: | |
return VK_COMPARE_OP_GREATER; | |
case ComparisonFunc::NOT_EQUAL: | |
return VK_COMPARE_OP_NOT_EQUAL; | |
case ComparisonFunc::GREATER_EQUAL: | |
return VK_COMPARE_OP_GREATER_OR_EQUAL; | |
case ComparisonFunc::ALWAYS: | |
return VK_COMPARE_OP_ALWAYS; | |
default: | |
return VK_COMPARE_OP_NEVER; | |
} | |
} | |
constexpr VkBlendFactor _ConvertBlend(Blend value) | |
{ | |
switch (value) | |
{ | |
case Blend::ZERO: | |
return VK_BLEND_FACTOR_ZERO; | |
case Blend::ONE: | |
return VK_BLEND_FACTOR_ONE; | |
case Blend::SRC_COLOR: | |
return VK_BLEND_FACTOR_SRC_COLOR; | |
case Blend::INV_SRC_COLOR: | |
return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; | |
case Blend::SRC_ALPHA: | |
return VK_BLEND_FACTOR_SRC_ALPHA; | |
case Blend::INV_SRC_ALPHA: | |
return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; | |
case Blend::DEST_ALPHA: | |
return VK_BLEND_FACTOR_DST_ALPHA; | |
case Blend::INV_DEST_ALPHA: | |
return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; | |
case Blend::DEST_COLOR: | |
return VK_BLEND_FACTOR_DST_COLOR; | |
case Blend::INV_DEST_COLOR: | |
return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; | |
case Blend::SRC_ALPHA_SAT: | |
return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; | |
case Blend::BLEND_FACTOR: | |
return VK_BLEND_FACTOR_CONSTANT_COLOR; | |
case Blend::INV_BLEND_FACTOR: | |
return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; | |
break; | |
case Blend::SRC1_COLOR: | |
return VK_BLEND_FACTOR_SRC1_COLOR; | |
case Blend::INV_SRC1_COLOR: | |
return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR; | |
case Blend::SRC1_ALPHA: | |
return VK_BLEND_FACTOR_SRC1_ALPHA; | |
case Blend::INV_SRC1_ALPHA: | |
return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA; | |
default: | |
return VK_BLEND_FACTOR_ZERO; | |
} | |
} | |
constexpr VkBlendOp _ConvertBlendOp(BlendOp value) | |
{ | |
switch (value) | |
{ | |
case BlendOp::ADD: | |
return VK_BLEND_OP_ADD; | |
case BlendOp::SUBTRACT: | |
return VK_BLEND_OP_SUBTRACT; | |
case BlendOp::REV_SUBTRACT: | |
return VK_BLEND_OP_REVERSE_SUBTRACT; | |
case BlendOp::MIN: | |
return VK_BLEND_OP_MIN; | |
case BlendOp::MAX: | |
return VK_BLEND_OP_MAX; | |
default: | |
return VK_BLEND_OP_ADD; | |
} | |
} | |
constexpr VkSamplerAddressMode _ConvertTextureAddressMode(TextureAddressMode value, const VkPhysicalDeviceVulkan12Features& features_1_2) | |
{ | |
switch (value) | |
{ | |
case TextureAddressMode::WRAP: | |
return VK_SAMPLER_ADDRESS_MODE_REPEAT; | |
case TextureAddressMode::MIRROR: | |
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; | |
case TextureAddressMode::CLAMP: | |
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; | |
case TextureAddressMode::BORDER: | |
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; | |
case TextureAddressMode::MIRROR_ONCE: | |
if (features_1_2.samplerMirrorClampToEdge == VK_TRUE) | |
{ | |
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE; | |
} | |
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; | |
default: | |
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; | |
} | |
} | |
constexpr VkBorderColor _ConvertSamplerBorderColor(SamplerBorderColor value) | |
{ | |
switch (value) | |
{ | |
case SamplerBorderColor::TRANSPARENT_BLACK: | |
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; | |
case SamplerBorderColor::OPAQUE_BLACK: | |
return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; | |
case SamplerBorderColor::OPAQUE_WHITE: | |
return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; | |
default: | |
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; | |
} | |
} | |
constexpr VkStencilOp _ConvertStencilOp(StencilOp value) | |
{ | |
switch (value) | |
{ | |
case wi::graphics::StencilOp::KEEP: | |
return VK_STENCIL_OP_KEEP; | |
case wi::graphics::StencilOp::ZERO: | |
return VK_STENCIL_OP_ZERO; | |
case wi::graphics::StencilOp::REPLACE: | |
return VK_STENCIL_OP_REPLACE; | |
case wi::graphics::StencilOp::INCR_SAT: | |
return VK_STENCIL_OP_INCREMENT_AND_CLAMP; | |
case wi::graphics::StencilOp::DECR_SAT: | |
return VK_STENCIL_OP_DECREMENT_AND_CLAMP; | |
case wi::graphics::StencilOp::INVERT: | |
return VK_STENCIL_OP_INVERT; | |
case wi::graphics::StencilOp::INCR: | |
return VK_STENCIL_OP_INCREMENT_AND_WRAP; | |
case wi::graphics::StencilOp::DECR: | |
return VK_STENCIL_OP_DECREMENT_AND_WRAP; | |
default: | |
return VK_STENCIL_OP_KEEP; | |
} | |
} | |
constexpr VkImageLayout _ConvertImageLayout(ResourceState value) | |
{ | |
switch (value) | |
{ | |
case ResourceState::UNDEFINED: | |
return VK_IMAGE_LAYOUT_UNDEFINED; | |
case ResourceState::RENDERTARGET: | |
return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; | |
case ResourceState::DEPTHSTENCIL: | |
return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; | |
case ResourceState::DEPTHSTENCIL_READONLY: | |
return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; | |
case ResourceState::SHADER_RESOURCE: | |
case ResourceState::SHADER_RESOURCE_COMPUTE: | |
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; | |
case ResourceState::UNORDERED_ACCESS: | |
return VK_IMAGE_LAYOUT_GENERAL; | |
case ResourceState::COPY_SRC: | |
return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; | |
case ResourceState::COPY_DST: | |
return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; | |
case ResourceState::SHADING_RATE_SOURCE: | |
return VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; | |
default: | |
return VK_IMAGE_LAYOUT_UNDEFINED; | |
} | |
} | |
constexpr VkShaderStageFlags _ConvertStageFlags(ShaderStage value) | |
{ | |
switch (value) | |
{ | |
case ShaderStage::MS: | |
return VK_SHADER_STAGE_MESH_BIT_NV; | |
case ShaderStage::AS: | |
return VK_SHADER_STAGE_TASK_BIT_NV; | |
case ShaderStage::VS: | |
return VK_SHADER_STAGE_VERTEX_BIT; | |
case ShaderStage::HS: | |
return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; | |
case ShaderStage::DS: | |
return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; | |
case ShaderStage::GS: | |
return VK_SHADER_STAGE_GEOMETRY_BIT; | |
case ShaderStage::PS: | |
return VK_SHADER_STAGE_FRAGMENT_BIT; | |
case ShaderStage::CS: | |
return VK_SHADER_STAGE_COMPUTE_BIT; | |
default: | |
return VK_SHADER_STAGE_ALL; | |
} | |
} | |
constexpr VkImageAspectFlags _ConvertImageAspect(ImageAspect value) | |
{ | |
switch (value) | |
{ | |
default: | |
case wi::graphics::ImageAspect::COLOR: | |
return VK_IMAGE_ASPECT_COLOR_BIT; | |
case wi::graphics::ImageAspect::DEPTH: | |
return VK_IMAGE_ASPECT_DEPTH_BIT; | |
case wi::graphics::ImageAspect::STENCIL: | |
return VK_IMAGE_ASPECT_STENCIL_BIT; | |
} | |
} | |
constexpr VkAccessFlags _ParseResourceState(ResourceState value) | |
{ | |
VkAccessFlags flags = 0; | |
if (has_flag(value, ResourceState::SHADER_RESOURCE)) | |
{ | |
flags |= VK_ACCESS_SHADER_READ_BIT; | |
} | |
if (has_flag(value, ResourceState::SHADER_RESOURCE_COMPUTE)) | |
{ | |
flags |= VK_ACCESS_SHADER_READ_BIT; | |
} | |
if (has_flag(value, ResourceState::UNORDERED_ACCESS)) | |
{ | |
flags |= VK_ACCESS_SHADER_READ_BIT; | |
flags |= VK_ACCESS_SHADER_WRITE_BIT; | |
} | |
if (has_flag(value, ResourceState::COPY_SRC)) | |
{ | |
flags |= VK_ACCESS_TRANSFER_READ_BIT; | |
} | |
if (has_flag(value, ResourceState::COPY_DST)) | |
{ | |
flags |= VK_ACCESS_TRANSFER_WRITE_BIT; | |
} | |
if (has_flag(value, ResourceState::RENDERTARGET)) | |
{ | |
flags |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; | |
flags |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | |
} | |
if (has_flag(value, ResourceState::DEPTHSTENCIL)) | |
{ | |
flags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; | |
flags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; | |
} | |
if (has_flag(value, ResourceState::DEPTHSTENCIL_READONLY)) | |
{ | |
flags |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; | |
} | |
if (has_flag(value, ResourceState::VERTEX_BUFFER)) | |
{ | |
flags |= VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT; | |
} | |
if (has_flag(value, ResourceState::INDEX_BUFFER)) | |
{ | |
flags |= VK_ACCESS_INDEX_READ_BIT; | |
} | |
if (has_flag(value, ResourceState::CONSTANT_BUFFER)) | |
{ | |
flags |= VK_ACCESS_UNIFORM_READ_BIT; | |
} | |
if (has_flag(value, ResourceState::INDIRECT_ARGUMENT)) | |
{ | |
flags |= VK_ACCESS_INDIRECT_COMMAND_READ_BIT; | |
} | |
if (has_flag(value, ResourceState::PREDICATION)) | |
{ | |
flags |= VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT; | |
} | |
return flags; | |
} | |
bool checkExtensionSupport(const char* checkExtension, const wi::vector<VkExtensionProperties>& available_extensions) | |
{ | |
for (const auto& x : available_extensions) | |
{ | |
if (strcmp(x.extensionName, checkExtension) == 0) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
bool ValidateLayers(const wi::vector<const char*>& required, | |
const wi::vector<VkLayerProperties>& available) | |
{ | |
for (auto layer : required) | |
{ | |
bool found = false; | |
for (auto& available_layer : available) | |
{ | |
if (strcmp(available_layer.layerName, layer) == 0) | |
{ | |
found = true; | |
break; | |
} | |
} | |
if (!found) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsMessengerCallback( | |
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, | |
VkDebugUtilsMessageTypeFlagsEXT message_type, | |
const VkDebugUtilsMessengerCallbackDataEXT* callback_data, | |
void* user_data) | |
{ | |
// Log debug message | |
std::string ss; | |
if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) | |
{ | |
ss += "[Vulkan Warning]: "; | |
ss += callback_data->pMessage; | |
wi::backlog::post(ss, wi::backlog::LogLevel::Warning); | |
} | |
else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) | |
{ | |
ss += "[Vulkan Error]: "; | |
ss += callback_data->pMessage; | |
#if 1 | |
wi::backlog::post(ss, wi::backlog::LogLevel::Error); | |
#else | |
OutputDebugStringA(callback_data->pMessage); | |
OutputDebugStringA("\n"); | |
#endif | |
//#ifdef _DEBUG | |
// assert(0); | |
//#endif // _DEBUG | |
} | |
return VK_FALSE; | |
} | |
struct BindingUsage | |
{ | |
bool used = false; | |
VkDescriptorSetLayoutBinding binding = {}; | |
}; | |
struct Buffer_Vulkan | |
{ | |
std::shared_ptr<GraphicsDevice_Vulkan::AllocationHandler> allocationhandler; | |
VmaAllocation allocation = nullptr; | |
VkBuffer resource = VK_NULL_HANDLE; | |
struct BufferSubresource | |
{ | |
bool is_typed = false; | |
VkBufferView buffer_view = VK_NULL_HANDLE; | |
VkDescriptorBufferInfo buffer_info = {}; | |
int index = -1; // bindless | |
constexpr bool IsValid() const | |
{ | |
return index >= 0; | |
} | |
}; | |
BufferSubresource srv; | |
BufferSubresource uav; | |
wi::vector<BufferSubresource> subresources_srv; | |
wi::vector<BufferSubresource> subresources_uav; | |
VkDeviceAddress address = 0; | |
~Buffer_Vulkan() | |
{ | |
if (allocationhandler == nullptr) | |
return; | |
allocationhandler->destroylocker.lock(); | |
uint64_t framecount = allocationhandler->framecount; | |
if (resource) | |
{ | |
allocationhandler->destroyer_buffers.push_back(std::make_pair(std::make_pair(resource, allocation), framecount)); | |
} | |
else if(allocation) | |
{ | |
allocationhandler->destroyer_allocations.push_back(std::make_pair(allocation, framecount)); | |
} | |
if (srv.IsValid()) | |
{ | |
if (srv.is_typed) | |
{ | |
allocationhandler->destroyer_bufferviews.push_back(std::make_pair(srv.buffer_view, framecount)); | |
allocationhandler->destroyer_bindlessUniformTexelBuffers.push_back(std::make_pair(srv.index, framecount)); | |
} | |
else | |
{ | |
allocationhandler->destroyer_bindlessStorageBuffers.push_back(std::make_pair(srv.index, framecount)); | |
} | |
} | |
if (uav.IsValid()) | |
{ | |
if (uav.is_typed) | |
{ | |
allocationhandler->destroyer_bufferviews.push_back(std::make_pair(uav.buffer_view, framecount)); | |
allocationhandler->destroyer_bindlessStorageTexelBuffers.push_back(std::make_pair(uav.index, framecount)); | |
} | |
else | |
{ | |
allocationhandler->destroyer_bindlessStorageBuffers.push_back(std::make_pair(uav.index, framecount)); | |
} | |
} | |
for (auto& x : subresources_srv) | |
{ | |
if (x.is_typed) | |
{ | |
allocationhandler->destroyer_bufferviews.push_back(std::make_pair(x.buffer_view, framecount)); | |
allocationhandler->destroyer_bindlessUniformTexelBuffers.push_back(std::make_pair(x.index, framecount)); | |
} | |
else | |
{ | |
allocationhandler->destroyer_bindlessStorageBuffers.push_back(std::make_pair(x.index, framecount)); | |
} | |
} | |
for (auto& x : subresources_uav) | |
{ | |
if (x.is_typed) | |
{ | |
allocationhandler->destroyer_bufferviews.push_back(std::make_pair(x.buffer_view, framecount)); | |
allocationhandler->destroyer_bindlessStorageTexelBuffers.push_back(std::make_pair(x.index, framecount)); | |
} | |
else | |
{ | |
allocationhandler->destroyer_bindlessStorageBuffers.push_back(std::make_pair(x.index, framecount)); | |
} | |
} | |
allocationhandler->destroylocker.unlock(); | |
} | |
}; | |
struct Texture_Vulkan | |
{ | |
std::shared_ptr<GraphicsDevice_Vulkan::AllocationHandler> allocationhandler; | |
VmaAllocation allocation = nullptr; | |
VkImage resource = VK_NULL_HANDLE; | |
VkBuffer staging_resource = VK_NULL_HANDLE; | |
struct TextureSubresource | |
{ | |
VkImageView image_view = VK_NULL_HANDLE; | |
int index = -1; // bindless | |
uint32_t firstMip = 0; | |
uint32_t mipCount = 0; | |
uint32_t firstSlice = 0; | |
uint32_t sliceCount = 0; | |
constexpr bool IsValid() const | |
{ | |
return image_view != VK_NULL_HANDLE; | |
} | |
}; | |
TextureSubresource srv; | |
TextureSubresource uav; | |
TextureSubresource rtv; | |
TextureSubresource dsv; | |
uint32_t framebuffer_layercount = 0; | |
wi::vector<TextureSubresource> subresources_srv; | |
wi::vector<TextureSubresource> subresources_uav; | |
wi::vector<TextureSubresource> subresources_rtv; | |
wi::vector<TextureSubresource> subresources_dsv; | |
wi::vector<SubresourceData> mapped_subresources; | |
SparseTextureProperties sparse_texture_properties; | |
~Texture_Vulkan() | |
{ | |
if (allocationhandler == nullptr) | |
return; | |
allocationhandler->destroylocker.lock(); | |
uint64_t framecount = allocationhandler->framecount; | |
if (resource) | |
{ | |
allocationhandler->destroyer_images.push_back(std::make_pair(std::make_pair(resource, allocation), framecount)); | |
} | |
else if (staging_resource) | |
{ | |
allocationhandler->destroyer_buffers.push_back(std::make_pair(std::make_pair(staging_resource, allocation), framecount)); | |
} | |
else if (allocation) | |
{ | |
allocationhandler->destroyer_allocations.push_back(std::make_pair(allocation, framecount)); | |
} | |
if (srv.IsValid()) | |
{ | |
allocationhandler->destroyer_imageviews.push_back(std::make_pair(srv.image_view, framecount)); | |
allocationhandler->destroyer_bindlessSampledImages.push_back(std::make_pair(srv.index, framecount)); | |
} | |
if (uav.IsValid()) | |
{ | |
allocationhandler->destroyer_imageviews.push_back(std::make_pair(uav.image_view, framecount)); | |
allocationhandler->destroyer_bindlessStorageImages.push_back(std::make_pair(uav.index, framecount)); | |
} | |
if (rtv.IsValid()) | |
{ | |
allocationhandler->destroyer_imageviews.push_back(std::make_pair(rtv.image_view, framecount)); | |
} | |
if (dsv.IsValid()) | |
{ | |
allocationhandler->destroyer_imageviews.push_back(std::make_pair(dsv.image_view, framecount)); | |
} | |
for (auto x : subresources_srv) | |
{ | |
allocationhandler->destroyer_imageviews.push_back(std::make_pair(x.image_view, framecount)); | |
allocationhandler->destroyer_bindlessSampledImages.push_back(std::make_pair(x.index, framecount)); | |
} | |
for (auto x : subresources_uav) | |
{ | |
allocationhandler->destroyer_imageviews.push_back(std::make_pair(x.image_view, framecount)); | |
allocationhandler->destroyer_bindlessStorageImages.push_back(std::make_pair(x.index, framecount)); | |
} | |
for (auto x : subresources_rtv) | |
{ | |
allocationhandler->destroyer_imageviews.push_back(std::make_pair(x.image_view, framecount)); | |
} | |
for (auto x : subresources_dsv) | |
{ | |
allocationhandler->destroyer_imageviews.push_back(std::make_pair(x.image_view, framecount)); | |
} | |
allocationhandler->destroylocker.unlock(); | |
} | |
}; | |
struct Sampler_Vulkan | |
{ | |
std::shared_ptr<GraphicsDevice_Vulkan::AllocationHandler> allocationhandler; | |
VkSampler resource = VK_NULL_HANDLE; | |
int index = -1; | |
~Sampler_Vulkan() | |
{ | |
if (allocationhandler == nullptr) | |
return; | |
allocationhandler->destroylocker.lock(); | |
uint64_t framecount = allocationhandler->framecount; | |
if (resource) allocationhandler->destroyer_samplers.push_back(std::make_pair(resource, framecount)); | |
if (index >= 0) allocationhandler->destroyer_bindlessSamplers.push_back(std::make_pair(index, framecount)); | |
allocationhandler->destroylocker.unlock(); | |
} | |
}; | |
struct QueryHeap_Vulkan | |
{ | |
std::shared_ptr<GraphicsDevice_Vulkan::AllocationHandler> allocationhandler; | |
VkQueryPool pool = VK_NULL_HANDLE; | |
~QueryHeap_Vulkan() | |
{ | |
if (allocationhandler == nullptr) | |
return; | |
allocationhandler->destroylocker.lock(); | |
uint64_t framecount = allocationhandler->framecount; | |
if (pool) allocationhandler->destroyer_querypools.push_back(std::make_pair(pool, framecount)); | |
allocationhandler->destroylocker.unlock(); | |
} | |
}; | |
struct Shader_Vulkan | |
{ | |
std::shared_ptr<GraphicsDevice_Vulkan::AllocationHandler> allocationhandler; | |
VkShaderModule shaderModule = VK_NULL_HANDLE; | |
VkPipeline pipeline_cs = VK_NULL_HANDLE; | |
VkPipelineShaderStageCreateInfo stageInfo = {}; | |
VkPipelineLayout pipelineLayout_cs = VK_NULL_HANDLE; // no lifetime management here | |
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; // no lifetime management here | |
wi::vector<VkDescriptorSetLayoutBinding> layoutBindings; | |
wi::vector<VkImageViewType> imageViewTypes; | |
wi::vector<BindingUsage> bindlessBindings; | |
wi::vector<VkDescriptorSet> bindlessSets; | |
uint32_t bindlessFirstSet = 0; | |
VkPushConstantRange pushconstants = {}; | |
VkDeviceSize uniform_buffer_sizes[DESCRIPTORBINDER_CBV_COUNT] = {}; | |
wi::vector<uint32_t> uniform_buffer_dynamic_slots; | |
size_t binding_hash = 0; | |
~Shader_Vulkan() | |
{ | |
if (allocationhandler == nullptr) | |
return; | |
allocationhandler->destroylocker.lock(); | |
uint64_t framecount = allocationhandler->framecount; | |
if (shaderModule) allocationhandler->destroyer_shadermodules.push_back(std::make_pair(shaderModule, framecount)); | |
if (pipeline_cs) allocationhandler->destroyer_pipelines.push_back(std::make_pair(pipeline_cs, framecount)); | |
allocationhandler->destroylocker.unlock(); | |
} | |
}; | |
struct PipelineState_Vulkan | |
{ | |
std::shared_ptr<GraphicsDevice_Vulkan::AllocationHandler> allocationhandler; | |
VkPipeline pipeline = VK_NULL_HANDLE; | |
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; // no lifetime management here | |
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; // no lifetime management here | |
wi::vector<VkDescriptorSetLayoutBinding> layoutBindings; | |
wi::vector<VkImageViewType> imageViewTypes; | |
size_t hash = 0; | |
wi::vector<BindingUsage> bindlessBindings; | |
wi::vector<VkDescriptorSet> bindlessSets; | |
uint32_t bindlessFirstSet = 0; | |
VkPushConstantRange pushconstants = {}; | |
VkDeviceSize uniform_buffer_sizes[DESCRIPTORBINDER_CBV_COUNT] = {}; | |
wi::vector<uint32_t> uniform_buffer_dynamic_slots; | |
size_t binding_hash = 0; | |
VkGraphicsPipelineCreateInfo pipelineInfo = {}; | |
VkPipelineShaderStageCreateInfo shaderStages[static_cast<size_t>(ShaderStage::Count)] = {}; | |
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; | |
VkPipelineRasterizationStateCreateInfo rasterizer = {}; | |
VkPipelineRasterizationDepthClipStateCreateInfoEXT depthclip = {}; | |
VkViewport viewport = {}; | |
VkRect2D scissor = {}; | |
VkPipelineViewportStateCreateInfo viewportState = {}; | |
VkPipelineDepthStencilStateCreateInfo depthstencil = {}; | |
VkSampleMask samplemask = {}; | |
VkPipelineTessellationStateCreateInfo tessellationInfo = {}; | |
~PipelineState_Vulkan() | |
{ | |
if (allocationhandler == nullptr) | |
return; | |
allocationhandler->destroylocker.lock(); | |
uint64_t framecount = allocationhandler->framecount; | |
if (pipeline) allocationhandler->destroyer_pipelines.push_back(std::make_pair(pipeline, framecount)); | |
allocationhandler->destroylocker.unlock(); | |
} | |
}; | |
struct BVH_Vulkan | |
{ | |
std::shared_ptr<GraphicsDevice_Vulkan::AllocationHandler> allocationhandler; | |
VmaAllocation allocation = nullptr; | |
VkBuffer buffer = VK_NULL_HANDLE; | |
VkAccelerationStructureKHR resource = VK_NULL_HANDLE; | |
int index = -1; | |
VkAccelerationStructureBuildGeometryInfoKHR buildInfo = {}; | |
VkAccelerationStructureBuildSizesInfoKHR sizeInfo = {}; | |
VkAccelerationStructureCreateInfoKHR createInfo = {}; | |
wi::vector<VkAccelerationStructureGeometryKHR> geometries; | |
wi::vector<uint32_t> primitiveCounts; | |
VkDeviceAddress scratch_address = 0; | |
VkDeviceAddress as_address = 0; | |
~BVH_Vulkan() | |
{ | |
if (allocationhandler == nullptr) | |
return; | |
allocationhandler->destroylocker.lock(); | |
uint64_t framecount = allocationhandler->framecount; | |
if (buffer) allocationhandler->destroyer_buffers.push_back(std::make_pair(std::make_pair(buffer, allocation), framecount)); | |
if (resource) allocationhandler->destroyer_bvhs.push_back(std::make_pair(resource, framecount)); | |
if (index >= 0) allocationhandler->destroyer_bindlessAccelerationStructures.push_back(std::make_pair(index, framecount)); | |
allocationhandler->destroylocker.unlock(); | |
} | |
}; | |
struct RTPipelineState_Vulkan | |
{ | |
std::shared_ptr<GraphicsDevice_Vulkan::AllocationHandler> allocationhandler; | |
VkPipeline pipeline; | |
~RTPipelineState_Vulkan() | |
{ | |
if (allocationhandler == nullptr) | |
return; | |
allocationhandler->destroylocker.lock(); | |
uint64_t framecount = allocationhandler->framecount; | |
if (pipeline) allocationhandler->destroyer_pipelines.push_back(std::make_pair(pipeline, framecount)); | |
allocationhandler->destroylocker.unlock(); | |
} | |
}; | |
struct SwapChain_Vulkan | |
{ | |
std::shared_ptr<GraphicsDevice_Vulkan::AllocationHandler> allocationhandler; | |
VkSwapchainKHR swapChain = VK_NULL_HANDLE; | |
VkFormat swapChainImageFormat; | |
VkExtent2D swapChainExtent; | |
wi::vector<VkImage> swapChainImages; | |
wi::vector<VkImageView> swapChainImageViews; | |
Texture dummyTexture; | |
VkSurfaceKHR surface = VK_NULL_HANDLE; | |
uint32_t swapChainImageIndex = 0; | |
VkSemaphore swapchainAcquireSemaphore = VK_NULL_HANDLE; | |
VkSemaphore swapchainReleaseSemaphore = VK_NULL_HANDLE; | |
ColorSpace colorSpace = ColorSpace::SRGB; | |
SwapChainDesc desc; | |
std::mutex locker; | |
~SwapChain_Vulkan() | |
{ | |
if (allocationhandler == nullptr) | |
return; | |
allocationhandler->destroylocker.lock(); | |
uint64_t framecount = allocationhandler->framecount; | |
for (size_t i = 0; i < swapChainImages.size(); ++i) | |
{ | |
allocationhandler->destroyer_imageviews.push_back(std::make_pair(swapChainImageViews[i], framecount)); | |
} | |
#ifdef SDL2 | |
// Checks if the SDL VIDEO System was already destroyed. | |
// If so we would delete the swapchain twice, causing a crash on wayland. | |
if (SDL_WasInit(SDL_INIT_VIDEO)) | |
#endif | |
{ | |
allocationhandler->destroyer_swapchains.push_back(std::make_pair(swapChain, framecount)); | |
allocationhandler->destroyer_surfaces.push_back(std::make_pair(surface, framecount)); | |
} | |
allocationhandler->destroyer_semaphores.push_back(std::make_pair(swapchainAcquireSemaphore, framecount)); | |
allocationhandler->destroyer_semaphores.push_back(std::make_pair(swapchainReleaseSemaphore, framecount)); | |
allocationhandler->destroylocker.unlock(); | |
} | |
}; | |
Buffer_Vulkan* to_internal(const GPUBuffer* param) | |
{ | |
return static_cast<Buffer_Vulkan*>(param->internal_state.get()); | |
} | |
Texture_Vulkan* to_internal(const Texture* param) | |
{ | |
return static_cast<Texture_Vulkan*>(param->internal_state.get()); | |
} | |
Sampler_Vulkan* to_internal(const Sampler* param) | |
{ | |
return static_cast<Sampler_Vulkan*>(param->internal_state.get()); | |
} | |
QueryHeap_Vulkan* to_internal(const GPUQueryHeap* param) | |
{ | |
return static_cast<QueryHeap_Vulkan*>(param->internal_state.get()); | |
} | |
Shader_Vulkan* to_internal(const Shader* param) | |
{ | |
return static_cast<Shader_Vulkan*>(param->internal_state.get()); | |
} | |
PipelineState_Vulkan* to_internal(const PipelineState* param) | |
{ | |
return static_cast<PipelineState_Vulkan*>(param->internal_state.get()); | |
} | |
BVH_Vulkan* to_internal(const RaytracingAccelerationStructure* param) | |
{ | |
return static_cast<BVH_Vulkan*>(param->internal_state.get()); | |
} | |
RTPipelineState_Vulkan* to_internal(const RaytracingPipelineState* param) | |
{ | |
return static_cast<RTPipelineState_Vulkan*>(param->internal_state.get()); | |
} | |
SwapChain_Vulkan* to_internal(const SwapChain* param) | |
{ | |
return static_cast<SwapChain_Vulkan*>(param->internal_state.get()); | |
} | |
inline const std::string GetCachePath() | |
{ | |
return wi::helper::GetTempDirectoryPath() + "wiPipelineCache_Vulkan"; | |
} | |
bool CreateSwapChainInternal( | |
SwapChain_Vulkan* internal_state, | |
VkPhysicalDevice physicalDevice, | |
VkDevice device, | |
std::shared_ptr<GraphicsDevice_Vulkan::AllocationHandler> allocationhandler | |
) | |
{ | |
// In vulkan, the swapchain recreate can happen whenever it gets outdated, it's not in application's control | |
// so we have to be extra careful | |
std::scoped_lock lock(internal_state->locker); | |
VkResult res; | |
VkSurfaceCapabilitiesKHR swapchain_capabilities; | |
res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, internal_state->surface, &swapchain_capabilities); | |
assert(res == VK_SUCCESS); | |
uint32_t formatCount; | |
res = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, internal_state->surface, &formatCount, nullptr); | |
assert(res == VK_SUCCESS); | |
wi::vector<VkSurfaceFormatKHR> swapchain_formats(formatCount); | |
res = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, internal_state->surface, &formatCount, swapchain_formats.data()); | |
assert(res == VK_SUCCESS); | |
uint32_t presentModeCount; | |
res = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, internal_state->surface, &presentModeCount, nullptr); | |
assert(res == VK_SUCCESS); | |
wi::vector<VkPresentModeKHR> swapchain_presentModes(presentModeCount); | |
swapchain_presentModes.resize(presentModeCount); | |
res = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, internal_state->surface, &presentModeCount, swapchain_presentModes.data()); | |
assert(res == VK_SUCCESS); | |
VkSurfaceFormatKHR surfaceFormat = {}; | |
surfaceFormat.format = _ConvertFormat(internal_state->desc.format); | |
surfaceFormat.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; | |
bool valid = false; | |
for (const auto& format : swapchain_formats) | |
{ | |
if (!internal_state->desc.allow_hdr && format.colorSpace != VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) | |
continue; | |
if (format.format == surfaceFormat.format) | |
{ | |
surfaceFormat = format; | |
valid = true; | |
break; | |
} | |
} | |
if (!valid) | |
{ | |
surfaceFormat.format = VK_FORMAT_B8G8R8A8_UNORM; | |
surfaceFormat.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; | |
} | |
// For now, we only include the color spaces that were tested successfully: | |
ColorSpace prev_colorspace = internal_state->colorSpace; | |
switch (surfaceFormat.colorSpace) | |
{ | |
default: | |
case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR: | |
internal_state->colorSpace = ColorSpace::SRGB; | |
break; | |
case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT: | |
internal_state->colorSpace = ColorSpace::HDR_LINEAR; | |
break; | |
case VK_COLOR_SPACE_HDR10_ST2084_EXT: | |
internal_state->colorSpace = ColorSpace::HDR10_ST2084; | |
break; | |
} | |
if (prev_colorspace != internal_state->colorSpace) | |
{ | |
if (internal_state->swapChain != VK_NULL_HANDLE) | |
{ | |
// For some reason, if the swapchain gets recreated (via oldSwapChain) with different color space but same image format, | |
// the color space change will not be applied | |
res = vkDeviceWaitIdle(device); | |
assert(res == VK_SUCCESS); | |
vkDestroySwapchainKHR(device, internal_state->swapChain, nullptr); | |
internal_state->swapChain = nullptr; | |
} | |
} | |
if (swapchain_capabilities.currentExtent.width != 0xFFFFFFFF && swapchain_capabilities.currentExtent.width != 0xFFFFFFFF) | |
{ | |
internal_state->swapChainExtent = swapchain_capabilities.currentExtent; | |
} | |
else | |
{ | |
internal_state->swapChainExtent = { internal_state->desc.width, internal_state->desc.height }; | |
internal_state->swapChainExtent.width = std::max(swapchain_capabilities.minImageExtent.width, std::min(swapchain_capabilities.maxImageExtent.width, internal_state->swapChainExtent.width)); | |
internal_state->swapChainExtent.height = std::max(swapchain_capabilities.minImageExtent.height, std::min(swapchain_capabilities.maxImageExtent.height, internal_state->swapChainExtent.height)); | |
} | |
uint32_t imageCount = std::max(internal_state->desc.buffer_count, swapchain_capabilities.minImageCount); | |
if ((swapchain_capabilities.maxImageCount > 0) && (imageCount > swapchain_capabilities.maxImageCount)) | |
{ | |
imageCount = swapchain_capabilities.maxImageCount; | |
} | |
VkSwapchainCreateInfoKHR createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; | |
createInfo.surface = internal_state->surface; | |
createInfo.minImageCount = imageCount; | |
createInfo.imageFormat = surfaceFormat.format; | |
createInfo.imageColorSpace = surfaceFormat.colorSpace; | |
createInfo.imageExtent = internal_state->swapChainExtent; | |
createInfo.imageArrayLayers = 1; | |
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; | |
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; | |
createInfo.preTransform = swapchain_capabilities.currentTransform; | |
createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; // The only one that is always supported | |
if (!internal_state->desc.vsync) | |
{ | |
// The mailbox/immediate present mode is not necessarily supported: | |
for (auto& presentMode : swapchain_presentModes) | |
{ | |
if (presentMode == VK_PRESENT_MODE_MAILBOX_KHR) | |
{ | |
createInfo.presentMode = VK_PRESENT_MODE_MAILBOX_KHR; | |
break; | |
} | |
if (presentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) | |
{ | |
createInfo.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; | |
} | |
} | |
} | |
createInfo.clipped = VK_TRUE; | |
createInfo.oldSwapchain = internal_state->swapChain; | |
res = vkCreateSwapchainKHR(device, &createInfo, nullptr, &internal_state->swapChain); | |
assert(res == VK_SUCCESS); | |
if (createInfo.oldSwapchain != VK_NULL_HANDLE) | |
{ | |
vkDestroySwapchainKHR(device, createInfo.oldSwapchain, nullptr); | |
} | |
res = vkGetSwapchainImagesKHR(device, internal_state->swapChain, &imageCount, nullptr); | |
assert(res == VK_SUCCESS); | |
internal_state->swapChainImages.resize(imageCount); | |
res = vkGetSwapchainImagesKHR(device, internal_state->swapChain, &imageCount, internal_state->swapChainImages.data()); | |
assert(res == VK_SUCCESS); | |
internal_state->swapChainImageFormat = surfaceFormat.format; | |
// Create swap chain render targets: | |
internal_state->swapChainImageViews.resize(internal_state->swapChainImages.size()); | |
for (size_t i = 0; i < internal_state->swapChainImages.size(); ++i) | |
{ | |
VkImageViewCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; | |
createInfo.image = internal_state->swapChainImages[i]; | |
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; | |
createInfo.format = internal_state->swapChainImageFormat; | |
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; | |
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; | |
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; | |
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; | |
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
createInfo.subresourceRange.baseMipLevel = 0; | |
createInfo.subresourceRange.levelCount = 1; | |
createInfo.subresourceRange.baseArrayLayer = 0; | |
createInfo.subresourceRange.layerCount = 1; | |
if (internal_state->swapChainImageViews[i] != VK_NULL_HANDLE) | |
{ | |
allocationhandler->destroylocker.lock(); | |
allocationhandler->destroyer_imageviews.push_back(std::make_pair(internal_state->swapChainImageViews[i], allocationhandler->framecount)); | |
allocationhandler->destroylocker.unlock(); | |
} | |
res = vkCreateImageView(device, &createInfo, nullptr, &internal_state->swapChainImageViews[i]); | |
assert(res == VK_SUCCESS); | |
} | |
VkSemaphoreCreateInfo semaphoreInfo = {}; | |
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; | |
if (internal_state->swapchainAcquireSemaphore == VK_NULL_HANDLE) | |
{ | |
res = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &internal_state->swapchainAcquireSemaphore); | |
assert(res == VK_SUCCESS); | |
} | |
if (internal_state->swapchainReleaseSemaphore == VK_NULL_HANDLE) | |
{ | |
res = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &internal_state->swapchainReleaseSemaphore); | |
assert(res == VK_SUCCESS); | |
} | |
return true; | |
} | |
} | |
using namespace vulkan_internal; | |
void GraphicsDevice_Vulkan::CommandQueue::submit(GraphicsDevice_Vulkan* device, VkFence fence) | |
{ | |
VkSubmitInfo submitInfo = {}; | |
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; | |
submitInfo.commandBufferCount = (uint32_t)submit_cmds.size(); | |
submitInfo.pCommandBuffers = submit_cmds.data(); | |
submitInfo.waitSemaphoreCount = (uint32_t)submit_waitSemaphores.size(); | |
submitInfo.pWaitSemaphores = submit_waitSemaphores.data(); | |
submitInfo.pWaitDstStageMask = submit_waitStages.data(); | |
submitInfo.signalSemaphoreCount = (uint32_t)submit_signalSemaphores.size(); | |
submitInfo.pSignalSemaphores = submit_signalSemaphores.data(); | |
VkTimelineSemaphoreSubmitInfo timelineInfo = {}; | |
timelineInfo.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO; | |
timelineInfo.pNext = nullptr; | |
timelineInfo.waitSemaphoreValueCount = (uint32_t)submit_waitValues.size(); | |
timelineInfo.pWaitSemaphoreValues = submit_waitValues.data(); | |
timelineInfo.signalSemaphoreValueCount = (uint32_t)submit_signalValues.size(); | |
timelineInfo.pSignalSemaphoreValues = submit_signalValues.data(); | |
submitInfo.pNext = &timelineInfo; | |
VkResult res = vkQueueSubmit(queue, 1, &submitInfo, fence); | |
assert(res == VK_SUCCESS); | |
if (!submit_swapchains.empty()) | |
{ | |
VkPresentInfoKHR presentInfo = {}; | |
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; | |
presentInfo.waitSemaphoreCount = (uint32_t)submit_signalSemaphores.size(); | |
presentInfo.pWaitSemaphores = submit_signalSemaphores.data(); | |
presentInfo.swapchainCount = (uint32_t)submit_swapchains.size(); | |
presentInfo.pSwapchains = submit_swapchains.data(); | |
presentInfo.pImageIndices = submit_swapChainImageIndices.data(); | |
res = vkQueuePresentKHR(queue, &presentInfo); | |
if (res != VK_SUCCESS) | |
{ | |
// Handle outdated error in present: | |
if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) | |
{ | |
for (auto& swapchain : swapchain_updates) | |
{ | |
auto internal_state = to_internal(&swapchain); | |
bool success = CreateSwapChainInternal(internal_state, device->physicalDevice, device->device, device->allocationhandler); | |
assert(success); | |
} | |
} | |
else | |
{ | |
assert(0); | |
} | |
} | |
} | |
swapchain_updates.clear(); | |
submit_swapchains.clear(); | |
submit_swapChainImageIndices.clear(); | |
submit_waitStages.clear(); | |
submit_waitSemaphores.clear(); | |
submit_waitValues.clear(); | |
submit_signalSemaphores.clear(); | |
submit_signalValues.clear(); | |
submit_cmds.clear(); | |
} | |
void GraphicsDevice_Vulkan::CopyAllocator::init(GraphicsDevice_Vulkan* device) | |
{ | |
this->device = device; | |
VkSemaphoreTypeCreateInfo timelineCreateInfo = {}; | |
timelineCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; | |
timelineCreateInfo.pNext = nullptr; | |
timelineCreateInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; | |
timelineCreateInfo.initialValue = 0; | |
VkSemaphoreCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; | |
createInfo.pNext = &timelineCreateInfo; | |
createInfo.flags = 0; | |
VkResult res = vkCreateSemaphore(device->device, &createInfo, nullptr, &semaphore); | |
assert(res == VK_SUCCESS); | |
} | |
void GraphicsDevice_Vulkan::CopyAllocator::destroy() | |
{ | |
vkQueueWaitIdle(device->copyQueue); | |
for (auto& x : freelist) | |
{ | |
vkDestroyCommandPool(device->device, x.commandPool, nullptr); | |
} | |
vkDestroySemaphore(device->device, semaphore, nullptr); | |
} | |
GraphicsDevice_Vulkan::CopyAllocator::CopyCMD GraphicsDevice_Vulkan::CopyAllocator::allocate(uint64_t staging_size) | |
{ | |
locker.lock(); | |
// create a new command list if there are no free ones: | |
if (freelist.empty()) | |
{ | |
CopyCMD cmd; | |
VkCommandPoolCreateInfo poolInfo = {}; | |
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; | |
poolInfo.queueFamilyIndex = device->copyFamily; | |
poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; | |
VkResult res = vkCreateCommandPool(device->device, &poolInfo, nullptr, &cmd.commandPool); | |
assert(res == VK_SUCCESS); | |
VkCommandBufferAllocateInfo commandBufferInfo = {}; | |
commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; | |
commandBufferInfo.commandBufferCount = 1; | |
commandBufferInfo.commandPool = cmd.commandPool; | |
commandBufferInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; | |
res = vkAllocateCommandBuffers(device->device, &commandBufferInfo, &cmd.commandBuffer); | |
assert(res == VK_SUCCESS); | |
freelist.push_back(cmd); | |
} | |
CopyCMD cmd = freelist.back(); | |
if (cmd.uploadbuffer.desc.size < staging_size) | |
{ | |
// Try to search for a staging buffer that can fit the request: | |
for (size_t i = 0; i < freelist.size(); ++i) | |
{ | |
if (freelist[i].uploadbuffer.desc.size >= staging_size) | |
{ | |
cmd = freelist[i]; | |
std::swap(freelist[i], freelist.back()); | |
break; | |
} | |
} | |
} | |
freelist.pop_back(); | |
locker.unlock(); | |
// If no buffer was found that fits the data, create one: | |
if (cmd.uploadbuffer.desc.size < staging_size) | |
{ | |
GPUBufferDesc uploaddesc; | |
uploaddesc.size = wi::math::GetNextPowerOfTwo(staging_size); | |
uploaddesc.usage = Usage::UPLOAD; | |
bool upload_success = device->CreateBuffer(&uploaddesc, nullptr, &cmd.uploadbuffer); | |
assert(upload_success); | |
device->SetName(&cmd.uploadbuffer, "CopyAllocator::uploadBuffer"); | |
} | |
// begin command list in valid state: | |
VkResult res = vkResetCommandPool(device->device, cmd.commandPool, 0); | |
assert(res == VK_SUCCESS); | |
VkCommandBufferBeginInfo beginInfo = {}; | |
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; | |
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; | |
beginInfo.pInheritanceInfo = nullptr; | |
res = vkBeginCommandBuffer(cmd.commandBuffer, &beginInfo); | |
assert(res == VK_SUCCESS); | |
return cmd; | |
} | |
void GraphicsDevice_Vulkan::CopyAllocator::submit(CopyCMD cmd) | |
{ | |
VkResult res = vkEndCommandBuffer(cmd.commandBuffer); | |
assert(res == VK_SUCCESS); | |
// It was very slow in Vulkan to submit the copies immediately | |
// In Vulkan, the submit is not thread safe, so it had to be locked | |
// Instead, the submits are batched and performed in flush() function | |
locker.lock(); | |
cmd.target = ++fenceValue; | |
worklist.push_back(cmd); | |
submit_cmds.push_back(cmd.commandBuffer); | |
submit_wait = std::max(submit_wait, cmd.target); | |
locker.unlock(); | |
} | |
uint64_t GraphicsDevice_Vulkan::CopyAllocator::flush() | |
{ | |
locker.lock(); | |
if (!submit_cmds.empty()) | |
{ | |
VkSubmitInfo submitInfo = {}; | |
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; | |
submitInfo.commandBufferCount = (uint32_t)submit_cmds.size(); | |
submitInfo.pCommandBuffers = submit_cmds.data(); | |
submitInfo.pSignalSemaphores = &semaphore; | |
submitInfo.signalSemaphoreCount = 1; | |
VkTimelineSemaphoreSubmitInfo timelineInfo = {}; | |
timelineInfo.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO; | |
timelineInfo.pNext = nullptr; | |
timelineInfo.waitSemaphoreValueCount = 0; | |
timelineInfo.pWaitSemaphoreValues = nullptr; | |
timelineInfo.signalSemaphoreValueCount = 1; | |
timelineInfo.pSignalSemaphoreValues = &submit_wait; | |
submitInfo.pNext = &timelineInfo; | |
VkResult res = vkQueueSubmit(device->copyQueue, 1, &submitInfo, VK_NULL_HANDLE); | |
assert(res == VK_SUCCESS); | |
submit_cmds.clear(); | |
} | |
// free up the finished command lists: | |
uint64_t completed_fence_value; | |
VkResult res = vkGetSemaphoreCounterValue(device->device, semaphore, &completed_fence_value); | |
assert(res == VK_SUCCESS); | |
for (size_t i = 0; i < worklist.size(); ++i) | |
{ | |
if (worklist[i].target <= completed_fence_value) | |
{ | |
freelist.push_back(worklist[i]); | |
worklist[i] = worklist.back(); | |
worklist.pop_back(); | |
i--; | |
} | |
} | |
uint64_t value = submit_wait; | |
submit_wait = 0; | |
locker.unlock(); | |
return value; | |
} | |
void GraphicsDevice_Vulkan::DescriptorBinderPool::init(GraphicsDevice_Vulkan* device) | |
{ | |
this->device = device; | |
VkResult res; | |
// Create descriptor pool: | |
VkDescriptorPoolSize poolSizes[10] = {}; | |
uint32_t count = 0; | |
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; | |
poolSizes[0].descriptorCount = DESCRIPTORBINDER_CBV_COUNT * poolSize; | |
count++; | |
poolSizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; | |
poolSizes[1].descriptorCount = DESCRIPTORBINDER_CBV_COUNT * poolSize; | |
count++; | |
poolSizes[2].type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; | |
poolSizes[2].descriptorCount = DESCRIPTORBINDER_SRV_COUNT * poolSize; | |
count++; | |
poolSizes[3].type = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; | |
poolSizes[3].descriptorCount = DESCRIPTORBINDER_SRV_COUNT * poolSize; | |
count++; | |
poolSizes[4].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; | |
poolSizes[4].descriptorCount = DESCRIPTORBINDER_SRV_COUNT * poolSize; | |
count++; | |
poolSizes[5].type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; | |
poolSizes[5].descriptorCount = DESCRIPTORBINDER_UAV_COUNT * poolSize; | |
count++; | |
poolSizes[6].type = VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER; | |
poolSizes[6].descriptorCount = DESCRIPTORBINDER_UAV_COUNT * poolSize; | |
count++; | |
poolSizes[7].type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; | |
poolSizes[7].descriptorCount = DESCRIPTORBINDER_UAV_COUNT * poolSize; | |
count++; | |
poolSizes[8].type = VK_DESCRIPTOR_TYPE_SAMPLER; | |
poolSizes[8].descriptorCount = DESCRIPTORBINDER_SAMPLER_COUNT * poolSize; | |
count++; | |
if (device->CheckCapability(GraphicsDeviceCapability::RAYTRACING)) | |
{ | |
poolSizes[9].type = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; | |
poolSizes[9].descriptorCount = DESCRIPTORBINDER_SRV_COUNT * poolSize; | |
count++; | |
} | |
VkDescriptorPoolCreateInfo poolInfo = {}; | |
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; | |
poolInfo.poolSizeCount = count; | |
poolInfo.pPoolSizes = poolSizes; | |
poolInfo.maxSets = poolSize; | |
res = vkCreateDescriptorPool(device->device, &poolInfo, nullptr, &descriptorPool); | |
assert(res == VK_SUCCESS); | |
// WARNING: MUST NOT CALL reset() HERE! | |
// This is because init can be called mid-frame when there is allocation error, but the bindings must be retained! | |
} | |
void GraphicsDevice_Vulkan::DescriptorBinderPool::destroy() | |
{ | |
if (descriptorPool != VK_NULL_HANDLE) | |
{ | |
device->allocationhandler->destroylocker.lock(); | |
device->allocationhandler->destroyer_descriptorPools.push_back(std::make_pair(descriptorPool, device->FRAMECOUNT)); | |
descriptorPool = VK_NULL_HANDLE; | |
device->allocationhandler->destroylocker.unlock(); | |
} | |
} | |
void GraphicsDevice_Vulkan::DescriptorBinderPool::reset() | |
{ | |
if (descriptorPool != VK_NULL_HANDLE) | |
{ | |
VkResult res = vkResetDescriptorPool(device->device, descriptorPool, 0); | |
assert(res == VK_SUCCESS); | |
} | |
} | |
void GraphicsDevice_Vulkan::DescriptorBinder::init(GraphicsDevice_Vulkan* device) | |
{ | |
this->device = device; | |
// Important that these don't reallocate themselves during writing descriptors! | |
descriptorWrites.reserve(128); | |
bufferInfos.reserve(128); | |
imageInfos.reserve(128); | |
texelBufferViews.reserve(128); | |
accelerationStructureViews.reserve(128); | |
} | |
void GraphicsDevice_Vulkan::DescriptorBinder::reset() | |
{ | |
table = {}; | |
dirty = true; | |
} | |
void GraphicsDevice_Vulkan::DescriptorBinder::flush(bool graphics, CommandList cmd) | |
{ | |
if (dirty == DIRTY_NONE) | |
return; | |
CommandList_Vulkan& commandlist = device->GetCommandList(cmd); | |
auto pso_internal = graphics ? to_internal(commandlist.active_pso) : nullptr; | |
auto cs_internal = graphics ? nullptr : to_internal(commandlist.active_cs); | |
VkCommandBuffer commandBuffer = commandlist.GetCommandBuffer(); | |
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; | |
VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; | |
VkDescriptorSet descriptorSet = VK_NULL_HANDLE; | |
uint32_t uniform_buffer_dynamic_count = 0; | |
if (graphics) | |
{ | |
pipelineLayout = pso_internal->pipelineLayout; | |
descriptorSetLayout = pso_internal->descriptorSetLayout; | |
descriptorSet = descriptorSet_graphics; | |
uniform_buffer_dynamic_count = (uint32_t)pso_internal->uniform_buffer_dynamic_slots.size(); | |
for (size_t i = 0; i < pso_internal->uniform_buffer_dynamic_slots.size(); ++i) | |
{ | |
uniform_buffer_dynamic_offsets[i] = (uint32_t)table.CBV_offset[pso_internal->uniform_buffer_dynamic_slots[i]]; | |
} | |
} | |
else | |
{ | |
pipelineLayout = cs_internal->pipelineLayout_cs; | |
descriptorSetLayout = cs_internal->descriptorSetLayout; | |
descriptorSet = descriptorSet_compute; | |
uniform_buffer_dynamic_count = (uint32_t)cs_internal->uniform_buffer_dynamic_slots.size(); | |
for (size_t i = 0; i < cs_internal->uniform_buffer_dynamic_slots.size(); ++i) | |
{ | |
uniform_buffer_dynamic_offsets[i] = (uint32_t)table.CBV_offset[cs_internal->uniform_buffer_dynamic_slots[i]]; | |
} | |
} | |
if (dirty & DIRTY_DESCRIPTOR) | |
{ | |
auto& binder_pool = commandlist.binder_pools[device->GetBufferIndex()]; | |
VkDescriptorSetAllocateInfo allocInfo = {}; | |
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; | |
allocInfo.descriptorPool = binder_pool.descriptorPool; | |
allocInfo.descriptorSetCount = 1; | |
allocInfo.pSetLayouts = &descriptorSetLayout; | |
VkResult res = vkAllocateDescriptorSets(device->device, &allocInfo, &descriptorSet); | |
while (res == VK_ERROR_OUT_OF_POOL_MEMORY) | |
{ | |
binder_pool.poolSize *= 2; | |
binder_pool.destroy(); | |
binder_pool.init(device); | |
allocInfo.descriptorPool = binder_pool.descriptorPool; | |
res = vkAllocateDescriptorSets(device->device, &allocInfo, &descriptorSet); | |
} | |
assert(res == VK_SUCCESS); | |
descriptorWrites.clear(); | |
bufferInfos.clear(); | |
imageInfos.clear(); | |
texelBufferViews.clear(); | |
accelerationStructureViews.clear(); | |
const auto& layoutBindings = graphics ? pso_internal->layoutBindings : cs_internal->layoutBindings; | |
const auto& imageViewTypes = graphics ? pso_internal->imageViewTypes : cs_internal->imageViewTypes; | |
int i = 0; | |
for (auto& x : layoutBindings) | |
{ | |
if (x.pImmutableSamplers != nullptr) | |
{ | |
i++; | |
continue; | |
} | |
VkImageViewType viewtype = imageViewTypes[i++]; | |
for (uint32_t descriptor_index = 0; descriptor_index < x.descriptorCount; ++descriptor_index) | |
{ | |
uint32_t unrolled_binding = x.binding + descriptor_index; | |
descriptorWrites.emplace_back(); | |
auto& write = descriptorWrites.back(); | |
write = {}; | |
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; | |
write.dstSet = descriptorSet; | |
write.dstArrayElement = descriptor_index; | |
write.descriptorType = x.descriptorType; | |
write.dstBinding = x.binding; | |
write.descriptorCount = 1; | |
switch (x.descriptorType) | |
{ | |
case VK_DESCRIPTOR_TYPE_SAMPLER: | |
{ | |
imageInfos.emplace_back(); | |
write.pImageInfo = &imageInfos.back(); | |
imageInfos.back() = {}; | |
const uint32_t original_binding = unrolled_binding - VULKAN_BINDING_SHIFT_S; | |
const Sampler& sampler = table.SAM[original_binding]; | |
if (!sampler.IsValid()) | |
{ | |
imageInfos.back().sampler = device->nullSampler; | |
} | |
else | |
{ | |
imageInfos.back().sampler = to_internal(&sampler)->resource; | |
} | |
} | |
break; | |
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: | |
{ | |
imageInfos.emplace_back(); | |
write.pImageInfo = &imageInfos.back(); | |
imageInfos.back() = {}; | |
const uint32_t original_binding = unrolled_binding - VULKAN_BINDING_SHIFT_T; | |
const GPUResource& resource = table.SRV[original_binding]; | |
if (!resource.IsValid() || !resource.IsTexture()) | |
{ | |
switch (viewtype) | |
{ | |
case VK_IMAGE_VIEW_TYPE_1D: | |
imageInfos.back().imageView = device->nullImageView1D; | |
break; | |
case VK_IMAGE_VIEW_TYPE_2D: | |
imageInfos.back().imageView = device->nullImageView2D; | |
break; | |
case VK_IMAGE_VIEW_TYPE_3D: | |
imageInfos.back().imageView = device->nullImageView3D; | |
break; | |
case VK_IMAGE_VIEW_TYPE_CUBE: | |
imageInfos.back().imageView = device->nullImageViewCube; | |
break; | |
case VK_IMAGE_VIEW_TYPE_1D_ARRAY: | |
imageInfos.back().imageView = device->nullImageView1DArray; | |
break; | |
case VK_IMAGE_VIEW_TYPE_2D_ARRAY: | |
imageInfos.back().imageView = device->nullImageView2DArray; | |
break; | |
case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: | |
imageInfos.back().imageView = device->nullImageViewCubeArray; | |
break; | |
case VK_IMAGE_VIEW_TYPE_MAX_ENUM: | |
break; | |
default: | |
break; | |
} | |
imageInfos.back().imageLayout = VK_IMAGE_LAYOUT_GENERAL; | |
} | |
else | |
{ | |
int subresource = table.SRV_index[original_binding]; | |
auto texture_internal = to_internal((const Texture*)&resource); | |
auto& subresource_descriptor = subresource >= 0 ? texture_internal->subresources_srv[subresource] : texture_internal->srv; | |
imageInfos.back().imageView = subresource_descriptor.image_view; | |
imageInfos.back().imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; | |
} | |
} | |
break; | |
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: | |
{ | |
imageInfos.emplace_back(); | |
write.pImageInfo = &imageInfos.back(); | |
imageInfos.back() = {}; | |
imageInfos.back().imageLayout = VK_IMAGE_LAYOUT_GENERAL; | |
const uint32_t original_binding = unrolled_binding - VULKAN_BINDING_SHIFT_U; | |
const GPUResource& resource = table.UAV[original_binding]; | |
if (!resource.IsValid() || !resource.IsTexture()) | |
{ | |
switch (viewtype) | |
{ | |
case VK_IMAGE_VIEW_TYPE_1D: | |
imageInfos.back().imageView = device->nullImageView1D; | |
break; | |
case VK_IMAGE_VIEW_TYPE_2D: | |
imageInfos.back().imageView = device->nullImageView2D; | |
break; | |
case VK_IMAGE_VIEW_TYPE_3D: | |
imageInfos.back().imageView = device->nullImageView3D; | |
break; | |
case VK_IMAGE_VIEW_TYPE_CUBE: | |
imageInfos.back().imageView = device->nullImageViewCube; | |
break; | |
case VK_IMAGE_VIEW_TYPE_1D_ARRAY: | |
imageInfos.back().imageView = device->nullImageView1DArray; | |
break; | |
case VK_IMAGE_VIEW_TYPE_2D_ARRAY: | |
imageInfos.back().imageView = device->nullImageView2DArray; | |
break; | |
case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: | |
imageInfos.back().imageView = device->nullImageViewCubeArray; | |
break; | |
case VK_IMAGE_VIEW_TYPE_MAX_ENUM: | |
break; | |
default: | |
break; | |
} | |
} | |
else | |
{ | |
int subresource = table.UAV_index[original_binding]; | |
auto texture_internal = to_internal((const Texture*)&resource); | |
auto& subresource_descriptor = subresource >= 0 ? texture_internal->subresources_uav[subresource] : texture_internal->uav; | |
imageInfos.back().imageView = subresource_descriptor.image_view; | |
} | |
} | |
break; | |
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: | |
{ | |
bufferInfos.emplace_back(); | |
write.pBufferInfo = &bufferInfos.back(); | |
bufferInfos.back() = {}; | |
const uint32_t original_binding = unrolled_binding - VULKAN_BINDING_SHIFT_B; | |
const GPUBuffer& buffer = table.CBV[original_binding]; | |
uint64_t offset = table.CBV_offset[original_binding]; | |
if (!buffer.IsValid()) | |
{ | |
bufferInfos.back().buffer = device->nullBuffer; | |
bufferInfos.back().range = VK_WHOLE_SIZE; | |
} | |
else | |
{ | |
auto internal_state = to_internal(&buffer); | |
bufferInfos.back().buffer = internal_state->resource; | |
bufferInfos.back().offset = offset; | |
if (graphics) | |
{ | |
bufferInfos.back().range = pso_internal->uniform_buffer_sizes[original_binding]; | |
} | |
else | |
{ | |
bufferInfos.back().range = cs_internal->uniform_buffer_sizes[original_binding]; | |
} | |
if (bufferInfos.back().range == 0ull) | |
{ | |
bufferInfos.back().range = VK_WHOLE_SIZE; | |
} | |
} | |
} | |
break; | |
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: | |
{ | |
bufferInfos.emplace_back(); | |
write.pBufferInfo = &bufferInfos.back(); | |
bufferInfos.back() = {}; | |
const uint32_t original_binding = unrolled_binding - VULKAN_BINDING_SHIFT_B; | |
const GPUBuffer& buffer = table.CBV[original_binding]; | |
if (!buffer.IsValid()) | |
{ | |
bufferInfos.back().buffer = device->nullBuffer; | |
bufferInfos.back().range = VK_WHOLE_SIZE; | |
} | |
else | |
{ | |
auto internal_state = to_internal(&buffer); | |
bufferInfos.back().buffer = internal_state->resource; | |
if (graphics) | |
{ | |
bufferInfos.back().range = pso_internal->uniform_buffer_sizes[original_binding]; | |
} | |
else | |
{ | |
bufferInfos.back().range = cs_internal->uniform_buffer_sizes[original_binding]; | |
} | |
if (bufferInfos.back().range == 0ull) | |
{ | |
bufferInfos.back().range = VK_WHOLE_SIZE; | |
} | |
} | |
} | |
break; | |
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: | |
{ | |
texelBufferViews.emplace_back(); | |
write.pTexelBufferView = &texelBufferViews.back(); | |
texelBufferViews.back() = {}; | |
const uint32_t original_binding = unrolled_binding - VULKAN_BINDING_SHIFT_T; | |
const GPUResource& resource = table.SRV[original_binding]; | |
if (!resource.IsValid() || !resource.IsBuffer()) | |
{ | |
texelBufferViews.back() = device->nullBufferView; | |
} | |
else | |
{ | |
int subresource = table.SRV_index[original_binding]; | |
auto buffer_internal = to_internal((const GPUBuffer*)&resource); | |
auto& subresource_descriptor = subresource >= 0 ? buffer_internal->subresources_srv[subresource] : buffer_internal->srv; | |
texelBufferViews.back() = subresource_descriptor.buffer_view; | |
} | |
} | |
break; | |
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: | |
{ | |
texelBufferViews.emplace_back(); | |
write.pTexelBufferView = &texelBufferViews.back(); | |
texelBufferViews.back() = {}; | |
const uint32_t original_binding = unrolled_binding - VULKAN_BINDING_SHIFT_U; | |
const GPUResource& resource = table.UAV[original_binding]; | |
if (!resource.IsValid() || !resource.IsBuffer()) | |
{ | |
texelBufferViews.back() = device->nullBufferView; | |
} | |
else | |
{ | |
int subresource = table.UAV_index[original_binding]; | |
auto buffer_internal = to_internal((const GPUBuffer*)&resource); | |
auto& subresource_descriptor = subresource >= 0 ? buffer_internal->subresources_uav[subresource] : buffer_internal->uav; | |
texelBufferViews.back() = subresource_descriptor.buffer_view; | |
} | |
} | |
break; | |
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: | |
{ | |
bufferInfos.emplace_back(); | |
write.pBufferInfo = &bufferInfos.back(); | |
bufferInfos.back() = {}; | |
if (x.binding < VULKAN_BINDING_SHIFT_U) | |
{ | |
// SRV | |
const uint32_t original_binding = unrolled_binding - VULKAN_BINDING_SHIFT_T; | |
const GPUResource& resource = table.SRV[original_binding]; | |
if (!resource.IsValid() || !resource.IsBuffer()) | |
{ | |
bufferInfos.back().buffer = device->nullBuffer; | |
bufferInfos.back().range = VK_WHOLE_SIZE; | |
} | |
else | |
{ | |
int subresource = table.SRV_index[original_binding]; | |
auto buffer_internal = to_internal((const GPUBuffer*)&resource); | |
auto& subresource_descriptor = subresource >= 0 ? buffer_internal->subresources_srv[subresource] : buffer_internal->srv; | |
bufferInfos.back() = subresource_descriptor.buffer_info; | |
} | |
} | |
else | |
{ | |
// UAV | |
const uint32_t original_binding = unrolled_binding - VULKAN_BINDING_SHIFT_U; | |
const GPUResource& resource = table.UAV[original_binding]; | |
if (!resource.IsValid() || !resource.IsBuffer()) | |
{ | |
bufferInfos.back().buffer = device->nullBuffer; | |
bufferInfos.back().range = VK_WHOLE_SIZE; | |
} | |
else | |
{ | |
int subresource = table.UAV_index[original_binding]; | |
auto buffer_internal = to_internal((const GPUBuffer*)&resource); | |
auto& subresource_descriptor = subresource >= 0 ? buffer_internal->subresources_uav[subresource] : buffer_internal->uav; | |
bufferInfos.back() = subresource_descriptor.buffer_info; | |
} | |
} | |
} | |
break; | |
case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR: | |
{ | |
accelerationStructureViews.emplace_back(); | |
write.pNext = &accelerationStructureViews.back(); | |
accelerationStructureViews.back() = {}; | |
accelerationStructureViews.back().sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR; | |
accelerationStructureViews.back().accelerationStructureCount = 1; | |
const uint32_t original_binding = unrolled_binding - VULKAN_BINDING_SHIFT_T; | |
const GPUResource& resource = table.SRV[original_binding]; | |
if (!resource.IsValid() || !resource.IsAccelerationStructure()) | |
{ | |
assert(0); // invalid acceleration structure! | |
} | |
else | |
{ | |
auto as_internal = to_internal((const RaytracingAccelerationStructure*)&resource); | |
accelerationStructureViews.back().pAccelerationStructures = &as_internal->resource; | |
} | |
} | |
break; | |
default: break; | |
} | |
} | |
} | |
vkUpdateDescriptorSets( | |
device->device, | |
(uint32_t)descriptorWrites.size(), | |
descriptorWrites.data(), | |
0, | |
nullptr | |
); | |
} | |
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; | |
if (!graphics) | |
{ | |
bindPoint = VK_PIPELINE_BIND_POINT_COMPUTE; | |
if (commandlist.active_cs->stage == ShaderStage::LIB) | |
{ | |
bindPoint = VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR; | |
} | |
} | |
vkCmdBindDescriptorSets( | |
commandBuffer, | |
bindPoint, | |
pipelineLayout, | |
0, | |
1, | |
&descriptorSet, | |
uniform_buffer_dynamic_count, | |
uniform_buffer_dynamic_offsets | |
); | |
// Save last used descriptor set handles: | |
// This is needed to handle the case when descriptorSet is not allocated, but only dynamic offsets are updated | |
if (graphics) | |
{ | |
descriptorSet_graphics = descriptorSet; | |
} | |
else | |
{ | |
descriptorSet_compute = descriptorSet; | |
} | |
dirty = DIRTY_NONE; | |
} | |
void GraphicsDevice_Vulkan::pso_validate(CommandList cmd) | |
{ | |
CommandList_Vulkan& commandlist = GetCommandList(cmd); | |
if (!commandlist.dirty_pso) | |
return; | |
const PipelineState* pso = commandlist.active_pso; | |
size_t pipeline_hash = commandlist.prev_pipeline_hash; | |
auto internal_state = to_internal(pso); | |
VkPipeline pipeline = VK_NULL_HANDLE; | |
auto it = pipelines_global.find(pipeline_hash); | |
if (it == pipelines_global.end()) | |
{ | |
for (auto& x : commandlist.pipelines_worker) | |
{ | |
if (pipeline_hash == x.first) | |
{ | |
pipeline = x.second; | |
break; | |
} | |
} | |
if (pipeline == VK_NULL_HANDLE) | |
{ | |
VkGraphicsPipelineCreateInfo pipelineInfo = internal_state->pipelineInfo; // make a copy here | |
// MSAA: | |
VkPipelineMultisampleStateCreateInfo multisampling = {}; | |
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; | |
multisampling.sampleShadingEnable = VK_FALSE; | |
multisampling.rasterizationSamples = (VkSampleCountFlagBits)commandlist.renderpass_info.sample_count; | |
if (pso->desc.rs != nullptr) | |
{ | |
const RasterizerState& desc = *pso->desc.rs; | |
if (desc.forced_sample_count > 1) | |
{ | |
multisampling.rasterizationSamples = (VkSampleCountFlagBits)desc.forced_sample_count; | |
} | |
} | |
multisampling.minSampleShading = 1.0f; | |
VkSampleMask samplemask = internal_state->samplemask; | |
samplemask = pso->desc.sample_mask; | |
multisampling.pSampleMask = &samplemask; | |
if (pso->desc.bs != nullptr) | |
{ | |
multisampling.alphaToCoverageEnable = pso->desc.bs->alpha_to_coverage_enable ? VK_TRUE : VK_FALSE; | |
} | |
else | |
{ | |
multisampling.alphaToCoverageEnable = VK_FALSE; | |
} | |
multisampling.alphaToOneEnable = VK_FALSE; | |
pipelineInfo.pMultisampleState = &multisampling; | |
// Blending: | |
uint32_t numBlendAttachments = 0; | |
VkPipelineColorBlendAttachmentState colorBlendAttachments[8] = {}; | |
for (size_t i = 0; i < commandlist.renderpass_info.rt_count; ++i) | |
{ | |
size_t attachmentIndex = 0; | |
if (pso->desc.bs->independent_blend_enable) | |
attachmentIndex = i; | |
const auto& desc = pso->desc.bs->render_target[attachmentIndex]; | |
VkPipelineColorBlendAttachmentState& attachment = colorBlendAttachments[numBlendAttachments]; | |
numBlendAttachments++; | |
attachment.blendEnable = desc.blend_enable ? VK_TRUE : VK_FALSE; | |
attachment.colorWriteMask = 0; | |
if (has_flag(desc.render_target_write_mask, ColorWrite::ENABLE_RED)) | |
{ | |
attachment.colorWriteMask |= VK_COLOR_COMPONENT_R_BIT; | |
} | |
if (has_flag(desc.render_target_write_mask, ColorWrite::ENABLE_GREEN)) | |
{ | |
attachment.colorWriteMask |= VK_COLOR_COMPONENT_G_BIT; | |
} | |
if (has_flag(desc.render_target_write_mask, ColorWrite::ENABLE_BLUE)) | |
{ | |
attachment.colorWriteMask |= VK_COLOR_COMPONENT_B_BIT; | |
} | |
if (has_flag(desc.render_target_write_mask, ColorWrite::ENABLE_ALPHA)) | |
{ | |
attachment.colorWriteMask |= VK_COLOR_COMPONENT_A_BIT; | |
} | |
attachment.srcColorBlendFactor = _ConvertBlend(desc.src_blend); | |
attachment.dstColorBlendFactor = _ConvertBlend(desc.dest_blend); | |
attachment.colorBlendOp = _ConvertBlendOp(desc.blend_op); | |
attachment.srcAlphaBlendFactor = _ConvertBlend(desc.src_blend_alpha); | |
attachment.dstAlphaBlendFactor = _ConvertBlend(desc.dest_blend_alpha); | |
attachment.alphaBlendOp = _ConvertBlendOp(desc.blend_op_alpha); | |
} | |
VkPipelineColorBlendStateCreateInfo colorBlending = {}; | |
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; | |
colorBlending.logicOpEnable = VK_FALSE; | |
colorBlending.logicOp = VK_LOGIC_OP_COPY; | |
colorBlending.attachmentCount = numBlendAttachments; | |
colorBlending.pAttachments = colorBlendAttachments; | |
colorBlending.blendConstants[0] = 1.0f; | |
colorBlending.blendConstants[1] = 1.0f; | |
colorBlending.blendConstants[2] = 1.0f; | |
colorBlending.blendConstants[3] = 1.0f; | |
pipelineInfo.pColorBlendState = &colorBlending; | |
// Input layout: | |
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; | |
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; | |
wi::vector<VkVertexInputBindingDescription> bindings; | |
wi::vector<VkVertexInputAttributeDescription> attributes; | |
if (pso->desc.il != nullptr) | |
{ | |
uint32_t lastBinding = 0xFFFFFFFF; | |
for (auto& x : pso->desc.il->elements) | |
{ | |
if (x.input_slot == lastBinding) | |
continue; | |
lastBinding = x.input_slot; | |
VkVertexInputBindingDescription& bind = bindings.emplace_back(); | |
bind.binding = x.input_slot; | |
bind.inputRate = x.input_slot_class == InputClassification::PER_VERTEX_DATA ? VK_VERTEX_INPUT_RATE_VERTEX : VK_VERTEX_INPUT_RATE_INSTANCE; | |
bind.stride = GetFormatStride(x.format); | |
} | |
uint32_t offset = 0; | |
uint32_t i = 0; | |
lastBinding = 0xFFFFFFFF; | |
for (auto& x : pso->desc.il->elements) | |
{ | |
VkVertexInputAttributeDescription attr = {}; | |
attr.binding = x.input_slot; | |
if (attr.binding != lastBinding) | |
{ | |
lastBinding = attr.binding; | |
offset = 0; | |
} | |
attr.format = _ConvertFormat(x.format); | |
attr.location = i; | |
attr.offset = x.aligned_byte_offset; | |
if (attr.offset == InputLayout::APPEND_ALIGNED_ELEMENT) | |
{ | |
// need to manually resolve this from the format spec. | |
attr.offset = offset; | |
offset += GetFormatStride(x.format); | |
} | |
attributes.push_back(attr); | |
i++; | |
} | |
vertexInputInfo.vertexBindingDescriptionCount = static_cast<uint32_t>(bindings.size()); | |
vertexInputInfo.pVertexBindingDescriptions = bindings.data(); | |
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributes.size()); | |
vertexInputInfo.pVertexAttributeDescriptions = attributes.data(); | |
} | |
pipelineInfo.pVertexInputState = &vertexInputInfo; | |
pipelineInfo.renderPass = VK_NULL_HANDLE; // instead we use VkPipelineRenderingCreateInfo | |
VkPipelineRenderingCreateInfo renderingInfo = {}; | |
renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO; | |
renderingInfo.viewMask = 0; | |
renderingInfo.colorAttachmentCount = commandlist.renderpass_info.rt_count; | |
VkFormat formats[8] = {}; | |
for (uint32_t i = 0; i < commandlist.renderpass_info.rt_count; ++i) | |
{ | |
formats[i] = _ConvertFormat(commandlist.renderpass_info.rt_formats[i]); | |
} | |
renderingInfo.pColorAttachmentFormats = formats; | |
renderingInfo.depthAttachmentFormat = _ConvertFormat(commandlist.renderpass_info.ds_format); | |
if (IsFormatStencilSupport(commandlist.renderpass_info.ds_format)) | |
{ | |
renderingInfo.stencilAttachmentFormat = renderingInfo.depthAttachmentFormat; | |
} | |
pipelineInfo.pNext = &renderingInfo; | |
VkResult res = vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineInfo, nullptr, &pipeline); | |
assert(res == VK_SUCCESS); | |
commandlist.pipelines_worker.push_back(std::make_pair(pipeline_hash, pipeline)); | |
} | |
} | |
else | |
{ | |
pipeline = it->second; | |
} | |
assert(pipeline != VK_NULL_HANDLE); | |
vkCmdBindPipeline(commandlist.GetCommandBuffer(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); | |
commandlist.dirty_pso = false; | |
} | |
void GraphicsDevice_Vulkan::predraw(CommandList cmd) | |
{ | |
pso_validate(cmd); | |
CommandList_Vulkan& commandlist = GetCommandList(cmd); | |
commandlist.binder.flush(true, cmd); | |
} | |
void GraphicsDevice_Vulkan::predispatch(CommandList cmd) | |
{ | |
CommandList_Vulkan& commandlist = GetCommandList(cmd); | |
commandlist.binder.flush(false, cmd); | |
} | |
// Engine functions | |
GraphicsDevice_Vulkan::GraphicsDevice_Vulkan(wi::platform::window_type window, ValidationMode validationMode_) | |
{ | |
wi::Timer timer; | |
TOPLEVEL_ACCELERATION_STRUCTURE_INSTANCE_SIZE = sizeof(VkAccelerationStructureInstanceKHR); | |
validationMode = validationMode_; | |
VkResult res; | |
res = volkInitialize(); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("volkInitialize failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
// Fill out application info: | |
VkApplicationInfo appInfo = {}; | |
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; | |
appInfo.pApplicationName = "Wicked Engine Application"; | |
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); | |
appInfo.pEngineName = "Wicked Engine"; | |
appInfo.engineVersion = VK_MAKE_VERSION(wi::version::GetMajor(), wi::version::GetMinor(), wi::version::GetRevision()); | |
appInfo.apiVersion = VK_API_VERSION_1_3; | |
// Enumerate available layers and extensions: | |
uint32_t instanceLayerCount; | |
res = vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr); | |
assert(res == VK_SUCCESS); | |
wi::vector<VkLayerProperties> availableInstanceLayers(instanceLayerCount); | |
res = vkEnumerateInstanceLayerProperties(&instanceLayerCount, availableInstanceLayers.data()); | |
assert(res == VK_SUCCESS); | |
uint32_t extensionCount = 0; | |
res = vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); | |
assert(res == VK_SUCCESS); | |
wi::vector<VkExtensionProperties> availableInstanceExtensions(extensionCount); | |
res = vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, availableInstanceExtensions.data()); | |
assert(res == VK_SUCCESS); | |
wi::vector<const char*> instanceLayers; | |
wi::vector<const char*> instanceExtensions; | |
for (auto& availableExtension : availableInstanceExtensions) | |
{ | |
if (strcmp(availableExtension.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) | |
{ | |
debugUtils = true; | |
instanceExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); | |
} | |
else if (strcmp(availableExtension.extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) == 0) | |
{ | |
instanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); | |
} | |
else if (strcmp(availableExtension.extensionName, VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME) == 0) | |
{ | |
instanceExtensions.push_back(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME); | |
} | |
} | |
instanceExtensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); | |
#if defined(VK_USE_PLATFORM_WIN32_KHR) | |
instanceExtensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); | |
#elif SDL2 | |
{ | |
uint32_t extensionCount; | |
SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr); | |
wi::vector<const char *> extensionNames_sdl(extensionCount); | |
SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensionNames_sdl.data()); | |
instanceExtensions.reserve(instanceExtensions.size() + extensionNames_sdl.size()); | |
for (auto& x : extensionNames_sdl) | |
{ | |
instanceExtensions.push_back(x); | |
} | |
} | |
#endif // _WIN32 | |
if (validationMode != ValidationMode::Disabled) | |
{ | |
// Determine the optimal validation layers to enable that are necessary for useful debugging | |
static const wi::vector<const char*> validationLayerPriorityList[] = | |
{ | |
// The preferred validation layer is "VK_LAYER_KHRONOS_validation" | |
{"VK_LAYER_KHRONOS_validation"}, | |
// Otherwise we fallback to using the LunarG meta layer | |
{"VK_LAYER_LUNARG_standard_validation"}, | |
// Otherwise we attempt to enable the individual layers that compose the LunarG meta layer since it doesn't exist | |
{ | |
"VK_LAYER_GOOGLE_threading", | |
"VK_LAYER_LUNARG_parameter_validation", | |
"VK_LAYER_LUNARG_object_tracker", | |
"VK_LAYER_LUNARG_core_validation", | |
"VK_LAYER_GOOGLE_unique_objects", | |
}, | |
// Otherwise as a last resort we fallback to attempting to enable the LunarG core layer | |
{"VK_LAYER_LUNARG_core_validation"} | |
}; | |
for (auto& validationLayers : validationLayerPriorityList) | |
{ | |
if (ValidateLayers(validationLayers, availableInstanceLayers)) | |
{ | |
for (auto& x : validationLayers) | |
{ | |
instanceLayers.push_back(x); | |
} | |
break; | |
} | |
} | |
} | |
// Create instance: | |
{ | |
VkInstanceCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; | |
createInfo.pApplicationInfo = &appInfo; | |
createInfo.enabledLayerCount = static_cast<uint32_t>(instanceLayers.size()); | |
createInfo.ppEnabledLayerNames = instanceLayers.data(); | |
createInfo.enabledExtensionCount = static_cast<uint32_t>(instanceExtensions.size()); | |
createInfo.ppEnabledExtensionNames = instanceExtensions.data(); | |
VkDebugUtilsMessengerCreateInfoEXT debugUtilsCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT }; | |
if (validationMode != ValidationMode::Disabled && debugUtils) | |
{ | |
debugUtilsCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; | |
debugUtilsCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; | |
if (validationMode == ValidationMode::Verbose) | |
{ | |
debugUtilsCreateInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; | |
debugUtilsCreateInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; | |
} | |
debugUtilsCreateInfo.pfnUserCallback = debugUtilsMessengerCallback; | |
createInfo.pNext = &debugUtilsCreateInfo; | |
} | |
res = vkCreateInstance(&createInfo, nullptr, &instance); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("vkCreateInstance failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
volkLoadInstanceOnly(instance); | |
if (validationMode != ValidationMode::Disabled && debugUtils) | |
{ | |
res = vkCreateDebugUtilsMessengerEXT(instance, &debugUtilsCreateInfo, nullptr, &debugUtilsMessenger); | |
assert(res == VK_SUCCESS); | |
} | |
} | |
// Enumerating and creating devices: | |
{ | |
uint32_t deviceCount = 0; | |
VkResult res = vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); | |
assert(res == VK_SUCCESS); | |
if (deviceCount == 0) | |
{ | |
assert(0); | |
wi::helper::messageBox("Failed to find GPU with Vulkan support!"); | |
wi::platform::Exit(); | |
} | |
wi::vector<VkPhysicalDevice> devices(deviceCount); | |
res = vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data()); | |
assert(res == VK_SUCCESS); | |
const wi::vector<const char*> required_deviceExtensions = { | |
VK_KHR_SWAPCHAIN_EXTENSION_NAME, | |
}; | |
wi::vector<const char*> enabled_deviceExtensions; | |
for (const auto& dev : devices) | |
{ | |
bool suitable = true; | |
uint32_t extensionCount; | |
VkResult res = vkEnumerateDeviceExtensionProperties(dev, nullptr, &extensionCount, nullptr); | |
assert(res == VK_SUCCESS); | |
wi::vector<VkExtensionProperties> available_deviceExtensions(extensionCount); | |
res = vkEnumerateDeviceExtensionProperties(dev, nullptr, &extensionCount, available_deviceExtensions.data()); | |
assert(res == VK_SUCCESS); | |
for (auto& x : required_deviceExtensions) | |
{ | |
if (!checkExtensionSupport(x, available_deviceExtensions)) | |
{ | |
suitable = false; | |
} | |
} | |
if (!suitable) | |
continue; | |
features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; | |
features_1_1.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; | |
features_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; | |
features_1_3.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; | |
features2.pNext = &features_1_1; | |
features_1_1.pNext = &features_1_2; | |
features_1_2.pNext = &features_1_3; | |
void** features_chain = &features_1_3.pNext; | |
acceleration_structure_features = {}; | |
raytracing_features = {}; | |
raytracing_query_features = {}; | |
fragment_shading_rate_features = {}; | |
mesh_shader_features = {}; | |
conditional_rendering_features = {}; | |
depth_clip_enable_features = {}; | |
properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; | |
properties_1_1.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES; | |
properties_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES; | |
properties_1_3.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_PROPERTIES; | |
properties2.pNext = &properties_1_1; | |
properties_1_1.pNext = &properties_1_2; | |
properties_1_2.pNext = &properties_1_3; | |
void** properties_chain = &properties_1_3.pNext; | |
sampler_minmax_properties = {}; | |
acceleration_structure_properties = {}; | |
raytracing_properties = {}; | |
fragment_shading_rate_properties = {}; | |
mesh_shader_properties = {}; | |
sampler_minmax_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_FILTER_MINMAX_PROPERTIES; | |
*properties_chain = &sampler_minmax_properties; | |
properties_chain = &sampler_minmax_properties.pNext; | |
depth_stencil_resolve_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES; | |
*properties_chain = &depth_stencil_resolve_properties; | |
properties_chain = &depth_stencil_resolve_properties.pNext; | |
enabled_deviceExtensions = required_deviceExtensions; | |
if (checkExtensionSupport(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, available_deviceExtensions)) | |
{ | |
enabled_deviceExtensions.push_back(VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME); | |
depth_clip_enable_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT; | |
*features_chain = &depth_clip_enable_features; | |
features_chain = &depth_clip_enable_features.pNext; | |
} | |
if (checkExtensionSupport(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, available_deviceExtensions)) | |
{ | |
enabled_deviceExtensions.push_back(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME); | |
assert(checkExtensionSupport(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME, available_deviceExtensions)); | |
enabled_deviceExtensions.push_back(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); | |
acceleration_structure_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; | |
*features_chain = &acceleration_structure_features; | |
features_chain = &acceleration_structure_features.pNext; | |
acceleration_structure_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR; | |
*properties_chain = &acceleration_structure_properties; | |
properties_chain = &acceleration_structure_properties.pNext; | |
if (checkExtensionSupport(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, available_deviceExtensions)) | |
{ | |
enabled_deviceExtensions.push_back(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME); | |
enabled_deviceExtensions.push_back(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME); | |
raytracing_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR; | |
*features_chain = &raytracing_features; | |
features_chain = &raytracing_features.pNext; | |
raytracing_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR; | |
*properties_chain = &raytracing_properties; | |
properties_chain = &raytracing_properties.pNext; | |
} | |
if (checkExtensionSupport(VK_KHR_RAY_QUERY_EXTENSION_NAME, available_deviceExtensions)) | |
{ | |
enabled_deviceExtensions.push_back(VK_KHR_RAY_QUERY_EXTENSION_NAME); | |
raytracing_query_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR; | |
*features_chain = &raytracing_query_features; | |
features_chain = &raytracing_query_features.pNext; | |
} | |
} | |
if (checkExtensionSupport(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, available_deviceExtensions)) | |
{ | |
enabled_deviceExtensions.push_back(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME); | |
fragment_shading_rate_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR; | |
*features_chain = &fragment_shading_rate_features; | |
features_chain = &fragment_shading_rate_features.pNext; | |
fragment_shading_rate_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR; | |
*properties_chain = &fragment_shading_rate_properties; | |
properties_chain = &fragment_shading_rate_properties.pNext; | |
} | |
if (checkExtensionSupport(VK_EXT_MESH_SHADER_EXTENSION_NAME, available_deviceExtensions)) | |
{ | |
enabled_deviceExtensions.push_back(VK_EXT_MESH_SHADER_EXTENSION_NAME); | |
mesh_shader_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT; | |
*features_chain = &mesh_shader_features; | |
features_chain = &mesh_shader_features.pNext; | |
mesh_shader_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_PROPERTIES_EXT; | |
*properties_chain = &mesh_shader_properties; | |
properties_chain = &mesh_shader_properties.pNext; | |
} | |
if (checkExtensionSupport(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME, available_deviceExtensions)) | |
{ | |
enabled_deviceExtensions.push_back(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME); | |
conditional_rendering_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONDITIONAL_RENDERING_FEATURES_EXT; | |
*features_chain = &conditional_rendering_features; | |
features_chain = &conditional_rendering_features.pNext; | |
} | |
vkGetPhysicalDeviceProperties2(dev, &properties2); | |
bool discrete = properties2.properties.deviceType == VkPhysicalDeviceType::VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; | |
if (discrete || physicalDevice == VK_NULL_HANDLE) | |
{ | |
physicalDevice = dev; | |
if (discrete) | |
{ | |
break; // if this is discrete GPU, look no further (prioritize discrete GPU) | |
} | |
} | |
} | |
if (physicalDevice == VK_NULL_HANDLE) | |
{ | |
assert(0); | |
wi::helper::messageBox("Failed to find a suitable GPU!"); | |
wi::platform::Exit(); | |
} | |
assert(properties2.properties.limits.timestampComputeAndGraphics == VK_TRUE); | |
vkGetPhysicalDeviceFeatures2(physicalDevice, &features2); | |
assert(features2.features.imageCubeArray == VK_TRUE); | |
assert(features2.features.independentBlend == VK_TRUE); | |
assert(features2.features.geometryShader == VK_TRUE); | |
assert(features2.features.samplerAnisotropy == VK_TRUE); | |
assert(features2.features.shaderClipDistance == VK_TRUE); | |
assert(features2.features.textureCompressionBC == VK_TRUE); | |
assert(features2.features.occlusionQueryPrecise == VK_TRUE); | |
assert(features_1_2.descriptorIndexing == VK_TRUE); | |
assert(features_1_3.dynamicRendering == VK_TRUE); | |
// Init adapter properties | |
vendorId = properties2.properties.vendorID; | |
deviceId = properties2.properties.deviceID; | |
adapterName = properties2.properties.deviceName; | |
driverDescription = properties_1_2.driverName; | |
if (properties_1_2.driverInfo[0] != '\0') | |
{ | |
driverDescription += std::string(": ") + properties_1_2.driverInfo; | |
} | |
switch (properties2.properties.deviceType) | |
{ | |
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: | |
adapterType = AdapterType::IntegratedGpu; | |
break; | |
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: | |
adapterType = AdapterType::DiscreteGpu; | |
break; | |
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: | |
adapterType = AdapterType::VirtualGpu; | |
break; | |
case VK_PHYSICAL_DEVICE_TYPE_CPU: | |
adapterType = AdapterType::Cpu; | |
break; | |
default: | |
adapterType = AdapterType::Other; | |
break; | |
} | |
if (features2.features.tessellationShader == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::TESSELLATION; | |
} | |
if (features2.features.shaderStorageImageExtendedFormats == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::UAV_LOAD_FORMAT_COMMON; | |
} | |
if (features_1_2.shaderOutputLayer == VK_TRUE && features_1_2.shaderOutputViewportIndex) | |
{ | |
capabilities |= GraphicsDeviceCapability::RENDERTARGET_AND_VIEWPORT_ARRAYINDEX_WITHOUT_GS; | |
} | |
if ( | |
raytracing_features.rayTracingPipeline == VK_TRUE && | |
raytracing_query_features.rayQuery == VK_TRUE && | |
acceleration_structure_features.accelerationStructure == VK_TRUE && | |
features_1_2.bufferDeviceAddress == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::RAYTRACING; | |
SHADER_IDENTIFIER_SIZE = raytracing_properties.shaderGroupHandleSize; | |
} | |
if (mesh_shader_features.meshShader == VK_TRUE && mesh_shader_features.taskShader == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::MESH_SHADER; | |
} | |
if (fragment_shading_rate_features.pipelineFragmentShadingRate == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::VARIABLE_RATE_SHADING; | |
} | |
if (fragment_shading_rate_features.attachmentFragmentShadingRate == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::VARIABLE_RATE_SHADING_TIER2; | |
VARIABLE_RATE_SHADING_TILE_SIZE = std::min(fragment_shading_rate_properties.maxFragmentShadingRateAttachmentTexelSize.width, fragment_shading_rate_properties.maxFragmentShadingRateAttachmentTexelSize.height); | |
} | |
VkFormatProperties formatProperties = {}; | |
vkGetPhysicalDeviceFormatProperties(physicalDevice, _ConvertFormat(Format::R11G11B10_FLOAT), &formatProperties); | |
if (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) | |
{ | |
capabilities |= GraphicsDeviceCapability::UAV_LOAD_FORMAT_R11G11B10_FLOAT; | |
} | |
if (conditional_rendering_features.conditionalRendering == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::PREDICATION; | |
} | |
if (features_1_2.samplerFilterMinmax == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::SAMPLER_MINMAX; | |
} | |
if (features2.features.depthBounds == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::DEPTH_BOUNDS_TEST; | |
} | |
if (features2.features.sparseBinding == VK_TRUE && features2.features.sparseResidencyAliased == VK_TRUE) | |
{ | |
if (properties2.properties.sparseProperties.residencyNonResidentStrict == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::SPARSE_NULL_MAPPING; | |
} | |
if (features2.features.sparseResidencyBuffer == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::SPARSE_BUFFER; | |
} | |
if (features2.features.sparseResidencyImage2D == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::SPARSE_TEXTURE2D; | |
} | |
if (features2.features.sparseResidencyImage3D == VK_TRUE) | |
{ | |
capabilities |= GraphicsDeviceCapability::SPARSE_TEXTURE3D; | |
} | |
capabilities |= GraphicsDeviceCapability::GENERIC_SPARSE_TILE_POOL; | |
} | |
if ( | |
(depth_stencil_resolve_properties.supportedDepthResolveModes & VK_RESOLVE_MODE_MIN_BIT) && | |
(depth_stencil_resolve_properties.supportedDepthResolveModes & VK_RESOLVE_MODE_MAX_BIT) | |
) | |
{ | |
capabilities |= GraphicsDeviceCapability::DEPTH_RESOLVE_MIN_MAX; | |
} | |
if ( | |
(depth_stencil_resolve_properties.supportedStencilResolveModes & VK_RESOLVE_MODE_MIN_BIT) && | |
(depth_stencil_resolve_properties.supportedStencilResolveModes & VK_RESOLVE_MODE_MAX_BIT) | |
) | |
{ | |
capabilities |= GraphicsDeviceCapability::STENCIL_RESOLVE_MIN_MAX; | |
} | |
// Find queue families: | |
uint32_t queueFamilyCount = 0; | |
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr); | |
queueFamilies.resize(queueFamilyCount); | |
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data()); | |
// Query base queue families: | |
uint32_t familyIndex = 0; | |
for (const auto& queueFamily : queueFamilies) | |
{ | |
if (graphicsFamily == VK_QUEUE_FAMILY_IGNORED && queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) | |
{ | |
graphicsFamily = familyIndex; | |
if (queueFamily.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) | |
{ | |
queues[QUEUE_GRAPHICS].sparse_binding_supported = true; | |
} | |
} | |
if (copyFamily == VK_QUEUE_FAMILY_IGNORED && queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT) | |
{ | |
copyFamily = familyIndex; | |
} | |
if (computeFamily == VK_QUEUE_FAMILY_IGNORED && queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT) | |
{ | |
computeFamily = familyIndex; | |
} | |
familyIndex++; | |
} | |
// Now try to find dedicated compute and transfer queues: | |
familyIndex = 0; | |
for (const auto& queueFamily : queueFamilies) | |
{ | |
if (queueFamily.queueCount > 0 && | |
queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT && | |
!(queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) && | |
!(queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT) | |
) | |
{ | |
copyFamily = familyIndex; | |
if (queueFamily.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) | |
{ | |
queues[QUEUE_COPY].sparse_binding_supported = true; | |
} | |
} | |
if (queueFamily.queueCount > 0 && | |
queueFamily.queueFlags & VK_QUEUE_COMPUTE_BIT && | |
!(queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) | |
) | |
{ | |
computeFamily = familyIndex; | |
if (queueFamily.queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) | |
{ | |
queues[QUEUE_COMPUTE].sparse_binding_supported = true; | |
} | |
} | |
familyIndex++; | |
} | |
wi::vector<VkDeviceQueueCreateInfo> queueCreateInfos; | |
wi::unordered_set<uint32_t> uniqueQueueFamilies = { graphicsFamily, copyFamily, computeFamily }; | |
float queuePriority = 1.0f; | |
for (uint32_t queueFamily : uniqueQueueFamilies) | |
{ | |
VkDeviceQueueCreateInfo queueCreateInfo = {}; | |
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | |
queueCreateInfo.queueFamilyIndex = queueFamily; | |
queueCreateInfo.queueCount = 1; | |
queueCreateInfo.pQueuePriorities = &queuePriority; | |
queueCreateInfos.push_back(queueCreateInfo); | |
families.push_back(queueFamily); | |
} | |
VkDeviceCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; | |
createInfo.queueCreateInfoCount = (uint32_t)queueCreateInfos.size(); | |
createInfo.pQueueCreateInfos = queueCreateInfos.data(); | |
createInfo.pEnabledFeatures = nullptr; | |
createInfo.pNext = &features2; | |
createInfo.enabledExtensionCount = static_cast<uint32_t>(enabled_deviceExtensions.size()); | |
createInfo.ppEnabledExtensionNames = enabled_deviceExtensions.data(); | |
res = vkCreateDevice(physicalDevice, &createInfo, nullptr, &device); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("vkCreateDevice failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
volkLoadDevice(device); | |
} | |
// queues: | |
{ | |
vkGetDeviceQueue(device, graphicsFamily, 0, &graphicsQueue); | |
vkGetDeviceQueue(device, computeFamily, 0, &computeQueue); | |
vkGetDeviceQueue(device, copyFamily, 0, ©Queue); | |
queues[QUEUE_GRAPHICS].queue = graphicsQueue; | |
queues[QUEUE_COMPUTE].queue = computeQueue; | |
queues[QUEUE_COPY].queue = copyQueue; | |
VkSemaphoreTypeCreateInfo timelineCreateInfo = {}; | |
timelineCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; | |
timelineCreateInfo.pNext = nullptr; | |
timelineCreateInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; | |
timelineCreateInfo.initialValue = 0; | |
VkSemaphoreCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; | |
createInfo.pNext = &timelineCreateInfo; | |
createInfo.flags = 0; | |
res = vkCreateSemaphore(device, &createInfo, nullptr, &queues[QUEUE_GRAPHICS].semaphore); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("vkCreateSemaphore[QUEUE_GRAPHICS] failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
res = vkCreateSemaphore(device, &createInfo, nullptr, &queues[QUEUE_COMPUTE].semaphore); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("vkCreateSemaphore[QUEUE_COMPUTE] failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
res = vkCreateSemaphore(device, &createInfo, nullptr, &queues[QUEUE_COPY].semaphore); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("vkCreateSemaphore[QUEUE_COPY] failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
} | |
memory_properties_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2; | |
vkGetPhysicalDeviceMemoryProperties2(physicalDevice, &memory_properties_2); | |
if (memory_properties_2.memoryProperties.memoryHeapCount == 1 && | |
memory_properties_2.memoryProperties.memoryHeaps[0].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) | |
{ | |
// https://registry.khronos.org/vulkan/specs/1.0-extensions/html/vkspec.html#memory-device | |
// "In a unified memory architecture (UMA) system there is often only a single memory heap which is | |
// considered to be equally “local” to the host and to the device, and such an implementation must advertise the heap as device-local." | |
capabilities |= GraphicsDeviceCapability::CACHE_COHERENT_UMA; | |
} | |
allocationhandler = std::make_shared<AllocationHandler>(); | |
allocationhandler->device = device; | |
allocationhandler->instance = instance; | |
// Initialize Vulkan Memory Allocator helper: | |
VmaVulkanFunctions vma_vulkan_func{}; | |
vma_vulkan_func.vkGetInstanceProcAddr = vkGetInstanceProcAddr; | |
vma_vulkan_func.vkGetDeviceProcAddr = vkGetDeviceProcAddr; | |
vma_vulkan_func.vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties; | |
vma_vulkan_func.vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties; | |
vma_vulkan_func.vkAllocateMemory = vkAllocateMemory; | |
vma_vulkan_func.vkFreeMemory = vkFreeMemory; | |
vma_vulkan_func.vkMapMemory = vkMapMemory; | |
vma_vulkan_func.vkUnmapMemory = vkUnmapMemory; | |
vma_vulkan_func.vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges; | |
vma_vulkan_func.vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges; | |
vma_vulkan_func.vkBindBufferMemory = vkBindBufferMemory; | |
vma_vulkan_func.vkBindImageMemory = vkBindImageMemory; | |
vma_vulkan_func.vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements; | |
vma_vulkan_func.vkGetImageMemoryRequirements = vkGetImageMemoryRequirements; | |
vma_vulkan_func.vkCreateBuffer = vkCreateBuffer; | |
vma_vulkan_func.vkDestroyBuffer = vkDestroyBuffer; | |
vma_vulkan_func.vkCreateImage = vkCreateImage; | |
vma_vulkan_func.vkDestroyImage = vkDestroyImage; | |
vma_vulkan_func.vkCmdCopyBuffer = vkCmdCopyBuffer; | |
VmaAllocatorCreateInfo allocatorInfo = {}; | |
allocatorInfo.physicalDevice = physicalDevice; | |
allocatorInfo.device = device; | |
allocatorInfo.instance = instance; | |
// Core in 1.1 | |
allocatorInfo.flags = | |
VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT | | |
VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT; | |
vma_vulkan_func.vkGetBufferMemoryRequirements2KHR = vkGetBufferMemoryRequirements2; | |
vma_vulkan_func.vkGetImageMemoryRequirements2KHR = vkGetImageMemoryRequirements2; | |
if (features_1_2.bufferDeviceAddress) | |
{ | |
allocatorInfo.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; | |
vma_vulkan_func.vkBindBufferMemory2KHR = vkBindBufferMemory2; | |
vma_vulkan_func.vkBindImageMemory2KHR = vkBindImageMemory2; | |
} | |
allocatorInfo.pVulkanFunctions = &vma_vulkan_func; | |
res = vmaCreateAllocator(&allocatorInfo, &allocationhandler->allocator); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("vmaCreateAllocator failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
copyAllocator.init(this); | |
// Create frame resources: | |
for (uint32_t fr = 0; fr < BUFFERCOUNT; ++fr) | |
{ | |
for (int queue = 0; queue < QUEUE_COUNT; ++queue) | |
{ | |
VkFenceCreateInfo fenceInfo = {}; | |
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; | |
//fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; | |
VkResult res = vkCreateFence(device, &fenceInfo, nullptr, &frames[fr].fence[queue]); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("vkCreateFence[FRAME] failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
} | |
// Create resources for transition command buffer: | |
{ | |
VkCommandPoolCreateInfo poolInfo = {}; | |
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; | |
poolInfo.queueFamilyIndex = graphicsFamily; | |
poolInfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; | |
res = vkCreateCommandPool(device, &poolInfo, nullptr, &frames[fr].initCommandPool); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("vkCreateCommandPool[FRAME_INIT] failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
VkCommandBufferAllocateInfo commandBufferInfo = {}; | |
commandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; | |
commandBufferInfo.commandBufferCount = 1; | |
commandBufferInfo.commandPool = frames[fr].initCommandPool; | |
commandBufferInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; | |
res = vkAllocateCommandBuffers(device, &commandBufferInfo, &frames[fr].initCommandBuffer); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("vkAllocateCommandBuffers[FRAME_INIT] failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
VkCommandBufferBeginInfo beginInfo = {}; | |
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; | |
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; | |
beginInfo.pInheritanceInfo = nullptr; // Optional | |
res = vkBeginCommandBuffer(frames[fr].initCommandBuffer, &beginInfo); | |
assert(res == VK_SUCCESS); | |
if (res != VK_SUCCESS) | |
{ | |
wi::helper::messageBox("vkBeginCommandBuffer[FRAME_INIT] failed! ERROR: " + std::to_string(res), "Error!"); | |
wi::platform::Exit(); | |
} | |
} | |
} | |
// Create default null descriptors: | |
{ | |
VkBufferCreateInfo bufferInfo = {}; | |
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; | |
bufferInfo.size = 4; | |
bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; | |
bufferInfo.flags = 0; | |
VmaAllocationCreateInfo allocInfo = {}; | |
allocInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; | |
res = vmaCreateBuffer(allocationhandler->allocator, &bufferInfo, &allocInfo, &nullBuffer, &nullBufferAllocation, nullptr); | |
assert(res == VK_SUCCESS); | |
VkBufferViewCreateInfo viewInfo = {}; | |
viewInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO; | |
viewInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT; | |
viewInfo.range = VK_WHOLE_SIZE; | |
viewInfo.buffer = nullBuffer; | |
res = vkCreateBufferView(device, &viewInfo, nullptr, &nullBufferView); | |
assert(res == VK_SUCCESS); | |
} | |
{ | |
VkImageCreateInfo imageInfo = {}; | |
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; | |
imageInfo.extent.width = 1; | |
imageInfo.extent.height = 1; | |
imageInfo.extent.depth = 1; | |
imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM; | |
imageInfo.arrayLayers = 1; | |
imageInfo.mipLevels = 1; | |
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; | |
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; | |
imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; | |
imageInfo.flags = 0; | |
VmaAllocationCreateInfo allocInfo = {}; | |
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY; | |
imageInfo.imageType = VK_IMAGE_TYPE_1D; | |
res = vmaCreateImage(allocationhandler->allocator, &imageInfo, &allocInfo, &nullImage1D, &nullImageAllocation1D, nullptr); | |
assert(res == VK_SUCCESS); | |
imageInfo.imageType = VK_IMAGE_TYPE_2D; | |
imageInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; | |
imageInfo.arrayLayers = 6; | |
res = vmaCreateImage(allocationhandler->allocator, &imageInfo, &allocInfo, &nullImage2D, &nullImageAllocation2D, nullptr); | |
assert(res == VK_SUCCESS); | |
imageInfo.imageType = VK_IMAGE_TYPE_3D; | |
imageInfo.flags = 0; | |
imageInfo.arrayLayers = 1; | |
res = vmaCreateImage(allocationhandler->allocator, &imageInfo, &allocInfo, &nullImage3D, &nullImageAllocation3D, nullptr); | |
assert(res == VK_SUCCESS); | |
// Transitions: | |
initLocker.lock(); | |
{ | |
VkImageMemoryBarrier barrier = {}; | |
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; | |
barrier.oldLayout = imageInfo.initialLayout; | |
barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; | |
barrier.srcAccessMask = 0; | |
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; | |
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
barrier.subresourceRange.baseArrayLayer = 0; | |
barrier.subresourceRange.baseMipLevel = 0; | |
barrier.subresourceRange.levelCount = 1; | |
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | |
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | |
barrier.image = nullImage1D; | |
barrier.subresourceRange.layerCount = 1; | |
vkCmdPipelineBarrier( | |
GetFrameResources().initCommandBuffer, | |
VK_PIPELINE_STAGE_TRANSFER_BIT, | |
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, | |
0, | |
0, nullptr, | |
0, nullptr, | |
1, &barrier | |
); | |
barrier.image = nullImage2D; | |
barrier.subresourceRange.layerCount = 6; | |
vkCmdPipelineBarrier( | |
GetFrameResources().initCommandBuffer, | |
VK_PIPELINE_STAGE_TRANSFER_BIT, | |
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, | |
0, | |
0, nullptr, | |
0, nullptr, | |
1, &barrier | |
); | |
barrier.image = nullImage3D; | |
barrier.subresourceRange.layerCount = 1; | |
vkCmdPipelineBarrier( | |
GetFrameResources().initCommandBuffer, | |
VK_PIPELINE_STAGE_TRANSFER_BIT, | |
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, | |
0, | |
0, nullptr, | |
0, nullptr, | |
1, &barrier | |
); | |
} | |
submit_inits = true; | |
initLocker.unlock(); | |
VkImageViewCreateInfo viewInfo = {}; | |
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; | |
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
viewInfo.subresourceRange.baseArrayLayer = 0; | |
viewInfo.subresourceRange.layerCount = 1; | |
viewInfo.subresourceRange.baseMipLevel = 0; | |
viewInfo.subresourceRange.levelCount = 1; | |
viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; | |
viewInfo.image = nullImage1D; | |
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_1D; | |
res = vkCreateImageView(device, &viewInfo, nullptr, &nullImageView1D); | |
assert(res == VK_SUCCESS); | |
viewInfo.image = nullImage1D; | |
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_1D_ARRAY; | |
res = vkCreateImageView(device, &viewInfo, nullptr, &nullImageView1DArray); | |
assert(res == VK_SUCCESS); | |
viewInfo.image = nullImage2D; | |
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; | |
res = vkCreateImageView(device, &viewInfo, nullptr, &nullImageView2D); | |
assert(res == VK_SUCCESS); | |
viewInfo.image = nullImage2D; | |
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; | |
res = vkCreateImageView(device, &viewInfo, nullptr, &nullImageView2DArray); | |
assert(res == VK_SUCCESS); | |
viewInfo.image = nullImage2D; | |
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE; | |
viewInfo.subresourceRange.layerCount = 6; | |
res = vkCreateImageView(device, &viewInfo, nullptr, &nullImageViewCube); | |
assert(res == VK_SUCCESS); | |
viewInfo.image = nullImage2D; | |
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; | |
viewInfo.subresourceRange.layerCount = 6; | |
res = vkCreateImageView(device, &viewInfo, nullptr, &nullImageViewCubeArray); | |
assert(res == VK_SUCCESS); | |
viewInfo.image = nullImage3D; | |
viewInfo.subresourceRange.layerCount = 1; | |
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_3D; | |
res = vkCreateImageView(device, &viewInfo, nullptr, &nullImageView3D); | |
assert(res == VK_SUCCESS); | |
} | |
{ | |
VkSamplerCreateInfo createInfo = {}; | |
createInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; | |
res = vkCreateSampler(device, &createInfo, nullptr, &nullSampler); | |
assert(res == VK_SUCCESS); | |
} | |
TIMESTAMP_FREQUENCY = uint64_t(1.0 / double(properties2.properties.limits.timestampPeriod) * 1000 * 1000 * 1000); | |
// Dynamic PSO states: | |
pso_dynamicStates.push_back(VK_DYNAMIC_STATE_VIEWPORT); | |
pso_dynamicStates.push_back(VK_DYNAMIC_STATE_SCISSOR); | |
pso_dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_REFERENCE); | |
pso_dynamicStates.push_back(VK_DYNAMIC_STATE_BLEND_CONSTANTS); | |
if (CheckCapability(GraphicsDeviceCapability::DEPTH_BOUNDS_TEST)) | |
{ | |
pso_dynamicStates.push_back(VK_DYNAMIC_STATE_DEPTH_BOUNDS); | |
} | |
if (CheckCapability(GraphicsDeviceCapability::VARIABLE_RATE_SHADING)) | |
{ | |
pso_dynamicStates.push_back(VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR); | |
} | |
pso_dynamicStates.push_back(VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE); | |
dynamicStateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; | |
dynamicStateInfo.dynamicStateCount = (uint32_t)pso_dynamicStates.size(); | |
dynamicStateInfo.pDynamicStates = pso_dynamicStates.data(); | |
// Note: limiting descriptors by constant amount is needed, because the bindless sets are bound to multiple slots to match DX12 layout | |
// And binding to multiple slot adds up towards limits, so the limits will be quickly reached for some descriptor types | |
// But not all descriptor types have this problem, like storage buffers that are not bound for multiple slots usually | |
// Ideally, this shouldn't be the case, because Vulkan could have it's own layout in shaders | |
const uint32_t limit_bindless_descriptors = 100000u; | |
if (features_1_2.descriptorBindingSampledImageUpdateAfterBind == VK_TRUE) | |
{ | |
allocationhandler->bindlessSampledImages.init(device, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, std::min(limit_bindless_descriptors, properties_1_2.maxDescriptorSetUpdateAfterBindSampledImages / 4)); | |
} | |
if (features_1_2.descriptorBinding |