UnleashedRecomp/UnleashedRecomp/gpu/rhi/plume_vulkan.cpp
2025-08-03 18:56:42 +03:00

4474 lines
204 KiB
C++

//
// plume
//
// Copyright (c) 2024 renderbag and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file for details.
//
#define VMA_IMPLEMENTATION
#define VOLK_IMPLEMENTATION
#include "plume_vulkan.h"
#include <algorithm>
#include <cmath>
#include <climits>
#include <unordered_map>
#if DLSS_ENABLED
# include "render/plume_dlss.h"
#endif
#ifndef NDEBUG
# define VULKAN_VALIDATION_LAYER_ENABLED
//# define VULKAN_OBJECT_NAMES_ENABLED
#endif
// TODO:
// - Fix resource pools.
namespace plume {
// Backend constants.
// Required buffer alignment for acceleration structures.
static const uint64_t AccelerationStructureBufferAlignment = 256;
// Required buffer alignment for shader binding table.
static const uint64_t ShaderBindingTableAlignment = 256;
// Controls the maximum amount of native queues the backend will create per queue family.
// Command queues are created as virtual queues on top of the native queues provided by Vulkan,
// so they're not under the limit set by the the device or the backend.
static const uint32_t MaxQueuesPerFamilyCount = 4;
// Required extensions.
static const std::unordered_set<std::string> RequiredInstanceExtensions = {
VK_KHR_SURFACE_EXTENSION_NAME,
# if defined(_WIN64)
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
# elif defined(__ANDROID__)
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
# elif defined(__linux__)
VK_KHR_XLIB_SURFACE_EXTENSION_NAME,
# elif defined(__APPLE__)
VK_EXT_METAL_SURFACE_EXTENSION_NAME,
# endif
};
static const std::unordered_set<std::string> OptionalInstanceExtensions = {
# if defined(__APPLE__)
// Tells the system Vulkan loader to enumerate portability drivers, if supported.
VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
# endif
};
static const std::unordered_set<std::string> RequiredDeviceExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME,
VK_EXT_ROBUSTNESS_2_EXTENSION_NAME,
VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME,
VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME,
# ifdef VULKAN_OBJECT_NAMES_ENABLED
VK_EXT_DEBUG_UTILS_EXTENSION_NAME
# endif
};
static const std::unordered_set<std::string> OptionalDeviceExtensions = {
VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME,
VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME,
VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME,
VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME,
VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME,
VK_EXT_LOAD_STORE_OP_NONE_EXTENSION_NAME,
VK_KHR_PRESENT_ID_EXTENSION_NAME,
VK_KHR_PRESENT_WAIT_EXTENSION_NAME,
VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME,
// Vulkan spec requires this to be enabled if supported by the driver.
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
};
// Common functions.
static uint32_t roundUp(uint32_t value, uint32_t powerOf2Alignment) {
return (value + powerOf2Alignment - 1) & ~(powerOf2Alignment - 1);
}
static uint64_t roundUp(uint64_t value, uint64_t powerOf2Alignment) {
return (value + powerOf2Alignment - 1) & ~(powerOf2Alignment - 1);
}
VkFormat toVk(RenderFormat format) {
switch (format) {
case RenderFormat::UNKNOWN:
return VK_FORMAT_UNDEFINED;
case RenderFormat::R32G32B32A32_TYPELESS:
return VK_FORMAT_R32G32B32A32_SFLOAT;
case RenderFormat::R32G32B32A32_FLOAT:
return VK_FORMAT_R32G32B32A32_SFLOAT;
case RenderFormat::R32G32B32A32_UINT:
return VK_FORMAT_R32G32B32A32_UINT;
case RenderFormat::R32G32B32A32_SINT:
return VK_FORMAT_R32G32B32A32_SINT;
case RenderFormat::R32G32B32_TYPELESS:
return VK_FORMAT_R32G32B32_SFLOAT;
case RenderFormat::R32G32B32_FLOAT:
return VK_FORMAT_R32G32B32_SFLOAT;
case RenderFormat::R32G32B32_UINT:
return VK_FORMAT_R32G32B32_UINT;
case RenderFormat::R32G32B32_SINT:
return VK_FORMAT_R32G32B32_SINT;
case RenderFormat::R16G16B16A16_TYPELESS:
return VK_FORMAT_R16G16B16A16_SFLOAT;
case RenderFormat::R16G16B16A16_FLOAT:
return VK_FORMAT_R16G16B16A16_SFLOAT;
case RenderFormat::R16G16B16A16_UNORM:
return VK_FORMAT_R16G16B16A16_UNORM;
case RenderFormat::R16G16B16A16_UINT:
return VK_FORMAT_R16G16B16A16_UINT;
case RenderFormat::R16G16B16A16_SNORM:
return VK_FORMAT_R16G16B16A16_SNORM;
case RenderFormat::R16G16B16A16_SINT:
return VK_FORMAT_R16G16B16A16_SINT;
case RenderFormat::R32G32_TYPELESS:
return VK_FORMAT_R32G32_SFLOAT;
case RenderFormat::R32G32_FLOAT:
return VK_FORMAT_R32G32_SFLOAT;
case RenderFormat::R8G8B8A8_TYPELESS:
return VK_FORMAT_R8G8B8A8_UNORM;
case RenderFormat::R8G8B8A8_UNORM:
return VK_FORMAT_R8G8B8A8_UNORM;
case RenderFormat::R8G8B8A8_UINT:
return VK_FORMAT_R8G8B8A8_UINT;
case RenderFormat::R8G8B8A8_SNORM:
return VK_FORMAT_R8G8B8A8_SNORM;
case RenderFormat::R8G8B8A8_SINT:
return VK_FORMAT_R8G8B8A8_SINT;
case RenderFormat::B8G8R8A8_UNORM:
return VK_FORMAT_B8G8R8A8_UNORM;
case RenderFormat::R16G16_TYPELESS:
return VK_FORMAT_R16G16_SFLOAT;
case RenderFormat::R16G16_FLOAT:
return VK_FORMAT_R16G16_SFLOAT;
case RenderFormat::R16G16_UNORM:
return VK_FORMAT_R16G16_UNORM;
case RenderFormat::R16G16_UINT:
return VK_FORMAT_R16G16_UINT;
case RenderFormat::R16G16_SNORM:
return VK_FORMAT_R16G16_SNORM;
case RenderFormat::R16G16_SINT:
return VK_FORMAT_R16G16_SINT;
case RenderFormat::R32_TYPELESS:
return VK_FORMAT_R32_SFLOAT;
case RenderFormat::D32_FLOAT:
return VK_FORMAT_D32_SFLOAT;
case RenderFormat::R32_FLOAT:
return VK_FORMAT_R32_SFLOAT;
case RenderFormat::R32_UINT:
return VK_FORMAT_R32_UINT;
case RenderFormat::R32_SINT:
return VK_FORMAT_R32_SINT;
case RenderFormat::R8G8_TYPELESS:
return VK_FORMAT_R8G8_UNORM;
case RenderFormat::R8G8_UNORM:
return VK_FORMAT_R8G8_UNORM;
case RenderFormat::R8G8_UINT:
return VK_FORMAT_R8G8_UINT;
case RenderFormat::R8G8_SNORM:
return VK_FORMAT_R8G8_SNORM;
case RenderFormat::R8G8_SINT:
return VK_FORMAT_R8G8_SINT;
case RenderFormat::R16_TYPELESS:
return VK_FORMAT_R16_SFLOAT;
case RenderFormat::R16_FLOAT:
return VK_FORMAT_R16_SFLOAT;
case RenderFormat::D16_UNORM:
return VK_FORMAT_D16_UNORM;
case RenderFormat::R16_UNORM:
return VK_FORMAT_R16_UNORM;
case RenderFormat::R16_UINT:
return VK_FORMAT_R16_UINT;
case RenderFormat::R16_SNORM:
return VK_FORMAT_R16_SNORM;
case RenderFormat::R16_SINT:
return VK_FORMAT_R16_SINT;
case RenderFormat::R8_TYPELESS:
return VK_FORMAT_R8_UNORM;
case RenderFormat::R8_UNORM:
return VK_FORMAT_R8_UNORM;
case RenderFormat::R8_UINT:
return VK_FORMAT_R8_UINT;
case RenderFormat::R8_SNORM:
return VK_FORMAT_R8_SNORM;
case RenderFormat::R8_SINT:
return VK_FORMAT_R8_SINT;
case RenderFormat::BC1_TYPELESS:
return VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
case RenderFormat::BC1_UNORM:
return VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
case RenderFormat::BC1_UNORM_SRGB:
return VK_FORMAT_BC1_RGBA_SRGB_BLOCK;
case RenderFormat::BC2_TYPELESS:
return VK_FORMAT_BC2_UNORM_BLOCK;
case RenderFormat::BC2_UNORM:
return VK_FORMAT_BC2_UNORM_BLOCK;
case RenderFormat::BC2_UNORM_SRGB:
return VK_FORMAT_BC2_SRGB_BLOCK;
case RenderFormat::BC3_TYPELESS:
return VK_FORMAT_BC3_UNORM_BLOCK;
case RenderFormat::BC3_UNORM:
return VK_FORMAT_BC3_UNORM_BLOCK;
case RenderFormat::BC3_UNORM_SRGB:
return VK_FORMAT_BC3_SRGB_BLOCK;
case RenderFormat::BC4_TYPELESS:
return VK_FORMAT_BC4_UNORM_BLOCK;
case RenderFormat::BC4_UNORM:
return VK_FORMAT_BC4_UNORM_BLOCK;
case RenderFormat::BC4_SNORM:
return VK_FORMAT_BC4_SNORM_BLOCK;
case RenderFormat::BC5_TYPELESS:
return VK_FORMAT_BC5_UNORM_BLOCK;
case RenderFormat::BC5_UNORM:
return VK_FORMAT_BC5_UNORM_BLOCK;
case RenderFormat::BC5_SNORM:
return VK_FORMAT_BC5_SNORM_BLOCK;
case RenderFormat::BC6H_TYPELESS:
return VK_FORMAT_BC6H_UFLOAT_BLOCK;
case RenderFormat::BC6H_UF16:
return VK_FORMAT_BC6H_UFLOAT_BLOCK;
case RenderFormat::BC6H_SF16:
return VK_FORMAT_BC6H_SFLOAT_BLOCK;
case RenderFormat::BC7_TYPELESS:
return VK_FORMAT_BC7_UNORM_BLOCK;
case RenderFormat::BC7_UNORM:
return VK_FORMAT_BC7_UNORM_BLOCK;
case RenderFormat::BC7_UNORM_SRGB:
return VK_FORMAT_BC7_SRGB_BLOCK;
default:
assert(false && "Unknown format.");
return VK_FORMAT_UNDEFINED;
}
}
static VkImageType toImageType(RenderTextureDimension dimension) {
switch (dimension) {
case RenderTextureDimension::TEXTURE_1D:
return VK_IMAGE_TYPE_1D;
case RenderTextureDimension::TEXTURE_2D:
return VK_IMAGE_TYPE_2D;
case RenderTextureDimension::TEXTURE_3D:
return VK_IMAGE_TYPE_3D;
default:
assert(false && "Unknown resource dimension.");
return VK_IMAGE_TYPE_MAX_ENUM;
}
}
static VkImageViewType toImageViewType(RenderTextureDimension dimension) {
switch (dimension) {
case RenderTextureDimension::TEXTURE_1D:
return VK_IMAGE_VIEW_TYPE_1D;
case RenderTextureDimension::TEXTURE_2D:
return VK_IMAGE_VIEW_TYPE_2D;
case RenderTextureDimension::TEXTURE_3D:
return VK_IMAGE_VIEW_TYPE_3D;
default:
assert(false && "Unknown resource dimension.");
return VK_IMAGE_VIEW_TYPE_MAX_ENUM;
}
}
static VkImageViewType toImageViewType(RenderTextureViewDimension dimension) {
switch (dimension) {
case RenderTextureViewDimension::TEXTURE_1D:
return VK_IMAGE_VIEW_TYPE_1D;
case RenderTextureViewDimension::TEXTURE_2D:
return VK_IMAGE_VIEW_TYPE_2D;
case RenderTextureViewDimension::TEXTURE_3D:
return VK_IMAGE_VIEW_TYPE_3D;
case RenderTextureViewDimension::TEXTURE_CUBE:
return VK_IMAGE_VIEW_TYPE_CUBE;
default:
assert(false && "Unknown resource dimension.");
return VK_IMAGE_VIEW_TYPE_MAX_ENUM;
}
}
static VkImageTiling toVk(RenderTextureArrangement arrangement) {
switch (arrangement) {
case RenderTextureArrangement::UNKNOWN:
return VkImageTiling::VK_IMAGE_TILING_OPTIMAL;
case RenderTextureArrangement::ROW_MAJOR:
return VkImageTiling::VK_IMAGE_TILING_LINEAR;
default:
assert(false && "Unknown texture arrangement.");
return VkImageTiling::VK_IMAGE_TILING_MAX_ENUM;
}
}
static VkVertexInputRate toVk(RenderInputSlotClassification classification) {
switch (classification) {
case RenderInputSlotClassification::PER_VERTEX_DATA:
return VK_VERTEX_INPUT_RATE_VERTEX;
case RenderInputSlotClassification::PER_INSTANCE_DATA:
return VK_VERTEX_INPUT_RATE_INSTANCE;
default:
assert(false && "Unknown input slot classification.");
return VK_VERTEX_INPUT_RATE_MAX_ENUM;
}
}
static VkCullModeFlags toVk(RenderCullMode cullMode) {
switch (cullMode) {
case RenderCullMode::NONE:
return VK_CULL_MODE_NONE;
case RenderCullMode::FRONT:
return VK_CULL_MODE_FRONT_BIT;
case RenderCullMode::BACK:
return VK_CULL_MODE_BACK_BIT;
default:
assert(false && "Unknown cull mode.");
return VK_CULL_MODE_FLAG_BITS_MAX_ENUM;
}
}
static VkPrimitiveTopology toVk(RenderPrimitiveTopology topology) {
switch (topology) {
case RenderPrimitiveTopology::POINT_LIST:
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
case RenderPrimitiveTopology::LINE_LIST:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
case RenderPrimitiveTopology::LINE_STRIP:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
case RenderPrimitiveTopology::TRIANGLE_LIST:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
case RenderPrimitiveTopology::TRIANGLE_STRIP:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
case RenderPrimitiveTopology::TRIANGLE_FAN:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
default:
assert(false && "Unknown primitive topology type.");
return VK_PRIMITIVE_TOPOLOGY_MAX_ENUM;
}
}
static VkBlendFactor toVk(RenderBlend blend) {
switch (blend) {
case RenderBlend::ZERO:
return VK_BLEND_FACTOR_ZERO;
case RenderBlend::ONE:
return VK_BLEND_FACTOR_ONE;
case RenderBlend::SRC_COLOR:
return VK_BLEND_FACTOR_SRC_COLOR;
case RenderBlend::INV_SRC_COLOR:
return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR;
case RenderBlend::SRC_ALPHA:
return VK_BLEND_FACTOR_SRC_ALPHA;
case RenderBlend::INV_SRC_ALPHA:
return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
case RenderBlend::DEST_ALPHA:
return VK_BLEND_FACTOR_DST_ALPHA;
case RenderBlend::INV_DEST_ALPHA:
return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA;
case RenderBlend::DEST_COLOR:
return VK_BLEND_FACTOR_DST_COLOR;
case RenderBlend::INV_DEST_COLOR:
return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR;
case RenderBlend::SRC_ALPHA_SAT:
return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE;
case RenderBlend::BLEND_FACTOR:
return VK_BLEND_FACTOR_CONSTANT_COLOR;
case RenderBlend::INV_BLEND_FACTOR:
return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR;
case RenderBlend::SRC1_COLOR:
return VK_BLEND_FACTOR_SRC1_COLOR;
case RenderBlend::INV_SRC1_COLOR:
return VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR;
case RenderBlend::SRC1_ALPHA:
return VK_BLEND_FACTOR_SRC1_ALPHA;
case RenderBlend::INV_SRC1_ALPHA:
return VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA;
default:
assert(false && "Unknown blend factor.");
return VK_BLEND_FACTOR_MAX_ENUM;
}
}
static VkBlendOp toVk(RenderBlendOperation operation) {
switch (operation) {
case RenderBlendOperation::ADD:
return VK_BLEND_OP_ADD;
case RenderBlendOperation::SUBTRACT:
return VK_BLEND_OP_SUBTRACT;
case RenderBlendOperation::REV_SUBTRACT:
return VK_BLEND_OP_REVERSE_SUBTRACT;
case RenderBlendOperation::MIN:
return VK_BLEND_OP_MIN;
case RenderBlendOperation::MAX:
return VK_BLEND_OP_MAX;
default:
assert(false && "Unknown blend operation.");
return VK_BLEND_OP_MAX_ENUM;
}
}
static VkLogicOp toVk(RenderLogicOperation operation) {
switch (operation) {
case RenderLogicOperation::CLEAR:
return VK_LOGIC_OP_CLEAR;
case RenderLogicOperation::SET:
return VK_LOGIC_OP_SET;
case RenderLogicOperation::COPY:
return VK_LOGIC_OP_COPY;
case RenderLogicOperation::COPY_INVERTED:
return VK_LOGIC_OP_COPY_INVERTED;
case RenderLogicOperation::NOOP:
return VK_LOGIC_OP_NO_OP;
case RenderLogicOperation::INVERT:
return VK_LOGIC_OP_INVERT;
case RenderLogicOperation::AND:
return VK_LOGIC_OP_AND;
case RenderLogicOperation::NAND:
return VK_LOGIC_OP_NAND;
case RenderLogicOperation::OR:
return VK_LOGIC_OP_OR;
case RenderLogicOperation::NOR:
return VK_LOGIC_OP_NOR;
case RenderLogicOperation::XOR:
return VK_LOGIC_OP_XOR;
case RenderLogicOperation::EQUIV:
return VK_LOGIC_OP_EQUIVALENT;
case RenderLogicOperation::AND_REVERSE:
return VK_LOGIC_OP_AND_REVERSE;
case RenderLogicOperation::AND_INVERTED:
return VK_LOGIC_OP_AND_INVERTED;
case RenderLogicOperation::OR_REVERSE:
return VK_LOGIC_OP_OR_REVERSE;
case RenderLogicOperation::OR_INVERTED:
return VK_LOGIC_OP_OR_INVERTED;
default:
assert(false && "Unknown logic operation.");
return VK_LOGIC_OP_MAX_ENUM;
}
}
static VkCompareOp toVk(RenderComparisonFunction function) {
switch (function) {
case RenderComparisonFunction::NEVER:
return VK_COMPARE_OP_NEVER;
case RenderComparisonFunction::LESS:
return VK_COMPARE_OP_LESS;
case RenderComparisonFunction::EQUAL:
return VK_COMPARE_OP_EQUAL;
case RenderComparisonFunction::LESS_EQUAL:
return VK_COMPARE_OP_LESS_OR_EQUAL;
case RenderComparisonFunction::GREATER:
return VK_COMPARE_OP_GREATER;
case RenderComparisonFunction::NOT_EQUAL:
return VK_COMPARE_OP_NOT_EQUAL;
case RenderComparisonFunction::GREATER_EQUAL:
return VK_COMPARE_OP_GREATER_OR_EQUAL;
case RenderComparisonFunction::ALWAYS:
return VK_COMPARE_OP_ALWAYS;
default:
assert(false && "Unknown comparison function.");
return VK_COMPARE_OP_MAX_ENUM;
}
}
static VkDescriptorType toVk(RenderDescriptorRangeType type) {
switch (type) {
case RenderDescriptorRangeType::CONSTANT_BUFFER:
return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
case RenderDescriptorRangeType::FORMATTED_BUFFER:
return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
case RenderDescriptorRangeType::READ_WRITE_FORMATTED_BUFFER:
return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
case RenderDescriptorRangeType::TEXTURE:
return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
case RenderDescriptorRangeType::READ_WRITE_TEXTURE:
return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
case RenderDescriptorRangeType::SAMPLER:
return VK_DESCRIPTOR_TYPE_SAMPLER;
case RenderDescriptorRangeType::STRUCTURED_BUFFER:
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
case RenderDescriptorRangeType::READ_WRITE_STRUCTURED_BUFFER:
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
case RenderDescriptorRangeType::BYTE_ADDRESS_BUFFER:
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
case RenderDescriptorRangeType::READ_WRITE_BYTE_ADDRESS_BUFFER:
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
case RenderDescriptorRangeType::ACCELERATION_STRUCTURE:
return VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
default:
assert(false && "Unknown descriptor range type.");
return VK_DESCRIPTOR_TYPE_MAX_ENUM;
}
}
static VkFilter toVk(RenderFilter filter) {
switch (filter) {
case RenderFilter::NEAREST:
return VK_FILTER_NEAREST;
case RenderFilter::LINEAR:
return VK_FILTER_LINEAR;
default:
assert(false && "Unknown filter.");
return VK_FILTER_MAX_ENUM;
}
}
static VkSamplerMipmapMode toVk(RenderMipmapMode mode) {
switch (mode) {
case RenderMipmapMode::NEAREST:
return VK_SAMPLER_MIPMAP_MODE_NEAREST;
case RenderMipmapMode::LINEAR:
return VK_SAMPLER_MIPMAP_MODE_LINEAR;
default:
assert(false && "Unknown mipmap mode.");
return VK_SAMPLER_MIPMAP_MODE_MAX_ENUM;
}
}
static VkSamplerAddressMode toVk(RenderTextureAddressMode mode) {
switch (mode) {
case RenderTextureAddressMode::WRAP:
return VK_SAMPLER_ADDRESS_MODE_REPEAT;
case RenderTextureAddressMode::MIRROR:
return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
case RenderTextureAddressMode::CLAMP:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
case RenderTextureAddressMode::BORDER:
return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
case RenderTextureAddressMode::MIRROR_ONCE:
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
default:
assert(false && "Unknown texture address mode.");
return VK_SAMPLER_ADDRESS_MODE_MAX_ENUM;
}
}
static VkBorderColor toVk(RenderBorderColor color) {
switch (color) {
case RenderBorderColor::TRANSPARENT_BLACK:
return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
case RenderBorderColor::OPAQUE_BLACK:
return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
case RenderBorderColor::OPAQUE_WHITE:
return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
default:
assert(false && "Unknown border color.");
return VK_BORDER_COLOR_MAX_ENUM;
}
}
static VkAccelerationStructureTypeKHR toVk(RenderAccelerationStructureType type) {
switch (type) {
case RenderAccelerationStructureType::TOP_LEVEL:
return VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
case RenderAccelerationStructureType::BOTTOM_LEVEL:
return VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
default:
assert(false && "Unknown acceleration structure type.");
return VK_ACCELERATION_STRUCTURE_TYPE_MAX_ENUM_KHR;
}
}
static VkPipelineStageFlags toStageFlags(RenderBarrierStages stages, bool geometrySupported, bool rtSupported) {
VkPipelineStageFlags flags = 0;
if (stages & RenderBarrierStage::GRAPHICS) {
flags |= VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT;
flags |= VK_PIPELINE_STAGE_VERTEX_INPUT_BIT;
flags |= VK_PIPELINE_STAGE_VERTEX_SHADER_BIT;
if (geometrySupported) {
flags |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
}
flags |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
flags |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
flags |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
flags |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
}
if (stages & RenderBarrierStage::COMPUTE) {
flags |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
if (rtSupported) {
flags |= VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR;
flags |= VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR;
}
}
if (stages & RenderBarrierStage::COPY) {
flags |= VK_PIPELINE_STAGE_TRANSFER_BIT;
flags |= VK_PIPELINE_STAGE_HOST_BIT;
}
return flags;
}
static VkShaderStageFlagBits toStage(RenderRaytracingPipelineLibrarySymbolType type) {
switch (type) {
case RenderRaytracingPipelineLibrarySymbolType::RAYGEN:
return VK_SHADER_STAGE_RAYGEN_BIT_KHR;
case RenderRaytracingPipelineLibrarySymbolType::MISS:
return VK_SHADER_STAGE_MISS_BIT_KHR;
case RenderRaytracingPipelineLibrarySymbolType::CLOSEST_HIT:
return VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR;
case RenderRaytracingPipelineLibrarySymbolType::ANY_HIT:
return VK_SHADER_STAGE_ANY_HIT_BIT_KHR;
case RenderRaytracingPipelineLibrarySymbolType::INTERSECTION:
return VK_SHADER_STAGE_INTERSECTION_BIT_KHR;
case RenderRaytracingPipelineLibrarySymbolType::CALLABLE:
return VK_SHADER_STAGE_CALLABLE_BIT_KHR;
default:
assert(false && "Unknown raytracing pipeline library symbol type.");
return VkShaderStageFlagBits(0);
}
}
static uint32_t toFamilyIndex(RenderCommandListType type) {
switch (type) {
case RenderCommandListType::DIRECT:
return 0;
case RenderCommandListType::COMPUTE:
return 1;
case RenderCommandListType::COPY:
return 2;
default:
assert(false && "Unknown command list type.");
return 0;
}
}
static VkIndexType toIndexType(RenderFormat format) {
switch (format) {
case RenderFormat::R8_UINT:
return VK_INDEX_TYPE_UINT8_EXT;
case RenderFormat::R16_UINT:
return VK_INDEX_TYPE_UINT16;
case RenderFormat::R32_UINT:
return VK_INDEX_TYPE_UINT32;
default:
assert(false && "Format is not supported as an index type.");
return VK_INDEX_TYPE_MAX_ENUM;
}
}
static VkBuildAccelerationStructureFlagsKHR toRTASBuildFlags(bool preferFastBuild, bool preferFastTrace) {
VkBuildAccelerationStructureFlagsKHR flags = 0;
flags |= preferFastBuild ? VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR : 0;
flags |= preferFastTrace ? VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR : 0;
return flags;
}
static VkImageLayout toImageLayout(RenderTextureLayout layout) {
switch (layout) {
case RenderTextureLayout::UNKNOWN:
return VK_IMAGE_LAYOUT_UNDEFINED;
case RenderTextureLayout::GENERAL:
return VK_IMAGE_LAYOUT_GENERAL;
case RenderTextureLayout::SHADER_READ:
return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
case RenderTextureLayout::COLOR_WRITE:
return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
case RenderTextureLayout::DEPTH_WRITE:
return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
case RenderTextureLayout::DEPTH_READ:
return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;
case RenderTextureLayout::COPY_SOURCE:
return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
case RenderTextureLayout::COPY_DEST:
return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
case RenderTextureLayout::RESOLVE_SOURCE:
return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
case RenderTextureLayout::RESOLVE_DEST:
return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
case RenderTextureLayout::PRESENT:
return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
default:
assert(false && "Unknown texture layout.");
return VK_IMAGE_LAYOUT_UNDEFINED;
}
}
static VkComponentSwizzle toVk(RenderSwizzle swizzle) {
switch (swizzle) {
case RenderSwizzle::IDENTITY:
return VK_COMPONENT_SWIZZLE_IDENTITY;
case RenderSwizzle::ZERO:
return VK_COMPONENT_SWIZZLE_ZERO;
case RenderSwizzle::ONE:
return VK_COMPONENT_SWIZZLE_ONE;
case RenderSwizzle::R:
return VK_COMPONENT_SWIZZLE_R;
case RenderSwizzle::G:
return VK_COMPONENT_SWIZZLE_G;
case RenderSwizzle::B:
return VK_COMPONENT_SWIZZLE_B;
case RenderSwizzle::A:
return VK_COMPONENT_SWIZZLE_A;
default:
assert(false && "Unknown swizzle type.");
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
}
static RenderDeviceType toDeviceType(VkPhysicalDeviceType type) {
switch (type) {
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
return RenderDeviceType::INTEGRATED;
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
return RenderDeviceType::DISCRETE;
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
return RenderDeviceType::VIRTUAL;
case VK_PHYSICAL_DEVICE_TYPE_CPU:
return RenderDeviceType::CPU;
default:
return RenderDeviceType::UNKNOWN;
}
}
static void setObjectName(VkDevice device, VkDebugReportObjectTypeEXT objectType, uint64_t object, const std::string &name) {
# ifdef VULKAN_OBJECT_NAMES_ENABLED
VkDebugMarkerObjectNameInfoEXT nameInfo = {};
nameInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
nameInfo.objectType = objectType;
nameInfo.object = object;
nameInfo.pObjectName = name.c_str();
VkResult res = vkDebugMarkerSetObjectNameEXT(device, &nameInfo);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkDebugMarkerSetObjectNameEXT failed with error code 0x%X.\n", res);
return;
}
# endif
}
static void fillSpecInfo(const RenderSpecConstant *specConstants, uint32_t specConstantsCount,
VkSpecializationInfo &specInfo, VkSpecializationMapEntry *specEntries, uint32_t *specData)
{
for (uint32_t i = 0; i < specConstantsCount; i++) {
VkSpecializationMapEntry &entry = specEntries[i];
entry.constantID = specConstants[i].index;
entry.offset = i * sizeof(uint32_t);
entry.size = sizeof(uint32_t);
specData[i] = specConstants[i].value;
}
specInfo.mapEntryCount = specConstantsCount;
specInfo.pMapEntries = specEntries;
specInfo.dataSize = specConstantsCount * sizeof(uint32_t);
specInfo.pData = specData;
}
// Underlying implementation for popcount
// https://stackoverflow.com/questions/109023/how-to-count-the-number-of-set-bits-in-a-32-bit-integer
static int numberOfSetBits(uint32_t i) {
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
// VulkanBuffer
VulkanBuffer::VulkanBuffer(VulkanDevice *device, VulkanPool *pool, const RenderBufferDesc &desc) {
assert(device != nullptr);
this->device = device;
this->pool = pool;
this->desc = desc;
const RenderBufferFlags storageFormattedMask = (RenderBufferFlag::STORAGE | RenderBufferFlag::FORMATTED);
VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = desc.size;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
bufferInfo.usage |= (desc.flags & RenderBufferFlag::VERTEX) ? VK_BUFFER_USAGE_VERTEX_BUFFER_BIT : 0;
bufferInfo.usage |= (desc.flags & RenderBufferFlag::INDEX) ? VK_BUFFER_USAGE_INDEX_BUFFER_BIT : 0;
bufferInfo.usage |= (desc.flags & RenderBufferFlag::STORAGE) ? VK_BUFFER_USAGE_STORAGE_BUFFER_BIT : 0;
bufferInfo.usage |= (desc.flags & RenderBufferFlag::CONSTANT) ? VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT : 0;
bufferInfo.usage |= (desc.flags & RenderBufferFlag::FORMATTED) ? VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT : 0;
bufferInfo.usage |= ((desc.flags & storageFormattedMask) == storageFormattedMask) ? VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT : 0;
bufferInfo.usage |= (desc.flags & RenderBufferFlag::ACCELERATION_STRUCTURE) ? VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR : 0;
bufferInfo.usage |= (desc.flags & RenderBufferFlag::ACCELERATION_STRUCTURE_SCRATCH) ? VK_BUFFER_USAGE_STORAGE_BUFFER_BIT : 0;
bufferInfo.usage |= (desc.flags & RenderBufferFlag::ACCELERATION_STRUCTURE_INPUT) ? VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR : 0;
bufferInfo.usage |= (desc.flags & RenderBufferFlag::SHADER_BINDING_TABLE) ? VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR : 0;
const uint32_t deviceAddressMask = RenderBufferFlag::CONSTANT | RenderBufferFlag::ACCELERATION_STRUCTURE | RenderBufferFlag::ACCELERATION_STRUCTURE_SCRATCH | RenderBufferFlag::ACCELERATION_STRUCTURE_INPUT | RenderBufferFlag::SHADER_BINDING_TABLE;
bufferInfo.usage |= (desc.flags & deviceAddressMask) ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT : 0;
VmaAllocationCreateInfo createInfo = {};
/* TODO: Debug pools.
createInfo.pool = (pool != nullptr) ? pool->vk : VK_NULL_HANDLE;
*/
createInfo.usage = VMA_MEMORY_USAGE_AUTO;
switch (desc.heapType) {
case RenderHeapType::DEFAULT:
bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
createInfo.preferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
break;
case RenderHeapType::UPLOAD:
bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
createInfo.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
break;
case RenderHeapType::READBACK:
bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
createInfo.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
break;
case RenderHeapType::GPU_UPLOAD:
bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
createInfo.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
createInfo.requiredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
break;
default:
assert(false && "Unknown heap type.");
break;
}
if (desc.committed) {
createInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
}
VkDeviceSize minAlignment = 0;
// The specification imposes an alignment requirement for SBTs.
if (desc.flags & RenderBufferFlag::SHADER_BINDING_TABLE) {
minAlignment = device->rtPipelineProperties.shaderGroupBaseAlignment;
}
VkResult res;
if (minAlignment > 0) {
res = vmaCreateBufferWithAlignment(device->allocator, &bufferInfo, &createInfo, minAlignment, &vk, &allocation, &allocationInfo);
}
else {
res = vmaCreateBuffer(device->allocator, &bufferInfo, &createInfo, &vk, &allocation, &allocationInfo);
}
if (res != VK_SUCCESS) {
fprintf(stderr, "vmaCreateBuffer failed with error code 0x%X.\n", res);
return;
}
}
VulkanBuffer::~VulkanBuffer() {
if (vk != VK_NULL_HANDLE) {
vmaDestroyBuffer(device->allocator, vk, allocation);
}
}
void *VulkanBuffer::map(uint32_t subresource, const RenderRange *readRange) {
void *data = nullptr;
VkResult res = vmaMapMemory(device->allocator, allocation, &data);
if (res != VK_SUCCESS) {
fprintf(stderr, "vmaMapMemory failed with error code 0x%X.\n", res);
return nullptr;
}
return data;
}
void VulkanBuffer::unmap(uint32_t subresource, const RenderRange *writtenRange) {
vmaUnmapMemory(device->allocator, allocation);
}
std::unique_ptr<RenderBufferFormattedView> VulkanBuffer::createBufferFormattedView(RenderFormat format) {
return std::make_unique<VulkanBufferFormattedView>(this, format);
}
void VulkanBuffer::setName(const std::string &name) {
setObjectName(device->vk, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, uint64_t(vk), name);
}
uint64_t VulkanBuffer::getDeviceAddress() const {
VkBufferDeviceAddressInfo info;
info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
info.pNext = nullptr;
info.buffer = vk;
return vkGetBufferDeviceAddress(device->vk, &info);
}
// VulkanBufferFormattedView
VulkanBufferFormattedView::VulkanBufferFormattedView(VulkanBuffer *buffer, RenderFormat format) {
assert(buffer != nullptr);
assert((buffer->desc.flags & RenderBufferFlag::FORMATTED) && "Buffer must allow formatted views.");
this->buffer = buffer;
VkBufferViewCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
createInfo.buffer = buffer->vk;
createInfo.format = toVk(format);
createInfo.offset = 0;
createInfo.range = buffer->desc.size;
VkResult res = vkCreateBufferView(buffer->device->vk, &createInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateBufferView failed with error code 0x%X.\n", res);
return;
}
}
VulkanBufferFormattedView::~VulkanBufferFormattedView() {
if (vk != VK_NULL_HANDLE) {
vkDestroyBufferView(buffer->device->vk, vk, nullptr);
}
}
// VulkanTexture
VulkanTexture::VulkanTexture(VulkanDevice *device, VulkanPool *pool, const RenderTextureDesc &desc) {
assert(device != nullptr);
this->device = device;
this->pool = pool;
this->desc = desc;
this->ownership = true;
VkImageCreateInfo imageInfo = {};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = toImageType(desc.dimension);
imageInfo.format = toVk(desc.format);
imageInfo.extent.width = uint32_t(desc.width);
imageInfo.extent.height = desc.height;
imageInfo.extent.depth = desc.depth;
imageInfo.mipLevels = desc.mipLevels;
imageInfo.arrayLayers = desc.arraySize;
imageInfo.samples = VkSampleCountFlagBits(desc.multisampling.sampleCount);
imageInfo.tiling = toVk(desc.textureArrangement);
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage |= (desc.flags & RenderTextureFlag::RENDER_TARGET) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0;
imageInfo.usage |= (desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : 0;
imageInfo.usage |= (desc.flags & RenderTextureFlag::STORAGE) ? VK_IMAGE_USAGE_STORAGE_BIT : 0;
if (desc.multisampling.sampleLocationsEnabled && (desc.flags & RenderTextureFlag::DEPTH_TARGET)) {
imageInfo.flags |= VK_IMAGE_CREATE_SAMPLE_LOCATIONS_COMPATIBLE_DEPTH_BIT_EXT;
}
if (desc.flags & RenderTextureFlag::CUBE) {
imageInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
}
imageFormat = imageInfo.format;
fillSubresourceRange();
VmaAllocationCreateInfo createInfo = {};
createInfo.pool = (pool != nullptr) ? pool->vk : VK_NULL_HANDLE;
createInfo.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
if (desc.committed) {
createInfo.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
}
VkResult res = vmaCreateImage(device->allocator, &imageInfo, &createInfo, &vk, &allocation, &allocationInfo);
if (res != VK_SUCCESS) {
fprintf(stderr, "vmaCreateImage failed with error code 0x%X.\n", res);
return;
}
createImageView(imageInfo.format);
}
VulkanTexture::VulkanTexture(VulkanDevice *device, VkImage image) {
assert(device != nullptr);
assert(image != VK_NULL_HANDLE);
this->device = device;
vk = image;
}
VulkanTexture::~VulkanTexture() {
if (imageView != VK_NULL_HANDLE) {
vkDestroyImageView(device->vk, imageView, nullptr);
}
if (ownership && (vk != VK_NULL_HANDLE)) {
vmaDestroyImage(device->allocator, vk, allocation);
}
}
void VulkanTexture::createImageView(VkFormat format) {
VkImageView view = VK_NULL_HANDLE;
VkImageViewCreateInfo viewInfo = {};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = vk;
viewInfo.viewType = toImageViewType(desc.dimension);
viewInfo.format = format;
viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.subresourceRange = imageSubresourceRange;
VkResult res = vkCreateImageView(device->vk, &viewInfo, nullptr, &imageView);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateImageView failed with error code 0x%X.\n", res);
return;
}
}
std::unique_ptr<RenderTextureView> VulkanTexture::createTextureView(const RenderTextureViewDesc &desc) {
return std::make_unique<VulkanTextureView>(this, desc);
}
void VulkanTexture::setName(const std::string &name) {
setObjectName(device->vk, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT, uint64_t(vk), name);
}
void VulkanTexture::fillSubresourceRange() {
imageSubresourceRange.aspectMask = (desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
imageSubresourceRange.baseMipLevel = 0;
imageSubresourceRange.levelCount = desc.mipLevels;
imageSubresourceRange.baseArrayLayer = 0;
imageSubresourceRange.layerCount = 1;
}
// VulkanTextureView
VulkanTextureView::VulkanTextureView(VulkanTexture *texture, const RenderTextureViewDesc &desc) {
assert(texture != nullptr);
this->texture = texture;
VkImageViewCreateInfo viewInfo = {};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = texture->vk;
viewInfo.viewType = toImageViewType(desc.dimension);
viewInfo.format = toVk(desc.format);
viewInfo.components.r = toVk(desc.componentMapping.r);
viewInfo.components.g = toVk(desc.componentMapping.g);
viewInfo.components.b = toVk(desc.componentMapping.b);
viewInfo.components.a = toVk(desc.componentMapping.a);
viewInfo.subresourceRange.aspectMask = (texture->desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = desc.mipSlice;
viewInfo.subresourceRange.levelCount = desc.mipLevels;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = texture->desc.arraySize;
VkResult res = vkCreateImageView(texture->device->vk, &viewInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateImageView failed with error code 0x%X.\n", res);
return;
}
}
VulkanTextureView::~VulkanTextureView() {
if (vk != VK_NULL_HANDLE) {
vkDestroyImageView(texture->device->vk, vk, nullptr);
}
}
// VulkanAccelerationStructure
VulkanAccelerationStructure::VulkanAccelerationStructure(VulkanDevice *device, const RenderAccelerationStructureDesc &desc) {
assert(device != nullptr);
assert(desc.buffer.ref != nullptr);
this->device = device;
this->type = desc.type;
const VulkanBuffer *interfaceBuffer = static_cast<const VulkanBuffer *>(desc.buffer.ref);
VkAccelerationStructureCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR;
createInfo.buffer = interfaceBuffer->vk;
createInfo.offset = desc.buffer.offset;
createInfo.size = desc.size;
createInfo.type = toVk(desc.type);
VkResult res = vkCreateAccelerationStructureKHR(device->vk, &createInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateAccelerationStructureKHR failed with error code 0x%X.\n", res);
return;
}
}
VulkanAccelerationStructure::~VulkanAccelerationStructure() {
if (vk != VK_NULL_HANDLE) {
vkDestroyAccelerationStructureKHR(device->vk, vk, nullptr);
}
}
// VulkanDescriptorSetLayout
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(VulkanDevice *device, const RenderDescriptorSetDesc &descriptorSetDesc) {
assert(device != nullptr);
this->device = device;
// Gather immutable sampler handles.
thread_local std::vector<VkSampler> samplerHandles;
samplerHandles.clear();
for (uint32_t i = 0; i < descriptorSetDesc.descriptorRangesCount; i++) {
const RenderDescriptorRange &srcRange = descriptorSetDesc.descriptorRanges[i];
if (srcRange.immutableSampler != nullptr) {
for (uint32_t j = 0; j < srcRange.count; j++) {
const VulkanSampler *interfaceSampler = static_cast<const VulkanSampler *>(srcRange.immutableSampler[j]);
assert(interfaceSampler != nullptr);
samplerHandles.emplace_back(interfaceSampler->vk);
}
}
}
// Create bindings.
uint32_t immutableSamplerIndex = 0;
for (uint32_t i = 0; i < descriptorSetDesc.descriptorRangesCount; i++) {
const RenderDescriptorRange &srcRange = descriptorSetDesc.descriptorRanges[i];
VkDescriptorSetLayoutBinding dstBinding = {};
dstBinding.binding = srcRange.binding;
dstBinding.descriptorCount = srcRange.count;
dstBinding.stageFlags = VK_SHADER_STAGE_ALL;
dstBinding.descriptorType = toVk(srcRange.type);
if (srcRange.immutableSampler != nullptr) {
dstBinding.pImmutableSamplers = &samplerHandles[immutableSamplerIndex];
immutableSamplerIndex += srcRange.count;
}
uint32_t indexBase = uint32_t(descriptorIndexBases.size());
uint32_t bindingIndex = uint32_t(setBindings.size());
for (uint32_t j = 0; j < srcRange.count; j++) {
descriptorIndexBases.emplace_back(indexBase);
descriptorBindingIndices.emplace_back(bindingIndex);
}
setBindings.emplace_back(dstBinding);
}
VkDescriptorSetLayoutCreateInfo setLayoutInfo = {};
setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
setLayoutInfo.pBindings = !setBindings.empty() ? setBindings.data() : nullptr;
setLayoutInfo.bindingCount = uint32_t(setBindings.size());
thread_local std::vector<VkDescriptorBindingFlags> bindingFlags;
VkDescriptorSetLayoutBindingFlagsCreateInfo flagsInfo = {};
if (descriptorSetDesc.lastRangeIsBoundless && (descriptorSetDesc.descriptorRangesCount > 0)) {
bindingFlags.clear();
bindingFlags.resize(descriptorSetDesc.descriptorRangesCount, 0);
bindingFlags[descriptorSetDesc.descriptorRangesCount - 1] = VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT | VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT;
flagsInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO;
flagsInfo.pBindingFlags = bindingFlags.data();
flagsInfo.bindingCount = uint32_t(bindingFlags.size());
setLayoutInfo.pNext = &flagsInfo;
setLayoutInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT;
}
VkResult res = vkCreateDescriptorSetLayout(device->vk, &setLayoutInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateDescriptorSetLayout failed with error code 0x%X.\n", res);
return;
}
}
VulkanDescriptorSetLayout::~VulkanDescriptorSetLayout() {
if (vk != VK_NULL_HANDLE) {
vkDestroyDescriptorSetLayout(device->vk, vk, nullptr);
}
}
// VulkanPipelineLayout
VulkanPipelineLayout::VulkanPipelineLayout(VulkanDevice *device, const RenderPipelineLayoutDesc &desc) {
assert(device != nullptr);
this->device = device;
VkPipelineLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
for (uint32_t i = 0; i < desc.pushConstantRangesCount; i++) {
const RenderPushConstantRange &srcRange = desc.pushConstantRanges[i];
VkPushConstantRange dstRange = {};
dstRange.size = srcRange.size;
dstRange.offset = srcRange.offset;
dstRange.stageFlags |= (srcRange.stageFlags & RenderShaderStageFlag::VERTEX) ? VK_SHADER_STAGE_VERTEX_BIT : 0;
dstRange.stageFlags |= (srcRange.stageFlags & RenderShaderStageFlag::GEOMETRY) ? VK_SHADER_STAGE_GEOMETRY_BIT : 0;
dstRange.stageFlags |= (srcRange.stageFlags & RenderShaderStageFlag::PIXEL) ? VK_SHADER_STAGE_FRAGMENT_BIT : 0;
dstRange.stageFlags |= (srcRange.stageFlags & RenderShaderStageFlag::COMPUTE) ? VK_SHADER_STAGE_COMPUTE_BIT : 0;
dstRange.stageFlags |= (srcRange.stageFlags & RenderShaderStageFlag::RAYGEN) ? VK_SHADER_STAGE_RAYGEN_BIT_KHR : 0;
dstRange.stageFlags |= (srcRange.stageFlags & RenderShaderStageFlag::ANY_HIT) ? VK_SHADER_STAGE_ANY_HIT_BIT_KHR : 0;
dstRange.stageFlags |= (srcRange.stageFlags & RenderShaderStageFlag::CLOSEST_HIT) ? VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR : 0;
dstRange.stageFlags |= (srcRange.stageFlags & RenderShaderStageFlag::MISS) ? VK_SHADER_STAGE_MISS_BIT_KHR : 0;
dstRange.stageFlags |= (srcRange.stageFlags & RenderShaderStageFlag::CALLABLE) ? VK_SHADER_STAGE_CALLABLE_BIT_KHR : 0;
pushConstantRanges.emplace_back(dstRange);
}
layoutInfo.pPushConstantRanges = !pushConstantRanges.empty() ? pushConstantRanges.data() : nullptr;
layoutInfo.pushConstantRangeCount = uint32_t(pushConstantRanges.size());
thread_local std::vector<VkDescriptorSetLayout> setLayoutHandles;
setLayoutHandles.clear();
for (uint32_t i = 0; i < desc.descriptorSetDescsCount; i++) {
VulkanDescriptorSetLayout *setLayout = new VulkanDescriptorSetLayout(device, desc.descriptorSetDescs[i]);
descriptorSetLayouts.emplace_back(setLayout);
setLayoutHandles.emplace_back(setLayout->vk);
}
layoutInfo.pSetLayouts = !setLayoutHandles.empty() ? setLayoutHandles.data() : nullptr;
layoutInfo.setLayoutCount = uint32_t(setLayoutHandles.size());
VkResult res = vkCreatePipelineLayout(device->vk, &layoutInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreatePipelineLayout failed with error code 0x%X.\n", res);
return;
}
}
VulkanPipelineLayout::~VulkanPipelineLayout() {
if (vk != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(device->vk, vk, nullptr);
}
for (VulkanDescriptorSetLayout *setLayout : descriptorSetLayouts) {
delete setLayout;
}
}
// VulkanShader
VulkanShader::VulkanShader(VulkanDevice *device, const void *data, uint64_t size, const char *entryPointName, RenderShaderFormat format) {
assert(device != nullptr);
assert(data != nullptr);
assert(size > 0);
assert(format != RenderShaderFormat::UNKNOWN);
assert(format == RenderShaderFormat::SPIRV);
this->device = device;
this->format = format;
this->entryPointName = (entryPointName != nullptr) ? std::string(entryPointName) : std::string();
VkShaderModuleCreateInfo shaderInfo = {};
shaderInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
shaderInfo.pCode = reinterpret_cast<const uint32_t *>(data);
shaderInfo.codeSize = size;
VkResult res = vkCreateShaderModule(device->vk, &shaderInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateShaderModule failed with error code 0x%X.\n", res);
return;
}
}
VulkanShader::~VulkanShader() {
if (vk != VK_NULL_HANDLE) {
vkDestroyShaderModule(device->vk, vk, nullptr);
}
}
// VulkanSampler
VulkanSampler::VulkanSampler(VulkanDevice *device, const RenderSamplerDesc &desc) {
assert(device != nullptr);
this->device = device;
VkSamplerCreateInfo samplerInfo = {};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.minFilter = toVk(desc.minFilter);
samplerInfo.magFilter = toVk(desc.magFilter);
samplerInfo.mipmapMode = toVk(desc.mipmapMode);
samplerInfo.addressModeU = toVk(desc.addressU);
samplerInfo.addressModeV = toVk(desc.addressV);
samplerInfo.addressModeW = toVk(desc.addressW);
samplerInfo.mipLodBias = desc.mipLODBias;
samplerInfo.anisotropyEnable = desc.anisotropyEnabled;
samplerInfo.maxAnisotropy = float(desc.maxAnisotropy);
samplerInfo.compareEnable = desc.comparisonEnabled;
samplerInfo.compareOp = toVk(desc.comparisonFunc);
samplerInfo.minLod = desc.minLOD;
samplerInfo.maxLod = desc.maxLOD;
samplerInfo.borderColor = toVk(desc.borderColor);
samplerInfo.unnormalizedCoordinates = VK_FALSE;
VkResult res = vkCreateSampler(device->vk, &samplerInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateSampler failed with error code 0x%X.\n", res);
return;
}
}
VulkanSampler::~VulkanSampler() {
if (vk != VK_NULL_HANDLE) {
vkDestroySampler(device->vk, vk, nullptr);
}
}
// VulkanPipeline
VulkanPipeline::VulkanPipeline(VulkanDevice *device, Type type) {
assert(device != nullptr);
assert(type != Type::Unknown);
this->device = device;
this->type = type;
}
VulkanPipeline::~VulkanPipeline() { }
// VulkanComputePipeline
VulkanComputePipeline::VulkanComputePipeline(VulkanDevice *device, const RenderComputePipelineDesc &desc) : VulkanPipeline(device, Type::Compute) {
assert(desc.computeShader != nullptr);
assert(desc.pipelineLayout != nullptr);
std::vector<VkSpecializationMapEntry> specEntries(desc.specConstantsCount);
std::vector<uint32_t> specData(desc.specConstantsCount);
VkSpecializationInfo specInfo = {};
fillSpecInfo(desc.specConstants, desc.specConstantsCount, specInfo, specEntries.data(), specData.data());
const VulkanShader *computeShader = static_cast<const VulkanShader *>(desc.computeShader);
VkPipelineShaderStageCreateInfo stageInfo = {};
stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT;
stageInfo.module = computeShader->vk;
stageInfo.pName = computeShader->entryPointName.c_str();
stageInfo.pSpecializationInfo = (specInfo.mapEntryCount > 0) ? &specInfo : nullptr;
const VulkanPipelineLayout *pipelineLayout = static_cast<const VulkanPipelineLayout *>(desc.pipelineLayout);
VkComputePipelineCreateInfo pipelineInfo = {};
pipelineInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
pipelineInfo.layout = pipelineLayout->vk;
pipelineInfo.stage = stageInfo;
VkResult res = vkCreateComputePipelines(device->vk, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateComputePipelines failed with error code 0x%X.\n", res);
return;
}
}
VulkanComputePipeline::~VulkanComputePipeline() {
if (vk != VK_NULL_HANDLE) {
vkDestroyPipeline(device->vk, vk, nullptr);
}
}
void VulkanComputePipeline::setName(const std::string& name) const {
setObjectName(device->vk, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, uint64_t(vk), name);
}
RenderPipelineProgram VulkanComputePipeline::getProgram(const std::string &name) const {
assert(false && "Compute pipelines can't retrieve shader programs.");
return RenderPipelineProgram();
}
// VulkanGraphicsPipeline
VulkanGraphicsPipeline::VulkanGraphicsPipeline(VulkanDevice *device, const RenderGraphicsPipelineDesc &desc) : VulkanPipeline(device, Type::Graphics) {
assert(desc.pipelineLayout != nullptr);
thread_local std::vector<VkPipelineShaderStageCreateInfo> stages;
stages.clear();
std::vector<VkSpecializationMapEntry> specEntries(desc.specConstantsCount);
std::vector<uint32_t> specData(desc.specConstantsCount);
VkSpecializationInfo specInfo = {};
fillSpecInfo(desc.specConstants, desc.specConstantsCount, specInfo, specEntries.data(), specData.data());
const VkSpecializationInfo *pSpecInfo = (specInfo.mapEntryCount > 0) ? &specInfo : nullptr;
if (desc.vertexShader != nullptr) {
const VulkanShader *vertexShader = static_cast<const VulkanShader *>(desc.vertexShader);
VkPipelineShaderStageCreateInfo stageInfo = {};
stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
stageInfo.module = vertexShader->vk;
stageInfo.pName = vertexShader->entryPointName.c_str();
stageInfo.pSpecializationInfo = pSpecInfo;
stages.emplace_back(stageInfo);
}
if (desc.geometryShader != nullptr) {
const VulkanShader *geometryShader = static_cast<const VulkanShader *>(desc.geometryShader);
VkPipelineShaderStageCreateInfo stageInfo = {};
stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stageInfo.stage = VK_SHADER_STAGE_GEOMETRY_BIT;
stageInfo.module = geometryShader->vk;
stageInfo.pName = geometryShader->entryPointName.c_str();
stageInfo.pSpecializationInfo = pSpecInfo;
stages.emplace_back(stageInfo);
}
if (desc.pixelShader != nullptr) {
const VulkanShader *pixelShader = static_cast<const VulkanShader *>(desc.pixelShader);
VkPipelineShaderStageCreateInfo stageInfo = {};
stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
stageInfo.module = pixelShader->vk;
stageInfo.pName = pixelShader->entryPointName.c_str();
stageInfo.pSpecializationInfo = pSpecInfo;
stages.emplace_back(stageInfo);
}
thread_local std::vector<VkVertexInputBindingDescription> vertexBindings;
thread_local std::vector<VkVertexInputAttributeDescription> vertexAttributes;
vertexBindings.clear();
vertexAttributes.clear();
for (uint32_t i = 0; i < desc.inputSlotsCount; i++) {
const RenderInputSlot &inputSlot = desc.inputSlots[i];
VkVertexInputBindingDescription binding = {};
binding.binding = inputSlot.index;
binding.stride = inputSlot.stride;
binding.inputRate = toVk(inputSlot.classification);
vertexBindings.emplace_back(binding);
}
for (uint32_t i = 0; i < desc.inputElementsCount; i++) {
const RenderInputElement &inputElement = desc.inputElements[i];
VkVertexInputAttributeDescription attribute = {};
attribute.location = inputElement.location;
attribute.binding = inputElement.slotIndex;
attribute.format = toVk(inputElement.format);
attribute.offset = inputElement.alignedByteOffset;
vertexAttributes.emplace_back(attribute);
}
VkPipelineVertexInputStateCreateInfo vertexInput = {};
vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInput.pVertexBindingDescriptions = !vertexBindings.empty() ? vertexBindings.data() : nullptr;
vertexInput.vertexBindingDescriptionCount = uint32_t(vertexBindings.size());
vertexInput.pVertexAttributeDescriptions = !vertexAttributes.empty() ? vertexAttributes.data() : nullptr;
vertexInput.vertexAttributeDescriptionCount = uint32_t(vertexAttributes.size());
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = toVk(desc.primitiveTopology);
if (desc.primitiveTopology == RenderPrimitiveTopology::LINE_STRIP || desc.primitiveTopology == RenderPrimitiveTopology::TRIANGLE_STRIP) {
inputAssembly.primitiveRestartEnable = VK_TRUE;
}
uint32_t renderTargetCount = desc.renderTargetCount;
if (renderTargetCount == 0 && desc.depthTargetFormat != RenderFormat::UNKNOWN) {
renderTargetCount = 1;
}
VkPipelineViewportStateCreateInfo viewportState = {};
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewportState.viewportCount = renderTargetCount;
viewportState.scissorCount = renderTargetCount;
VkPipelineRasterizationStateCreateInfo rasterization = {};
rasterization.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterization.depthClampEnable = !desc.depthClipEnabled;
rasterization.rasterizerDiscardEnable = VK_FALSE;
rasterization.polygonMode = VK_POLYGON_MODE_FILL;
rasterization.lineWidth = 1.0f;
rasterization.cullMode = toVk(desc.cullMode);
rasterization.frontFace = VK_FRONT_FACE_CLOCKWISE;
if (desc.dynamicDepthBiasEnabled) {
rasterization.depthBiasEnable = true;
}
else if (desc.depthBias != 0 || desc.slopeScaledDepthBias != 0.0f) {
rasterization.depthBiasEnable = true;
rasterization.depthBiasConstantFactor = float(desc.depthBias);
rasterization.depthBiasSlopeFactor = desc.slopeScaledDepthBias;
}
thread_local std::vector<VkSampleLocationEXT> sampleLocationVector;
VkSampleLocationsInfoEXT sampleLocationsInfo = {};
VkPipelineSampleLocationsStateCreateInfoEXT sampleLocations = {};
const void *multisamplingNext = nullptr;
if (desc.multisampling.sampleLocationsEnabled) {
const float *coordinateRange = device->sampleLocationProperties.sampleLocationCoordinateRange;
const float coordinateBase = coordinateRange[0];
const float coordinateSpace = (coordinateRange[1] - coordinateRange[0]) / 15.0f;
sampleLocationVector.resize(desc.multisampling.sampleCount);
for (uint32_t i = 0; i < desc.multisampling.sampleCount; i++) {
const RenderMultisamplingLocation &location = desc.multisampling.sampleLocations[i];
sampleLocationVector[i].x = coordinateBase + (location.x + 8) * coordinateSpace;
sampleLocationVector[i].y = coordinateBase + (location.y + 8) * coordinateSpace;
}
sampleLocationsInfo.sType = VK_STRUCTURE_TYPE_SAMPLE_LOCATIONS_INFO_EXT;
sampleLocationsInfo.sampleLocationsPerPixel = VkSampleCountFlagBits(desc.multisampling.sampleCount);
sampleLocationsInfo.sampleLocationGridSize.width = 1;
sampleLocationsInfo.sampleLocationGridSize.height = 1;
sampleLocationsInfo.sampleLocationsCount = uint32_t(sampleLocationVector.size());
sampleLocationsInfo.pSampleLocations = sampleLocationVector.data();
sampleLocations.sType = VK_STRUCTURE_TYPE_PIPELINE_SAMPLE_LOCATIONS_STATE_CREATE_INFO_EXT;
sampleLocations.sampleLocationsEnable = true;
sampleLocations.sampleLocationsInfo = sampleLocationsInfo;
multisamplingNext = &sampleLocations;
}
VkPipelineMultisampleStateCreateInfo multisampling = {};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.pNext = multisamplingNext;
multisampling.rasterizationSamples = VkSampleCountFlagBits(desc.multisampling.sampleCount);
multisampling.alphaToCoverageEnable = desc.alphaToCoverageEnabled;
thread_local std::vector<VkPipelineColorBlendAttachmentState> colorBlendAttachments;
colorBlendAttachments.clear();
for (uint32_t i = 0; i < desc.renderTargetCount; i++) {
VkPipelineColorBlendAttachmentState attachment = {};
const RenderBlendDesc &blendDesc = desc.renderTargetBlend[i];
attachment.blendEnable = blendDesc.blendEnabled;
attachment.srcColorBlendFactor = toVk(blendDesc.srcBlend);
attachment.dstColorBlendFactor = toVk(blendDesc.dstBlend);
attachment.colorBlendOp = toVk(blendDesc.blendOp);
attachment.srcAlphaBlendFactor = toVk(blendDesc.srcBlendAlpha);
attachment.dstAlphaBlendFactor = toVk(blendDesc.dstBlendAlpha);
attachment.alphaBlendOp = toVk(blendDesc.blendOpAlpha);
attachment.colorWriteMask = blendDesc.renderTargetWriteMask;
colorBlendAttachments.emplace_back(attachment);
}
VkPipelineColorBlendStateCreateInfo colorBlend = {};
colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlend.logicOpEnable = desc.logicOpEnabled;
colorBlend.logicOp = toVk(desc.logicOp);
colorBlend.pAttachments = !colorBlendAttachments.empty() ? colorBlendAttachments.data() : nullptr;
colorBlend.attachmentCount = uint32_t(colorBlendAttachments.size());
VkPipelineDepthStencilStateCreateInfo depthStencil = {};
depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencil.depthTestEnable = desc.depthEnabled;
depthStencil.depthWriteEnable = desc.depthWriteEnabled;
depthStencil.depthCompareOp = toVk(desc.depthFunction);
depthStencil.depthBoundsTestEnable = VK_FALSE;
depthStencil.minDepthBounds = 0.0f;
depthStencil.maxDepthBounds = 1.0f;
thread_local std::vector<VkDynamicState> dynamicStates;
dynamicStates.clear();
dynamicStates.emplace_back(VK_DYNAMIC_STATE_VIEWPORT);
dynamicStates.emplace_back(VK_DYNAMIC_STATE_SCISSOR);
if (desc.dynamicDepthBiasEnabled) {
dynamicStates.emplace_back(VK_DYNAMIC_STATE_DEPTH_BIAS);
}
VkPipelineDynamicStateCreateInfo dynamicState = {};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.pDynamicStates = dynamicStates.data();
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
thread_local std::vector<VkFormat> renderTargetFormats;
renderTargetFormats.resize(desc.renderTargetCount);
for (uint32_t i = 0; i < desc.renderTargetCount; i++) {
renderTargetFormats[i] = toVk(desc.renderTargetFormat[i]);
}
renderPass = createRenderPass(device, renderTargetFormats.data(), desc.renderTargetCount, toVk(desc.depthTargetFormat), VkSampleCountFlagBits(desc.multisampling.sampleCount));
if (renderPass == VK_NULL_HANDLE) {
return;
}
const VulkanPipelineLayout *pipelineLayout = static_cast<const VulkanPipelineLayout *>(desc.pipelineLayout);
VkGraphicsPipelineCreateInfo pipelineInfo = {};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.pStages = stages.data();
pipelineInfo.stageCount = uint32_t(stages.size());
pipelineInfo.pVertexInputState = &vertexInput;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterization;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pColorBlendState = &colorBlend;
pipelineInfo.pDepthStencilState = &depthStencil;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = pipelineLayout->vk;
pipelineInfo.renderPass = renderPass;
VkResult res = vkCreateGraphicsPipelines(device->vk, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateGraphicsPipelines failed with error code 0x%X.\n", res);
return;
}
}
VulkanGraphicsPipeline::~VulkanGraphicsPipeline() {
if (vk != VK_NULL_HANDLE) {
vkDestroyPipeline(device->vk, vk, nullptr);
}
if (renderPass != VK_NULL_HANDLE) {
vkDestroyRenderPass(device->vk, renderPass, nullptr);
}
}
void VulkanGraphicsPipeline::setName(const std::string& name) const {
setObjectName(device->vk, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, uint64_t(vk), name);
}
RenderPipelineProgram VulkanGraphicsPipeline::getProgram(const std::string &name) const {
assert(false && "Graphics pipelines can't retrieve shader programs.");
return RenderPipelineProgram();
}
VkRenderPass VulkanGraphicsPipeline::createRenderPass(VulkanDevice *device, const VkFormat *renderTargetFormat, uint32_t renderTargetCount, VkFormat depthTargetFormat, VkSampleCountFlagBits sampleCount) {
VkRenderPass renderPass = VK_NULL_HANDLE;
VkSubpassDescription subpass = {};
VkAttachmentReference depthReference = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
thread_local std::vector<VkAttachmentDescription> attachments;
thread_local std::vector<VkAttachmentReference> colorReferences;
attachments.clear();
colorReferences.clear();
for (uint32_t i = 0; i < renderTargetCount; i++) {
VkAttachmentReference reference = {};
reference.attachment = uint32_t(attachments.size());
reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorReferences.emplace_back(reference);
VkAttachmentDescription attachment = {};
attachment.format = renderTargetFormat[i];
attachment.samples = sampleCount;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments.emplace_back(attachment);
}
subpass.pColorAttachments = !colorReferences.empty() ? colorReferences.data() : nullptr;
subpass.colorAttachmentCount = uint32_t(colorReferences.size());
if (depthTargetFormat != VK_FORMAT_UNDEFINED) {
depthReference.attachment = uint32_t(attachments.size());
depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
subpass.pDepthStencilAttachment = &depthReference;
VkAttachmentDescription attachment = {};
attachment.format = depthTargetFormat;
attachment.samples = sampleCount;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = attachment.loadOp;
attachment.stencilStoreOp = attachment.storeOp;
attachment.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachments.emplace_back(attachment);
}
VkRenderPassCreateInfo passInfo = {};
passInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
passInfo.pAttachments = !attachments.empty() ? attachments.data() : nullptr;
passInfo.attachmentCount = uint32_t(attachments.size());
passInfo.pSubpasses = &subpass;
passInfo.subpassCount = 1;
VkResult res = vkCreateRenderPass(device->vk, &passInfo, nullptr, &renderPass);
if (res == VK_SUCCESS) {
return renderPass;
}
else {
fprintf(stderr, "vkCreateRenderPass failed with error code 0x%X.\n", res);
return VK_NULL_HANDLE;
}
}
// VulkanRaytracingPipeline
VulkanRaytracingPipeline::VulkanRaytracingPipeline(VulkanDevice *device, const RenderRaytracingPipelineDesc &desc, const RenderPipeline *previousPipeline) : VulkanPipeline(device, VulkanPipeline::Type::Raytracing) {
assert(desc.pipelineLayout != nullptr);
assert(!desc.stateUpdateEnabled && "State updates are not supported.");
std::vector<VkPipelineShaderStageCreateInfo> shaderStages;
std::vector<VkRayTracingShaderGroupCreateInfoKHR> shaderGroups;
std::unordered_map<std::string, uint32_t> shaderIndices;
// Prepare all the vectors for the spec constants beforehand so they're not re-allocated.
std::vector<VkSpecializationMapEntry> specEntries;
std::vector<uint32_t> specData;
std::vector<VkSpecializationInfo> specInfo;
for (uint32_t i = 0; i < desc.librariesCount; i++) {
const RenderRaytracingPipelineLibrary &library = desc.libraries[i];
for (uint32_t j = 0; j < library.symbolsCount; j++) {
const RenderRaytracingPipelineLibrarySymbol &symbol = library.symbols[j];
if (symbol.specConstantsCount == 0) {
continue;
}
for (uint32_t i = 0; i < symbol.specConstantsCount; i++) {
specEntries.emplace_back();
specData.emplace_back();
}
specInfo.emplace_back();
}
}
uint32_t specConstantIndex = 0;
uint32_t specConstantCursor = 0;
for (uint32_t i = 0; i < desc.librariesCount; i++) {
const RenderRaytracingPipelineLibrary &library = desc.libraries[i];
assert(library.shader != nullptr);
const VulkanShader *interfaceShader = static_cast<const VulkanShader *>(library.shader);
for (uint32_t j = 0; j < library.symbolsCount; j++) {
const RenderRaytracingPipelineLibrarySymbol &symbol = library.symbols[j];
const bool isRaygen = (symbol.type == RenderRaytracingPipelineLibrarySymbolType::RAYGEN);
const bool isMiss = (symbol.type == RenderRaytracingPipelineLibrarySymbolType::MISS);
const uint32_t shaderStageIndex = uint32_t(shaderStages.size());
const char *exportName = (symbol.exportName != nullptr) ? symbol.exportName : symbol.importName;
if (isRaygen || isMiss) {
VkRayTracingShaderGroupCreateInfoKHR groupInfo = {};
groupInfo.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
groupInfo.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
groupInfo.closestHitShader = VK_SHADER_UNUSED_KHR;
groupInfo.anyHitShader = VK_SHADER_UNUSED_KHR;
groupInfo.intersectionShader = VK_SHADER_UNUSED_KHR;
groupInfo.generalShader = shaderStageIndex;
nameProgramMap[std::string(exportName)] = uint32_t(shaderGroups.size());
shaderGroups.emplace_back(groupInfo);
}
VkPipelineShaderStageCreateInfo stageInfo = {};
stageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stageInfo.pName = symbol.importName;
stageInfo.module = interfaceShader->vk;
stageInfo.stage = toStage(symbol.type);
if (symbol.specConstantsCount > 0) {
stageInfo.pSpecializationInfo = &specInfo[specConstantIndex];
fillSpecInfo(symbol.specConstants, symbol.specConstantsCount, specInfo[specConstantIndex], &specEntries[specConstantCursor], &specData[specConstantCursor]);
specConstantCursor += symbol.specConstantsCount;
specConstantIndex++;
}
shaderIndices[std::string(exportName)] = uint32_t(shaderStages.size());
shaderStages.emplace_back(stageInfo);
}
}
for (uint32_t i = 0; i < desc.hitGroupsCount; i++) {
auto getShaderIndex = [&](const char *name) {
if (name != nullptr) {
auto it = shaderIndices.find(std::string(name));
assert(it != shaderIndices.end());
return it->second;
}
else {
return uint32_t(VK_SHADER_UNUSED_KHR);
}
};
const RenderRaytracingPipelineHitGroup &hitGroup = desc.hitGroups[i];
VkRayTracingShaderGroupCreateInfoKHR groupInfo = {};
groupInfo.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
groupInfo.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
groupInfo.generalShader = VK_SHADER_UNUSED_KHR;
groupInfo.closestHitShader = getShaderIndex(hitGroup.closestHitName);
groupInfo.anyHitShader = getShaderIndex(hitGroup.anyHitName);
groupInfo.intersectionShader = getShaderIndex(hitGroup.intersectionName);
nameProgramMap[std::string(hitGroup.hitGroupName)] = uint32_t(shaderGroups.size());
shaderGroups.emplace_back(groupInfo);
}
VkRayTracingPipelineInterfaceCreateInfoKHR interfaceInfo = {};
interfaceInfo.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_INTERFACE_CREATE_INFO_KHR;
interfaceInfo.maxPipelineRayPayloadSize = desc.maxPayloadSize;
interfaceInfo.maxPipelineRayHitAttributeSize = desc.maxAttributeSize;
const VulkanPipelineLayout *pipelineLayout = static_cast<const VulkanPipelineLayout *>(desc.pipelineLayout);
VkRayTracingPipelineCreateInfoKHR pipelineInfo = {};
pipelineInfo.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR;
pipelineInfo.pStages = shaderStages.data();
pipelineInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineInfo.pGroups = shaderGroups.data();
pipelineInfo.groupCount = static_cast<uint32_t>(shaderGroups.size());
pipelineInfo.maxPipelineRayRecursionDepth = desc.maxRecursionDepth;
pipelineInfo.layout = pipelineLayout->vk;
this->descriptorSetCount = uint32_t(pipelineLayout->descriptorSetLayouts.size());
VkResult res = vkCreateRayTracingPipelinesKHR(device->vk, nullptr, nullptr, 1, &pipelineInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateRayTracingPipelinesKHR failed with error code 0x%X.\n", res);
return;
}
groupCount = pipelineInfo.groupCount;
}
VulkanRaytracingPipeline::~VulkanRaytracingPipeline() {
if (vk != VK_NULL_HANDLE) {
vkDestroyPipeline(device->vk, vk, nullptr);
}
}
void VulkanRaytracingPipeline::setName(const std::string& name) const {
setObjectName(device->vk, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT, uint64_t(vk), name);
}
RenderPipelineProgram VulkanRaytracingPipeline::getProgram(const std::string &name) const {
auto it = nameProgramMap.find(name);
assert((it != nameProgramMap.end()) && "Program must exist in the PSO.");
return it->second;
}
// VulkanDescriptorSet
VulkanDescriptorSet::VulkanDescriptorSet(VulkanDevice *device, const RenderDescriptorSetDesc &desc) {
assert(device != nullptr);
this->device = device;
thread_local std::unordered_map<VkDescriptorType, uint32_t> typeCounts;
typeCounts.clear();
uint32_t boundlessRangeSize = 0;
uint32_t rangeCount = desc.descriptorRangesCount;
if (desc.lastRangeIsBoundless) {
assert((desc.descriptorRangesCount > 0) && "There must be at least one descriptor set to define the last range as boundless.");
// Ensure at least one entry is created for boundless ranges.
boundlessRangeSize = std::max(desc.boundlessRangeSize, 1U);
const RenderDescriptorRange &lastDescriptorRange = desc.descriptorRanges[desc.descriptorRangesCount - 1];
typeCounts[toVk(lastDescriptorRange.type)] += boundlessRangeSize;
rangeCount--;
}
for (uint32_t i = 0; i < rangeCount; i++) {
const RenderDescriptorRange &descriptorRange = desc.descriptorRanges[i];
typeCounts[toVk(descriptorRange.type)] += descriptorRange.count;
}
setLayout = new VulkanDescriptorSetLayout(device, desc);
descriptorPool = createDescriptorPool(device, typeCounts, desc.lastRangeIsBoundless);
if (descriptorPool == VK_NULL_HANDLE) {
return;
}
VkDescriptorSetAllocateInfo allocateInfo = {};
allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocateInfo.descriptorPool = descriptorPool;
allocateInfo.pSetLayouts = &setLayout->vk;
allocateInfo.descriptorSetCount = 1;
VkDescriptorSetVariableDescriptorCountAllocateInfo countInfo = {};
if (desc.lastRangeIsBoundless) {
countInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO;
countInfo.pDescriptorCounts = &boundlessRangeSize;
countInfo.descriptorSetCount = 1;
allocateInfo.pNext = &countInfo;
}
VkResult res = vkAllocateDescriptorSets(device->vk, &allocateInfo, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkAllocateDescriptorSets failed with error code 0x%X.\n", res);
return;
}
}
VulkanDescriptorSet::~VulkanDescriptorSet() {
if (descriptorPool != VK_NULL_HANDLE) {
vkDestroyDescriptorPool(device->vk, descriptorPool, nullptr);
}
delete setLayout;
}
void VulkanDescriptorSet::setBuffer(uint32_t descriptorIndex, const RenderBuffer *buffer, uint64_t bufferSize, const RenderBufferStructuredView *bufferStructuredView, const RenderBufferFormattedView *bufferFormattedView) {
if (buffer == nullptr) {
return;
}
const VulkanBuffer *interfaceBuffer = static_cast<const VulkanBuffer *>(buffer);
const VkBufferView *bufferView = nullptr;
VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = interfaceBuffer->vk;
bufferInfo.range = (bufferSize > 0) ? bufferSize : interfaceBuffer->desc.size;
if (bufferFormattedView != nullptr) {
assert((bufferStructuredView == nullptr) && "Can't use structured views and formatted views at the same time.");
const VulkanBufferFormattedView *interfaceBufferFormattedView = static_cast<const VulkanBufferFormattedView *>(bufferFormattedView);
bufferView = &interfaceBufferFormattedView->vk;
}
else if (bufferStructuredView != nullptr) {
assert((bufferFormattedView == nullptr) && "Can't use structured views and formatted views at the same time.");
assert(bufferStructuredView->structureByteStride > 0);
bufferInfo.offset = bufferStructuredView->firstElement * bufferStructuredView->structureByteStride;
}
else {
bufferInfo.offset = 0;
}
setDescriptor(descriptorIndex, &bufferInfo, nullptr, bufferView, nullptr);
}
void VulkanDescriptorSet::setTexture(uint32_t descriptorIndex, const RenderTexture *texture, const RenderTextureLayout textureLayout, const RenderTextureView *textureView) {
if (texture == nullptr) {
return;
}
const VulkanTexture *interfaceTexture = static_cast<const VulkanTexture *>(texture);
VkDescriptorImageInfo imageInfo = {};
imageInfo.imageLayout = toImageLayout(textureLayout);
if (textureView != nullptr) {
const VulkanTextureView *interfaceTextureView = static_cast<const VulkanTextureView *>(textureView);
imageInfo.imageView = interfaceTextureView->vk;
}
else {
imageInfo.imageView = (interfaceTexture != nullptr) ? interfaceTexture->imageView : VK_NULL_HANDLE;
}
setDescriptor(descriptorIndex, nullptr, &imageInfo, nullptr, nullptr);
}
void VulkanDescriptorSet::setSampler(uint32_t descriptorIndex, const RenderSampler *sampler) {
if (sampler == nullptr) {
return;
}
const VulkanSampler *interfaceSampler = static_cast<const VulkanSampler *>(sampler);
VkDescriptorImageInfo imageInfo = {};
imageInfo.sampler = interfaceSampler->vk;
setDescriptor(descriptorIndex, nullptr, &imageInfo, nullptr, nullptr);
}
void VulkanDescriptorSet::setAccelerationStructure(uint32_t descriptorIndex, const RenderAccelerationStructure *accelerationStructure) {
if (accelerationStructure == nullptr) {
return;
}
const VulkanAccelerationStructure *interfaceAccelerationStructure = static_cast<const VulkanAccelerationStructure *>(accelerationStructure);
VkWriteDescriptorSetAccelerationStructureKHR setAccelerationStructure = {};
setAccelerationStructure.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR;
setAccelerationStructure.pAccelerationStructures = &interfaceAccelerationStructure->vk;
setAccelerationStructure.accelerationStructureCount = 1;
setDescriptor(descriptorIndex, nullptr, nullptr, nullptr, &setAccelerationStructure);
}
void VulkanDescriptorSet::setDescriptor(uint32_t descriptorIndex, const VkDescriptorBufferInfo *bufferInfo, const VkDescriptorImageInfo *imageInfo, const VkBufferView *texelBufferView, void *pNext) {
assert(descriptorIndex < setLayout->descriptorBindingIndices.size());
const uint32_t indexBase = setLayout->descriptorIndexBases[descriptorIndex];
const uint32_t bindingIndex = setLayout->descriptorBindingIndices[descriptorIndex];
const VkDescriptorSetLayoutBinding &setLayoutBinding = setLayout->setBindings[bindingIndex];
VkWriteDescriptorSet writeDescriptor = {};
writeDescriptor.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
writeDescriptor.pNext = pNext;
writeDescriptor.dstSet = vk;
writeDescriptor.dstBinding = setLayoutBinding.binding;
writeDescriptor.dstArrayElement = descriptorIndex - indexBase;
writeDescriptor.descriptorCount = 1;
writeDescriptor.descriptorType = setLayoutBinding.descriptorType;
writeDescriptor.pBufferInfo = bufferInfo;
writeDescriptor.pImageInfo = imageInfo;
writeDescriptor.pTexelBufferView = texelBufferView;
vkUpdateDescriptorSets(device->vk, 1, &writeDescriptor, 0, nullptr);
}
VkDescriptorPool VulkanDescriptorSet::createDescriptorPool(VulkanDevice *device, const std::unordered_map<VkDescriptorType, uint32_t> &typeCounts, bool lastRangeIsBoundless) {
thread_local std::vector<VkDescriptorPoolSize> poolSizes;
poolSizes.clear();
VkDescriptorPool descriptorPool;
for (auto it : typeCounts) {
VkDescriptorPoolSize poolSize = {};
poolSize.type = it.first;
poolSize.descriptorCount = it.second;
poolSizes.emplace_back(poolSize);
}
VkDescriptorPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.maxSets = 1;
poolInfo.pPoolSizes = !poolSizes.empty() ? poolSizes.data() : nullptr;
poolInfo.poolSizeCount = uint32_t(poolSizes.size());
if (lastRangeIsBoundless) {
poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT;
}
VkResult res = vkCreateDescriptorPool(device->vk, &poolInfo, nullptr, &descriptorPool);
if (res == VK_SUCCESS) {
return descriptorPool;
}
else {
fprintf(stderr, "vkCreateDescriptorPool failed with error code 0x%X.\n", res);
return VK_NULL_HANDLE;
}
}
// VulkanSwapChain
VulkanSwapChain::VulkanSwapChain(VulkanCommandQueue *commandQueue, RenderWindow renderWindow, uint32_t textureCount, RenderFormat format, uint32_t maxFrameLatency) {
assert(commandQueue != nullptr);
assert(textureCount > 0);
this->commandQueue = commandQueue;
this->renderWindow = renderWindow;
this->format = format;
this->maxFrameLatency = maxFrameLatency;
VkResult res;
# ifdef _WIN64
assert(renderWindow != 0);
VkWin32SurfaceCreateInfoKHR surfaceCreateInfo = {};
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
surfaceCreateInfo.hwnd = HWND(renderWindow);
surfaceCreateInfo.hinstance = GetModuleHandle(nullptr);
VulkanInterface *renderInterface = commandQueue->device->renderInterface;
res = vkCreateWin32SurfaceKHR(renderInterface->instance, &surfaceCreateInfo, nullptr, &surface);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateWin32SurfaceKHR failed with error code 0x%X.\n", res);
return;
}
# elif defined(SDL_VULKAN_ENABLED)
VulkanInterface *renderInterface = commandQueue->device->renderInterface;
SDL_bool sdlRes = SDL_Vulkan_CreateSurface(renderWindow, renderInterface->instance, &surface);
if (sdlRes == SDL_FALSE) {
fprintf(stderr, "SDL_Vulkan_CreateSurface failed with error %s.\n", SDL_GetError());
return;
}
# elif defined(__ANDROID__)
assert(renderWindow != nullptr);
VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo = {};
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
surfaceCreateInfo.window = renderWindow;
VulkanInterface *renderInterface = commandQueue->device->renderInterface;
res = vkCreateAndroidSurfaceKHR(renderInterface->instance, &surfaceCreateInfo, nullptr, &surface);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateAndroidSurfaceKHR failed with error code 0x%X.\n", res);
return;
}
# elif defined(__linux__)
assert(renderWindow.display != 0);
assert(renderWindow.window != 0);
VkXlibSurfaceCreateInfoKHR surfaceCreateInfo = {};
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
surfaceCreateInfo.dpy = renderWindow.display;
surfaceCreateInfo.window = renderWindow.window;
VulkanInterface *renderInterface = commandQueue->device->renderInterface;
res = vkCreateXlibSurfaceKHR(renderInterface->instance, &surfaceCreateInfo, nullptr, &surface);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateXlibSurfaceKHR failed with error code 0x%X.\n", res);
return;
}
# elif defined(__APPLE__)
assert(renderWindow.window != 0);
assert(renderWindow.view != 0);
VkMetalSurfaceCreateInfoEXT surfaceCreateInfo = {};
surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
surfaceCreateInfo.pLayer = renderWindow.view;
VulkanInterface *renderInterface = commandQueue->device->renderInterface;
res = vkCreateMetalSurfaceEXT(renderInterface->instance, &surfaceCreateInfo, nullptr, &surface);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateMetalSurfaceEXT failed with error code 0x%X.\n", res);
return;
}
# endif
VkBool32 presentSupported = false;
VkPhysicalDevice physicalDevice = commandQueue->device->physicalDevice;
res = vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, commandQueue->familyIndex, surface, &presentSupported);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkGetPhysicalDeviceSurfaceSupportKHR failed with error code 0x%X.\n", res);
return;
}
if (!presentSupported) {
fprintf(stderr, "Command queue does not support present.\n");
return;
}
VkSurfaceCapabilitiesKHR surfaceCapabilities = {};
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &surfaceCapabilities);
// Pick an alpha compositing mode
if (surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) {
pickedAlphaFlag = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
}
else if (surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) {
pickedAlphaFlag = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
}
else {
fprintf(stderr, "No known supported alpha compositing mode\n");
return;
}
// Make sure maxImageCount is never below minImageCount, as it's allowed to be zero.
surfaceCapabilities.maxImageCount = std::max(surfaceCapabilities.minImageCount, surfaceCapabilities.maxImageCount);
// Clamp the requested buffer count between the bounds of the surface capabilities.
this->textureCount = std::clamp(textureCount, surfaceCapabilities.minImageCount, surfaceCapabilities.maxImageCount);
uint32_t surfaceFormatCount = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &surfaceFormatCount, nullptr);
std::vector<VkSurfaceFormatKHR> surfaceFormats(surfaceFormatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &surfaceFormatCount, surfaceFormats.data());
uint32_t presentModeCount = 0;
vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, nullptr);
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes.data());
immediatePresentModeSupported = std::find(presentModes.begin(), presentModes.end(), VK_PRESENT_MODE_IMMEDIATE_KHR) != presentModes.end();
// Check if the format we requested is part of the supported surface formats.
std::vector<VkSurfaceFormatKHR> compatibleSurfaceFormats;
VkFormat requestedFormat = toVk(format);
for (uint32_t i = 0; i < surfaceFormatCount; i++) {
if (surfaceFormats[i].format == requestedFormat) {
compatibleSurfaceFormats.emplace_back(surfaceFormats[i]);
break;
}
}
if (compatibleSurfaceFormats.empty()) {
fprintf(stderr, "No compatible surface formats were found.\n");
return;
}
// Pick the preferred color space, if not available, pick whatever first shows up on the list.
for (const VkSurfaceFormatKHR &surfaceFormat : compatibleSurfaceFormats) {
if (surfaceFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
pickedSurfaceFormat = surfaceFormat;
break;
}
}
if (pickedSurfaceFormat.format == VK_FORMAT_UNDEFINED) {
pickedSurfaceFormat = compatibleSurfaceFormats[0];
}
// FIFO is guaranteed to be supported.
requiredPresentMode = VK_PRESENT_MODE_FIFO_KHR;
// Pick an alpha compositing mode, prefer opaque over inherit.
if (surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) {
pickedAlphaFlag = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
}
else if (surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) {
pickedAlphaFlag = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
}
else {
fprintf(stderr, "No supported alpha compositing mode was found.\n");
return;
}
// Parent command queue should track this swap chain.
commandQueue->swapChains.insert(this);
}
VulkanSwapChain::~VulkanSwapChain() {
releaseImageViews();
releaseSwapChain();
if (surface != VK_NULL_HANDLE) {
VulkanInterface *renderInterface = commandQueue->device->renderInterface;
vkDestroySurfaceKHR(renderInterface->instance, surface, nullptr);
}
// Remove tracking from the parent command queue.
commandQueue->swapChains.erase(this);
}
bool VulkanSwapChain::present(uint32_t textureIndex, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount) {
thread_local std::vector<VkSemaphore> waitSemaphoresVector;
waitSemaphoresVector.clear();
for (uint32_t i = 0; i < waitSemaphoreCount; i++) {
VulkanCommandSemaphore *interfaceSemaphore = (VulkanCommandSemaphore *)(waitSemaphores[i]);
waitSemaphoresVector.emplace_back(interfaceSemaphore->vk);
}
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pSwapchains = &vk;
presentInfo.swapchainCount = 1;
presentInfo.pImageIndices = &textureIndex;
presentInfo.pWaitSemaphores = !waitSemaphoresVector.empty() ? waitSemaphoresVector.data() : nullptr;
presentInfo.waitSemaphoreCount = uint32_t(waitSemaphoresVector.size());
VkPresentIdKHR presentId = {};
if (commandQueue->device->capabilities.presentWait) {
currentPresentId++;
presentId.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR;
presentId.pPresentIds = &currentPresentId;
presentId.swapchainCount = 1;
presentInfo.pNext = &presentId;
}
VkResult res;
{
const std::scoped_lock queueLock(*commandQueue->queue->mutex);
res = vkQueuePresentKHR(commandQueue->queue->vk, &presentInfo);
}
// Handle the error silently.
#if defined(__APPLE__)
// Under MoltenVK, VK_SUBOPTIMAL_KHR does not result in a valid state for rendering. We intentionally
// only check for this error during present to avoid having to synchronize manually against the semaphore
// signalled by vkAcquireNextImageKHR.
if (res != VK_SUCCESS) {
#else
if ((res != VK_SUCCESS) && (res != VK_SUBOPTIMAL_KHR)) {
#endif
return false;
}
return true;
}
void VulkanSwapChain::wait() {
if (commandQueue->device->capabilities.presentWait && (currentPresentId >= maxFrameLatency)) {
constexpr uint64_t waitTimeout = 100000000;
vkWaitForPresentKHR(commandQueue->device->vk, vk, currentPresentId - (maxFrameLatency - 1), waitTimeout);
}
}
bool VulkanSwapChain::resize() {
getWindowSize(width, height);
// Don't recreate the swap chain at all if the window doesn't have a valid size.
if ((width == 0) || (height == 0)) {
return false;
}
// Destroy any image view references to the current swap chain.
releaseImageViews();
// We don't actually need to query the surface capabilities but the validation layer seems to cache the valid extents from this call.
VkSurfaceCapabilitiesKHR surfaceCapabilities = {};
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(commandQueue->device->physicalDevice, surface, &surfaceCapabilities);
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;
createInfo.minImageCount = textureCount;
createInfo.imageFormat = pickedSurfaceFormat.format;
createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace;
createInfo.imageExtent.width = width;
createInfo.imageExtent.height = height;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
createInfo.compositeAlpha = pickedAlphaFlag;
createInfo.presentMode = requiredPresentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = vk;
VkResult res = vkCreateSwapchainKHR(commandQueue->device->vk, &createInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateSwapchainKHR failed with error code 0x%X.\n", res);
return false;
}
// Store the chosen present mode to identify later whether the swapchain needs to be recreated.
createdPresentMode = requiredPresentMode;
// Reset present counter.
presentCount = 1;
if (createInfo.oldSwapchain != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(commandQueue->device->vk, createInfo.oldSwapchain, nullptr);
}
uint32_t retrievedImageCount = 0;
vkGetSwapchainImagesKHR(commandQueue->device->vk, vk, &retrievedImageCount, nullptr);
if (retrievedImageCount < textureCount) {
releaseSwapChain();
fprintf(stderr, "Image count differs from the texture count.\n");
return false;
}
textureCount = retrievedImageCount;
std::vector<VkImage> images(textureCount);
res = vkGetSwapchainImagesKHR(commandQueue->device->vk, vk, &textureCount, images.data());
if (res != VK_SUCCESS) {
releaseSwapChain();
fprintf(stderr, "vkGetSwapchainImagesKHR failed with error code 0x%X.\n", res);
return false;
}
// Assign the swap chain images to the buffer resources.
textures.resize(textureCount);
for (uint32_t i = 0; i < textureCount; i++) {
textures[i] = VulkanTexture(commandQueue->device, images[i]);
textures[i].desc.dimension = RenderTextureDimension::TEXTURE_2D;
textures[i].desc.format = format;
textures[i].desc.width = width;
textures[i].desc.height = height;
textures[i].desc.depth = 1;
textures[i].desc.mipLevels = 1;
textures[i].desc.arraySize = 1;
textures[i].desc.flags = RenderTextureFlag::RENDER_TARGET;
textures[i].fillSubresourceRange();
textures[i].createImageView(pickedSurfaceFormat.format);
}
return true;
}
bool VulkanSwapChain::needsResize() const {
uint32_t windowWidth, windowHeight;
getWindowSize(windowWidth, windowHeight);
return (vk == VK_NULL_HANDLE) || (windowWidth != width) || (windowHeight != height) || (requiredPresentMode != createdPresentMode);
}
void VulkanSwapChain::setVsyncEnabled(bool vsyncEnabled) {
// Immediate mode must be supported and the presentation mode will only be used on the next resize.
// needsResize() will return as true as long as the created and required present mode do not match.
if (immediatePresentModeSupported) {
requiredPresentMode = vsyncEnabled ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR;
}
}
bool VulkanSwapChain::isVsyncEnabled() const {
return createdPresentMode == VK_PRESENT_MODE_FIFO_KHR;
}
uint32_t VulkanSwapChain::getWidth() const {
return width;
}
uint32_t VulkanSwapChain::getHeight() const {
return height;
}
RenderTexture *VulkanSwapChain::getTexture(uint32_t textureIndex) {
return &textures[textureIndex];
}
uint32_t VulkanSwapChain::getTextureCount() const {
return textureCount;
}
RenderWindow VulkanSwapChain::getWindow() const {
return renderWindow;
}
bool VulkanSwapChain::isEmpty() const {
return (vk == VK_NULL_HANDLE) || (width == 0) || (height == 0);
}
uint32_t VulkanSwapChain::getRefreshRate() const {
VkRefreshCycleDurationGOOGLE refreshCycle = {};
VkResult res = vkGetRefreshCycleDurationGOOGLE(commandQueue->device->vk, vk, &refreshCycle);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkGetRefreshCycleDurationGOOGLE failed with error code 0x%X.\n", res);
return 0;
}
return std::lround(1000000000.0 / refreshCycle.refreshDuration);
}
void VulkanSwapChain::getWindowSize(uint32_t &dstWidth, uint32_t &dstHeight) const {
# if defined(_WIN64)
RECT rect;
GetClientRect(renderWindow, &rect);
dstWidth = rect.right - rect.left;
dstHeight = rect.bottom - rect.top;
# elif defined(SDL_VULKAN_ENABLED)
SDL_GetWindowSizeInPixels(renderWindow, (int *)(&dstWidth), (int *)(&dstHeight));
# elif defined(__ANDROID__)
dstWidth = ANativeWindow_getWidth(renderWindow);
dstHeight = ANativeWindow_getHeight(renderWindow);
# elif defined(__linux__)
XWindowAttributes attributes;
XGetWindowAttributes(renderWindow.display, renderWindow.window, &attributes);
// The attributes width and height members do not include the border.
dstWidth = attributes.width;
dstHeight = attributes.height;
# elif defined(__APPLE__)
SDL_GetWindowSizeInPixels(renderWindow.window, (int *)(&dstWidth), (int *)(&dstHeight));
# endif
}
bool VulkanSwapChain::acquireTexture(RenderCommandSemaphore *signalSemaphore, uint32_t *textureIndex) {
assert(signalSemaphore != nullptr);
VulkanCommandSemaphore *interfaceSemaphore = static_cast<VulkanCommandSemaphore *>(signalSemaphore);
VkResult res = vkAcquireNextImageKHR(commandQueue->device->vk, vk, UINT64_MAX, interfaceSemaphore->vk, VK_NULL_HANDLE, textureIndex);
if ((res != VK_SUCCESS) && (res != VK_SUBOPTIMAL_KHR)) {
return false;
}
return true;
}
void VulkanSwapChain::releaseSwapChain() {
if (vk != VK_NULL_HANDLE) {
vkDestroySwapchainKHR(commandQueue->device->vk, vk, nullptr);
vk = VK_NULL_HANDLE;
}
}
void VulkanSwapChain::releaseImageViews() {
for (VulkanTexture &texture : textures) {
if (texture.imageView != VK_NULL_HANDLE) {
vkDestroyImageView(commandQueue->device->vk, texture.imageView, nullptr);
texture.imageView = VK_NULL_HANDLE;
}
}
}
// VulkanFramebuffer
VulkanFramebuffer::VulkanFramebuffer(VulkanDevice *device, const RenderFramebufferDesc &desc) {
assert(device != nullptr);
this->device = device;
depthAttachmentReadOnly = desc.depthAttachmentReadOnly;
VkResult res;
std::vector<VkAttachmentDescription> attachments;
std::vector<VkAttachmentReference> colorReferences;
std::vector<VkImageView> imageViews;
VkAttachmentReference depthReference = {};
for (uint32_t i = 0; i < desc.colorAttachmentsCount; i++) {
const VulkanTexture *colorAttachment = static_cast<const VulkanTexture *>(desc.colorAttachments[i]);
assert((colorAttachment->desc.flags & RenderTextureFlag::RENDER_TARGET) && "Color attachment must be a render target.");
colorAttachments.emplace_back(colorAttachment);
imageViews.emplace_back(colorAttachment->imageView);
if (i == 0) {
width = uint32_t(colorAttachment->desc.width);
height = colorAttachment->desc.height;
}
VkAttachmentReference reference = {};
reference.attachment = uint32_t(attachments.size());
reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorReferences.emplace_back(reference);
VkAttachmentDescription attachment = {};
attachment.format = toVk(colorAttachment->desc.format);
attachment.samples = VkSampleCountFlagBits(colorAttachment->desc.multisampling.sampleCount);
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments.emplace_back(attachment);
}
if (desc.depthAttachment != nullptr) {
depthAttachment = static_cast<const VulkanTexture *>(desc.depthAttachment);
assert((depthAttachment->desc.flags & RenderTextureFlag::DEPTH_TARGET) && "Depth attachment must be a depth target.");
imageViews.emplace_back(depthAttachment->imageView);
if (desc.colorAttachmentsCount == 0) {
width = uint32_t(depthAttachment->desc.width);
height = depthAttachment->desc.height;
}
depthReference.attachment = uint32_t(attachments.size());
depthReference.layout = desc.depthAttachmentReadOnly ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
// Upgrade the operations to NONE if supported. Fixes the following validation issue: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/2349
// We prefer to just ignore this potential hazard on older Vulkan versions as it just seems to be an edge case for some hardware.
const bool preferNoneForReadOnly = desc.depthAttachmentReadOnly && device->loadStoreOpNoneSupported;
VkAttachmentDescription attachment = {};
attachment.format = toVk(depthAttachment->desc.format);
attachment.samples = VkSampleCountFlagBits(depthAttachment->desc.multisampling.sampleCount);
attachment.loadOp = preferNoneForReadOnly ? VK_ATTACHMENT_LOAD_OP_NONE_EXT : VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.storeOp = preferNoneForReadOnly ? VK_ATTACHMENT_STORE_OP_NONE_EXT : VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = attachment.loadOp;
attachment.stencilStoreOp = attachment.storeOp;
attachment.initialLayout = depthReference.layout;
attachment.finalLayout = depthReference.layout;
attachments.emplace_back(attachment);
}
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.pColorAttachments = !colorReferences.empty() ? colorReferences.data() : nullptr;
subpass.colorAttachmentCount = uint32_t(colorReferences.size());
if (desc.depthAttachment != nullptr) {
subpass.pDepthStencilAttachment = &depthReference;
}
VkRenderPassCreateInfo passInfo = {};
passInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
passInfo.pAttachments = attachments.data();
passInfo.attachmentCount = uint32_t(attachments.size());
passInfo.pSubpasses = &subpass;
passInfo.subpassCount = 1;
res = vkCreateRenderPass(device->vk, &passInfo, nullptr, &renderPass);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateRenderPass failed with error code 0x%X.\n", res);
return;
}
VkFramebufferCreateInfo fbInfo = {};
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fbInfo.renderPass = renderPass;
fbInfo.pAttachments = imageViews.data();
fbInfo.attachmentCount = uint32_t(imageViews.size());
fbInfo.width = width;
fbInfo.height = height;
fbInfo.layers = 1;
res = vkCreateFramebuffer(device->vk, &fbInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateFramebuffer failed with error code 0x%X.\n", res);
return;
}
}
VulkanFramebuffer::~VulkanFramebuffer() {
if (vk != VK_NULL_HANDLE) {
vkDestroyFramebuffer(device->vk, vk, nullptr);
}
if (renderPass != VK_NULL_HANDLE) {
vkDestroyRenderPass(device->vk, renderPass, nullptr);
}
}
uint32_t VulkanFramebuffer::getWidth() const {
return width;
}
uint32_t VulkanFramebuffer::getHeight() const {
return height;
}
bool VulkanFramebuffer::contains(const VulkanTexture *attachment) const {
assert(attachment != nullptr);
for (uint32_t i = 0; i < colorAttachments.size(); i++) {
if (colorAttachments[i] == attachment) {
return true;
}
}
return (depthAttachment == attachment);
}
// VulkanQueryPool
VulkanQueryPool::VulkanQueryPool(VulkanDevice *device, uint32_t queryCount) {
assert(device != nullptr);
assert(queryCount > 0);
this->device = device;
VkQueryPoolCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
createInfo.queryType = VK_QUERY_TYPE_TIMESTAMP;
createInfo.queryCount = queryCount;
VkResult res = vkCreateQueryPool(device->vk, &createInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateQueryPool failed with error code 0x%X.\n", res);
return;
}
results.resize(queryCount);
}
VulkanQueryPool::~VulkanQueryPool() {
vkDestroyQueryPool(device->vk, vk, nullptr);
}
void VulkanQueryPool::queryResults() {
VkResult res = vkGetQueryPoolResults(device->vk, vk, 0, uint32_t(results.size()), sizeof(uint64_t) * results.size(), results.data(), sizeof(uint64_t), VK_QUERY_RESULT_64_BIT);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkGetQueryPoolResults failed with error code 0x%X.\n", res);
return;
}
// Conversion sourced from Godot Engine's Vulkan Rendering Driver.
auto mult64to128 = [](uint64_t u, uint64_t v, uint64_t &h, uint64_t &l) {
uint64_t u1 = (u & 0xffffffff);
uint64_t v1 = (v & 0xffffffff);
uint64_t t = (u1 * v1);
uint64_t w3 = (t & 0xffffffff);
uint64_t k = (t >> 32);
u >>= 32;
t = (u * v1) + k;
k = (t & 0xffffffff);
uint64_t w1 = (t >> 32);
v >>= 32;
t = (u1 * v) + k;
k = (t >> 32);
h = (u * v) + w1 + k;
l = (t << 32) + w3;
};
// Convert results to timestamps.
constexpr uint64_t shift_bits = 16;
double timestampPeriod = double(device->physicalDeviceProperties.limits.timestampPeriod);
uint64_t h = 0, l = 0;
for (uint64_t &result : results) {
mult64to128(result, uint64_t(timestampPeriod * double(1 << shift_bits)), h, l);
result = l;
result >>= shift_bits;
result |= h << (64 - shift_bits);
}
}
const uint64_t *VulkanQueryPool::getResults() const {
return results.data();
}
uint32_t VulkanQueryPool::getCount() const {
return uint32_t(results.size());
}
// VulkanCommandList
VulkanCommandList::VulkanCommandList(VulkanDevice *device, RenderCommandListType type) {
assert(device != nullptr);
assert(type != RenderCommandListType::UNKNOWN);
this->device = device;
this->type = type;
VkCommandPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = device->queueFamilyIndices[toFamilyIndex(type)];
VkResult res = vkCreateCommandPool(device->vk, &poolInfo, nullptr, &commandPool);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateCommandPool failed with error code 0x%X.\n", res);
return;
}
VkCommandBufferAllocateInfo allocateInfo = {};
allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocateInfo.commandPool = commandPool;
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocateInfo.commandBufferCount = 1;
res = vkAllocateCommandBuffers(device->vk, &allocateInfo, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkAllocateCommandBuffers failed with error code 0x%X.\n", res);
return;
}
}
VulkanCommandList::~VulkanCommandList() {
if (vk != VK_NULL_HANDLE) {
vkFreeCommandBuffers(device->vk, commandPool, 1, &vk);
}
if (commandPool != VK_NULL_HANDLE) {
vkDestroyCommandPool(device->vk, commandPool, nullptr);
}
}
void VulkanCommandList::begin() {
vkResetCommandBuffer(vk, 0);
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
VkResult res = vkBeginCommandBuffer(vk, &beginInfo);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkBeginCommandBuffer failed with error code 0x%X.\n", res);
return;
}
}
void VulkanCommandList::end() {
endActiveRenderPass();
VkResult res = vkEndCommandBuffer(vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkEndCommandBuffer failed with error code 0x%X.\n", res);
return;
}
targetFramebuffer = nullptr;
activeComputePipelineLayout = nullptr;
activeGraphicsPipelineLayout = nullptr;
activeRaytracingPipelineLayout = nullptr;
}
void VulkanCommandList::barriers(RenderBarrierStages stages, const RenderBufferBarrier *bufferBarriers, uint32_t bufferBarriersCount, const RenderTextureBarrier *textureBarriers, uint32_t textureBarriersCount) {
assert((bufferBarriersCount == 0) || (bufferBarriers != nullptr));
assert((textureBarriersCount == 0) || (textureBarriers != nullptr));
if ((bufferBarriersCount == 0) && (textureBarriersCount == 0)) {
return;
}
endActiveRenderPass();
const bool geometryEnabled = device->capabilities.geometryShader;
const bool rtEnabled = device->capabilities.raytracing;
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT | toStageFlags(stages, geometryEnabled, rtEnabled);
thread_local std::vector<VkBufferMemoryBarrier> bufferMemoryBarriers;
thread_local std::vector<VkImageMemoryBarrier> imageMemoryBarriers;
bufferMemoryBarriers.clear();
imageMemoryBarriers.clear();
for (uint32_t i = 0; i < bufferBarriersCount; i++) {
const RenderBufferBarrier &bufferBarrier = bufferBarriers[i];
VulkanBuffer *interfaceBuffer = static_cast<VulkanBuffer *>(bufferBarrier.buffer);
VkBufferMemoryBarrier bufferMemoryBarrier = {};
bufferMemoryBarrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
bufferMemoryBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; // TODO
bufferMemoryBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; // TODO
bufferMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
bufferMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
bufferMemoryBarrier.buffer = interfaceBuffer->vk;
bufferMemoryBarrier.offset = 0;
bufferMemoryBarrier.size = interfaceBuffer->desc.size;
bufferMemoryBarriers.emplace_back(bufferMemoryBarrier);
srcStageMask |= toStageFlags(interfaceBuffer->barrierStages, geometryEnabled, rtEnabled);
interfaceBuffer->barrierStages = stages;
}
for (uint32_t i = 0; i < textureBarriersCount; i++) {
const RenderTextureBarrier &textureBarrier = textureBarriers[i];
VulkanTexture *interfaceTexture = static_cast<VulkanTexture *>(textureBarrier.texture);
VkImageMemoryBarrier imageMemoryBarrier = {};
imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
imageMemoryBarrier.image = interfaceTexture->vk;
imageMemoryBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; // TODO
imageMemoryBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; // TODO
imageMemoryBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imageMemoryBarrier.oldLayout = toImageLayout(interfaceTexture->textureLayout);
imageMemoryBarrier.newLayout = toImageLayout(textureBarrier.layout);
imageMemoryBarrier.subresourceRange.levelCount = interfaceTexture->desc.mipLevels;
imageMemoryBarrier.subresourceRange.layerCount = interfaceTexture->desc.arraySize;
imageMemoryBarrier.subresourceRange.aspectMask = (interfaceTexture->desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
imageMemoryBarriers.emplace_back(imageMemoryBarrier);
srcStageMask |= toStageFlags(interfaceTexture->barrierStages, geometryEnabled, rtEnabled);
interfaceTexture->textureLayout = textureBarrier.layout;
interfaceTexture->barrierStages = stages;
}
if (bufferMemoryBarriers.empty() && imageMemoryBarriers.empty()) {
return;
}
vkCmdPipelineBarrier(vk, srcStageMask, dstStageMask, 0, 0, nullptr, uint32_t(bufferMemoryBarriers.size()), bufferMemoryBarriers.data(), uint32_t(imageMemoryBarriers.size()), imageMemoryBarriers.data());
}
void VulkanCommandList::dispatch(uint32_t threadGroupCountX, uint32_t threadGroupCountY, uint32_t threadGroupCountZ) {
vkCmdDispatch(vk, threadGroupCountX, threadGroupCountY, threadGroupCountZ);
}
void VulkanCommandList::traceRays(uint32_t width, uint32_t height, uint32_t depth, RenderBufferReference shaderBindingTable, const RenderShaderBindingGroupsInfo &shaderBindingGroupsInfo) {
const VulkanBuffer *interfaceBuffer = static_cast<const VulkanBuffer *>(shaderBindingTable.ref);
assert(interfaceBuffer != nullptr);
assert((interfaceBuffer->desc.flags & RenderBufferFlag::SHADER_BINDING_TABLE) && "Buffer must allow being used as a shader binding table.");
VkBufferDeviceAddressInfo tableAddressInfo = {};
tableAddressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
tableAddressInfo.buffer = interfaceBuffer->vk;
const VkDeviceAddress tableAddress = vkGetBufferDeviceAddress(device->vk, &tableAddressInfo) + shaderBindingTable.offset;
const RenderShaderBindingGroupInfo &rayGen = shaderBindingGroupsInfo.rayGen;
const RenderShaderBindingGroupInfo &miss = shaderBindingGroupsInfo.miss;
const RenderShaderBindingGroupInfo &hitGroup = shaderBindingGroupsInfo.hitGroup;
const RenderShaderBindingGroupInfo &callable = shaderBindingGroupsInfo.callable;
VkStridedDeviceAddressRegionKHR rayGenSbt = {};
VkStridedDeviceAddressRegionKHR missSbt = {};
VkStridedDeviceAddressRegionKHR hitSbt = {};
VkStridedDeviceAddressRegionKHR callableSbt = {};
rayGenSbt.deviceAddress = (rayGen.size > 0) ? (tableAddress + rayGen.offset + rayGen.startIndex * rayGen.stride) : 0;
rayGenSbt.size = rayGen.stride; // RayGen is a special case where the size must be the same as the stride.
rayGenSbt.stride = rayGen.stride;
missSbt.deviceAddress = (miss.size > 0) ? (tableAddress + miss.offset + miss.startIndex * miss.stride) : 0;
missSbt.size = miss.size;
missSbt.stride = miss.stride;
hitSbt.deviceAddress = (hitGroup.size > 0) ? (tableAddress + hitGroup.offset + hitGroup.startIndex * hitGroup.stride) : 0;
hitSbt.size = hitGroup.size;
hitSbt.stride = hitGroup.stride;
callableSbt.deviceAddress = (callable.size > 0) ? (tableAddress + callable.offset + callable.startIndex * callable.stride) : 0;
callableSbt.size = callable.size;
callableSbt.stride = callable.stride;
vkCmdTraceRaysKHR(vk, &rayGenSbt, &missSbt, &hitSbt, &callableSbt, width, height, depth);
}
void VulkanCommandList::drawInstanced(uint32_t vertexCountPerInstance, uint32_t instanceCount, uint32_t startVertexLocation, uint32_t startInstanceLocation) {
checkActiveRenderPass();
vkCmdDraw(vk, vertexCountPerInstance, instanceCount, startVertexLocation, startInstanceLocation);
}
void VulkanCommandList::drawIndexedInstanced(uint32_t indexCountPerInstance, uint32_t instanceCount, uint32_t startIndexLocation, int32_t baseVertexLocation, uint32_t startInstanceLocation) {
checkActiveRenderPass();
vkCmdDrawIndexed(vk, indexCountPerInstance, instanceCount, startIndexLocation, baseVertexLocation, startInstanceLocation);
}
void VulkanCommandList::setPipeline(const RenderPipeline *pipeline) {
assert(pipeline != nullptr);
const VulkanPipeline *interfacePipeline = static_cast<const VulkanPipeline *>(pipeline);
switch (interfacePipeline->type) {
case VulkanPipeline::Type::Compute: {
const VulkanComputePipeline *computePipeline = static_cast<const VulkanComputePipeline *>(interfacePipeline);
vkCmdBindPipeline(vk, VK_PIPELINE_BIND_POINT_COMPUTE, computePipeline->vk);
break;
}
case VulkanPipeline::Type::Graphics: {
const VulkanGraphicsPipeline *graphicsPipeline = static_cast<const VulkanGraphicsPipeline *>(interfacePipeline);
vkCmdBindPipeline(vk, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline->vk);
break;
}
case VulkanPipeline::Type::Raytracing: {
const VulkanRaytracingPipeline *raytracingPipeline = static_cast<const VulkanRaytracingPipeline *>(interfacePipeline);
vkCmdBindPipeline(vk, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, raytracingPipeline->vk);
break;
}
default:
assert(false && "Unknown pipeline type.");
break;
}
}
void VulkanCommandList::setComputePipelineLayout(const RenderPipelineLayout *pipelineLayout) {
assert(pipelineLayout != nullptr);
activeComputePipelineLayout = static_cast<const VulkanPipelineLayout *>(pipelineLayout);
}
void VulkanCommandList::setComputePushConstants(uint32_t rangeIndex, const void *data, uint32_t offset, uint32_t size) {
assert(activeComputePipelineLayout != nullptr);
assert(rangeIndex < activeComputePipelineLayout->pushConstantRanges.size());
const VkPushConstantRange &range = activeComputePipelineLayout->pushConstantRanges[rangeIndex];
vkCmdPushConstants(vk, activeComputePipelineLayout->vk, range.stageFlags & VK_SHADER_STAGE_COMPUTE_BIT, range.offset + offset, size == 0 ? range.size : size, data);
}
void VulkanCommandList::setComputeDescriptorSet(RenderDescriptorSet *descriptorSet, uint32_t setIndex) {
setDescriptorSet(VK_PIPELINE_BIND_POINT_COMPUTE, activeComputePipelineLayout, descriptorSet, setIndex);
}
void VulkanCommandList::setGraphicsPipelineLayout(const RenderPipelineLayout *pipelineLayout) {
assert(pipelineLayout != nullptr);
activeGraphicsPipelineLayout = static_cast<const VulkanPipelineLayout *>(pipelineLayout);
}
void VulkanCommandList::setGraphicsPushConstants(uint32_t rangeIndex, const void *data, uint32_t offset, uint32_t size) {
assert(activeGraphicsPipelineLayout != nullptr);
assert(rangeIndex < activeGraphicsPipelineLayout->pushConstantRanges.size());
const VkPushConstantRange &range = activeGraphicsPipelineLayout->pushConstantRanges[rangeIndex];
vkCmdPushConstants(vk, activeGraphicsPipelineLayout->vk, range.stageFlags & VK_SHADER_STAGE_ALL_GRAPHICS, range.offset + offset, size == 0 ? range.size : size, data);
}
void VulkanCommandList::setGraphicsDescriptorSet(RenderDescriptorSet *descriptorSet, uint32_t setIndex) {
setDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, activeGraphicsPipelineLayout, descriptorSet, setIndex);
}
void VulkanCommandList::setGraphicsRootDescriptor(RenderBufferReference bufferReference, uint32_t rootDescriptorIndex) {
assert(false && "Root descriptors are not supported in Vulkan.");
}
void VulkanCommandList::setRaytracingPipelineLayout(const RenderPipelineLayout *pipelineLayout) {
assert(pipelineLayout != nullptr);
activeRaytracingPipelineLayout = static_cast<const VulkanPipelineLayout *>(pipelineLayout);
}
void VulkanCommandList::setRaytracingPushConstants(uint32_t rangeIndex, const void *data, uint32_t offset, uint32_t size) {
assert(activeRaytracingPipelineLayout != nullptr);
assert(rangeIndex < activeRaytracingPipelineLayout->pushConstantRanges.size());
const VkPushConstantRange &range = activeRaytracingPipelineLayout->pushConstantRanges[rangeIndex];
const VkShaderStageFlags raytracingStageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR |
VK_SHADER_STAGE_MISS_BIT_KHR | VK_SHADER_STAGE_INTERSECTION_BIT_KHR | VK_SHADER_STAGE_CALLABLE_BIT_KHR;
vkCmdPushConstants(vk, activeRaytracingPipelineLayout->vk, range.stageFlags & raytracingStageFlags, range.offset + offset, size == 0 ? range.size : size, data);
}
void VulkanCommandList::setRaytracingDescriptorSet(RenderDescriptorSet *descriptorSet, uint32_t setIndex) {
setDescriptorSet(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, activeRaytracingPipelineLayout, descriptorSet, setIndex);
}
void VulkanCommandList::setIndexBuffer(const RenderIndexBufferView *view) {
if (view != nullptr) {
const VulkanBuffer *interfaceBuffer = static_cast<const VulkanBuffer *>(view->buffer.ref);
vkCmdBindIndexBuffer(vk, (interfaceBuffer != nullptr) ? interfaceBuffer->vk : VK_NULL_HANDLE, view->buffer.offset, toIndexType(view->format));
}
}
void VulkanCommandList::setVertexBuffers(uint32_t startSlot, const RenderVertexBufferView *views, uint32_t viewCount, const RenderInputSlot *inputSlots) {
if ((views != nullptr) && (viewCount > 0)) {
// Input slots aren't actually used by Vulkan as the stride is baked into the pipeline, but we validate it for the sake of consistency with D3D12.
assert(inputSlots != nullptr);
thread_local std::vector<VkBuffer> bufferVector;
thread_local std::vector<VkDeviceSize> offsetVector;
bufferVector.clear();
offsetVector.clear();
for (uint32_t i = 0; i < viewCount; i++) {
const VulkanBuffer *interfaceBuffer = static_cast<const VulkanBuffer *>(views[i].buffer.ref);
if (interfaceBuffer == nullptr && !device->nullDescriptorSupported) {
interfaceBuffer = static_cast<const VulkanBuffer *>(device->nullBuffer.get());
}
bufferVector.emplace_back((interfaceBuffer != nullptr) ? interfaceBuffer->vk : VK_NULL_HANDLE);
offsetVector.emplace_back(views[i].buffer.offset);
}
vkCmdBindVertexBuffers(vk, startSlot, viewCount, bufferVector.data(), offsetVector.data());
}
}
void VulkanCommandList::setViewports(const RenderViewport *viewports, uint32_t count) {
if (count > 1) {
thread_local std::vector<VkViewport> viewportVector;
viewportVector.clear();
for (uint32_t i = 0; i < count; i++) {
viewportVector.emplace_back(VkViewport{ viewports[i].x, viewports[i].y, viewports[i].width, viewports[i].height, viewports[i].minDepth, viewports[i].maxDepth });
}
if (!viewportVector.empty()) {
vkCmdSetViewport(vk, 0, uint32_t(viewportVector.size()), viewportVector.data());
}
}
else {
// Single element fast path.
VkViewport viewport = VkViewport{ viewports[0].x, viewports[0].y, viewports[0].width, viewports[0].height, viewports[0].minDepth, viewports[0].maxDepth };
vkCmdSetViewport(vk, 0, 1, &viewport);
}
}
void VulkanCommandList::setScissors(const RenderRect *scissorRects, uint32_t count) {
if (count > 1) {
thread_local std::vector<VkRect2D> scissorVector;
scissorVector.clear();
for (uint32_t i = 0; i < count; i++) {
scissorVector.emplace_back(VkRect2D{ VkOffset2D{ scissorRects[i].left, scissorRects[i].top }, VkExtent2D{ uint32_t(scissorRects[i].right - scissorRects[i].left), uint32_t(scissorRects[i].bottom - scissorRects[i].top) } });
}
if (!scissorVector.empty()) {
vkCmdSetScissor(vk, 0, uint32_t(scissorVector.size()), scissorVector.data());
}
}
else {
// Single element fast path.
VkRect2D scissor = VkRect2D{ VkOffset2D{ scissorRects[0].left, scissorRects[0].top }, VkExtent2D{ uint32_t(scissorRects[0].right - scissorRects[0].left), uint32_t(scissorRects[0].bottom - scissorRects[0].top) } };
vkCmdSetScissor(vk, 0, 1, &scissor);
}
}
void VulkanCommandList::setFramebuffer(const RenderFramebuffer *framebuffer) {
endActiveRenderPass();
if (framebuffer != nullptr) {
const VulkanFramebuffer *interfaceFramebuffer = static_cast<const VulkanFramebuffer *>(framebuffer);
targetFramebuffer = interfaceFramebuffer;
}
else {
targetFramebuffer = nullptr;
}
}
void VulkanCommandList::setDepthBias(float depthBias, float depthBiasClamp, float slopeScaledDepthBias) {
vkCmdSetDepthBias(vk, depthBias, depthBiasClamp, slopeScaledDepthBias);
}
static void clearCommonRectVector(uint32_t width, uint32_t height, const RenderRect *clearRects, uint32_t clearRectsCount, std::vector<VkClearRect> &rectVector) {
rectVector.clear();
if (clearRectsCount > 0) {
for (uint32_t i = 0; i < clearRectsCount; i++) {
VkClearRect clearRect;
clearRect.rect.offset.x = clearRects[i].left;
clearRect.rect.offset.y = clearRects[i].top;
clearRect.rect.extent.width = clearRects[i].right - clearRects[i].left;
clearRect.rect.extent.height = clearRects[i].bottom - clearRects[i].top;
clearRect.baseArrayLayer = 0;
clearRect.layerCount = 1;
rectVector.emplace_back(clearRect);
}
}
else {
VkClearRect clearRect;
clearRect.rect.offset.x = 0;
clearRect.rect.offset.y = 0;
clearRect.rect.extent.width = width;
clearRect.rect.extent.height = height;
clearRect.baseArrayLayer = 0;
clearRect.layerCount = 1;
rectVector.emplace_back(clearRect);
}
}
void VulkanCommandList::clearColor(uint32_t attachmentIndex, RenderColor colorValue, const RenderRect *clearRects, uint32_t clearRectsCount) {
assert(targetFramebuffer != nullptr);
assert(attachmentIndex < targetFramebuffer->colorAttachments.size());
assert((clearRectsCount == 0) || (clearRects != nullptr));
checkActiveRenderPass();
thread_local std::vector<VkClearRect> rectVector;
clearCommonRectVector(targetFramebuffer->getWidth(), targetFramebuffer->getHeight(), clearRects, clearRectsCount, rectVector);
VkClearAttachment attachment = {};
auto &rgba = attachment.clearValue.color.float32;
rgba[0] = colorValue.r;
rgba[1] = colorValue.g;
rgba[2] = colorValue.b;
rgba[3] = colorValue.a;
attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
attachment.colorAttachment = attachmentIndex;
vkCmdClearAttachments(vk, 1, &attachment, uint32_t(rectVector.size()), rectVector.data());
}
void VulkanCommandList::clearDepth(bool clearDepth, float depthValue, const RenderRect *clearRects, uint32_t clearRectsCount) {
assert(targetFramebuffer != nullptr);
assert((clearRectsCount == 0) || (clearRects != nullptr));
checkActiveRenderPass();
thread_local std::vector<VkClearRect> rectVector;
clearCommonRectVector(targetFramebuffer->getWidth(), targetFramebuffer->getHeight(), clearRects, clearRectsCount, rectVector);
VkClearAttachment attachment = {};
attachment.clearValue.depthStencil.depth = depthValue;
if (clearDepth) {
attachment.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
}
vkCmdClearAttachments(vk, 1, &attachment, uint32_t(rectVector.size()), rectVector.data());
}
void VulkanCommandList::copyBufferRegion(RenderBufferReference dstBuffer, RenderBufferReference srcBuffer, uint64_t size) {
endActiveRenderPass();
assert(dstBuffer.ref != nullptr);
assert(srcBuffer.ref != nullptr);
const VulkanBuffer *interfaceDstBuffer = static_cast<const VulkanBuffer *>(dstBuffer.ref);
const VulkanBuffer *interfaceSrcBuffer = static_cast<const VulkanBuffer *>(srcBuffer.ref);
VkBufferCopy bufferCopy = {};
bufferCopy.dstOffset = dstBuffer.offset;
bufferCopy.srcOffset = srcBuffer.offset;
bufferCopy.size = size;
vkCmdCopyBuffer(vk, interfaceSrcBuffer->vk, interfaceDstBuffer->vk, 1, &bufferCopy);
}
void VulkanCommandList::copyTextureRegion(const RenderTextureCopyLocation &dstLocation, const RenderTextureCopyLocation &srcLocation, uint32_t dstX, uint32_t dstY, uint32_t dstZ, const RenderBox *srcBox) {
endActiveRenderPass();
assert(dstLocation.type != RenderTextureCopyType::UNKNOWN);
assert(srcLocation.type != RenderTextureCopyType::UNKNOWN);
const VulkanTexture *dstTexture = static_cast<const VulkanTexture *>(dstLocation.texture);
const VulkanTexture *srcTexture = static_cast<const VulkanTexture *>(srcLocation.texture);
const VulkanBuffer *dstBuffer = static_cast<const VulkanBuffer *>(dstLocation.buffer);
const VulkanBuffer *srcBuffer = static_cast<const VulkanBuffer *>(srcLocation.buffer);
if ((dstLocation.type == RenderTextureCopyType::SUBRESOURCE) && (srcLocation.type == RenderTextureCopyType::PLACED_FOOTPRINT)) {
assert(dstTexture != nullptr);
assert(srcBuffer != nullptr);
const uint32_t blockWidth = RenderFormatBlockWidth(dstTexture->desc.format);
VkBufferImageCopy imageCopy = {};
imageCopy.bufferOffset = srcLocation.placedFootprint.offset;
imageCopy.bufferRowLength = ((srcLocation.placedFootprint.rowWidth + blockWidth - 1) / blockWidth) * blockWidth;
imageCopy.bufferImageHeight = ((srcLocation.placedFootprint.height + blockWidth - 1) / blockWidth) * blockWidth;
imageCopy.imageSubresource.aspectMask = (dstTexture->desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
imageCopy.imageSubresource.baseArrayLayer = dstLocation.subresource.index / dstTexture->desc.mipLevels;
imageCopy.imageSubresource.layerCount = 1;
imageCopy.imageSubresource.mipLevel = dstLocation.subresource.index % dstTexture->desc.mipLevels;
imageCopy.imageOffset.x = dstX;
imageCopy.imageOffset.y = dstY;
imageCopy.imageOffset.z = dstZ;
imageCopy.imageExtent.width = srcLocation.placedFootprint.width;
imageCopy.imageExtent.height = srcLocation.placedFootprint.height;
imageCopy.imageExtent.depth = srcLocation.placedFootprint.depth;
vkCmdCopyBufferToImage(vk, srcBuffer->vk, dstTexture->vk, toImageLayout(dstTexture->textureLayout), 1, &imageCopy);
}
else {
VkImageCopy imageCopy = {};
imageCopy.srcSubresource.aspectMask = (srcTexture->desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
imageCopy.srcSubresource.baseArrayLayer = 0;
imageCopy.srcSubresource.layerCount = 1;
imageCopy.srcSubresource.mipLevel = srcLocation.subresource.index;
imageCopy.dstSubresource.aspectMask = (dstTexture->desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
imageCopy.dstSubresource.baseArrayLayer = 0;
imageCopy.dstSubresource.layerCount = 1;
imageCopy.dstSubresource.mipLevel = dstLocation.subresource.index;
imageCopy.dstOffset.x = dstX;
imageCopy.dstOffset.y = dstY;
imageCopy.dstOffset.z = dstZ;
if (srcBox != nullptr) {
imageCopy.srcOffset.x = srcBox->left;
imageCopy.srcOffset.y = srcBox->top;
imageCopy.srcOffset.z = srcBox->front;
imageCopy.extent.width = srcBox->right - srcBox->left;
imageCopy.extent.height = srcBox->bottom - srcBox->top;
imageCopy.extent.depth = srcBox->back - srcBox->front;
}
else {
imageCopy.srcOffset.x = 0;
imageCopy.srcOffset.y = 0;
imageCopy.srcOffset.z = 0;
imageCopy.extent.width = srcTexture->desc.width;
imageCopy.extent.height = srcTexture->desc.height;
imageCopy.extent.depth = srcTexture->desc.depth;
}
vkCmdCopyImage(vk, srcTexture->vk, toImageLayout(srcTexture->textureLayout), dstTexture->vk, toImageLayout(dstTexture->textureLayout), 1, &imageCopy);
}
}
void VulkanCommandList::copyBuffer(const RenderBuffer *dstBuffer, const RenderBuffer *srcBuffer) {
endActiveRenderPass();
assert(dstBuffer != nullptr);
assert(srcBuffer != nullptr);
const VulkanBuffer *interfaceDstBuffer = static_cast<const VulkanBuffer *>(dstBuffer);
const VulkanBuffer *interfaceSrcBuffer = static_cast<const VulkanBuffer *>(srcBuffer);
VkBufferCopy bufferCopy = {};
bufferCopy.dstOffset = 0;
bufferCopy.srcOffset = 0;
bufferCopy.size = interfaceDstBuffer->desc.size;
vkCmdCopyBuffer(vk, interfaceSrcBuffer->vk, interfaceDstBuffer->vk, 1, &bufferCopy);
}
void VulkanCommandList::copyTexture(const RenderTexture *dstTexture, const RenderTexture *srcTexture) {
endActiveRenderPass();
assert(dstTexture != nullptr);
assert(srcTexture != nullptr);
thread_local std::vector<VkImageCopy> imageCopies;
imageCopies.clear();
const VulkanTexture *dst = static_cast<const VulkanTexture *>(dstTexture);
const VulkanTexture *src = static_cast<const VulkanTexture *>(srcTexture);
VkImageLayout srcLayout = toImageLayout(src->textureLayout);
VkImageLayout dstLayout = toImageLayout(dst->textureLayout);
VkImageCopy imageCopy = {};
imageCopy.srcSubresource.aspectMask = (src->desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
imageCopy.srcSubresource.baseArrayLayer = 0;
imageCopy.srcSubresource.layerCount = 1;
imageCopy.dstSubresource.aspectMask = (dst->desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
imageCopy.dstSubresource.baseArrayLayer = 0;
imageCopy.dstSubresource.layerCount = 1;
imageCopy.extent.width = uint32_t(dst->desc.width);
imageCopy.extent.height = dst->desc.height;
imageCopy.extent.depth = dst->desc.depth;
assert(dst->desc.mipLevels > 0);
assert(src->desc.mipLevels == dst->desc.mipLevels);
for (uint32_t i = 0; i < dst->desc.mipLevels; i++) {
imageCopy.srcSubresource.mipLevel = i;
imageCopy.dstSubresource.mipLevel = i;
imageCopies.emplace_back(imageCopy);
}
vkCmdCopyImage(vk, src->vk, srcLayout, dst->vk, dstLayout, uint32_t(imageCopies.size()), imageCopies.data());
}
void VulkanCommandList::resolveTexture(const RenderTexture *dstTexture, const RenderTexture *srcTexture) {
resolveTextureRegion(dstTexture, 0, 0, srcTexture, nullptr, RenderResolveMode::AVERAGE);
}
void VulkanCommandList::resolveTextureRegion(const RenderTexture *dstTexture, uint32_t dstX, uint32_t dstY, const RenderTexture *srcTexture, const RenderRect *srcRect, RenderResolveMode resolveMode) {
assert(dstTexture != nullptr);
assert(srcTexture != nullptr);
assert(resolveMode == RenderResolveMode::AVERAGE && "Vulkan only supports AVERAGE resolve mode.");
thread_local std::vector<VkImageResolve> imageResolves;
imageResolves.clear();
const VulkanTexture *dst = static_cast<const VulkanTexture *>(dstTexture);
const VulkanTexture *src = static_cast<const VulkanTexture *>(srcTexture);
VkImageLayout srcLayout = toImageLayout(src->textureLayout);
VkImageLayout dstLayout = toImageLayout(dst->textureLayout);
VkImageResolve imageResolve = {};
imageResolve.srcSubresource.aspectMask = (src->desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
imageResolve.srcSubresource.baseArrayLayer = 0;
imageResolve.srcSubresource.layerCount = 1;
imageResolve.dstOffset.x = dstX;
imageResolve.dstOffset.y = dstY;
imageResolve.dstSubresource.aspectMask = (dst->desc.flags & RenderTextureFlag::DEPTH_TARGET) ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
imageResolve.dstSubresource.baseArrayLayer = 0;
imageResolve.dstSubresource.layerCount = 1;
imageResolve.extent.depth = dst->desc.depth;
if (srcRect != nullptr) {
imageResolve.srcOffset.x = srcRect->left;
imageResolve.srcOffset.y = srcRect->top;
imageResolve.extent.width = (srcRect->right - srcRect->left);
imageResolve.extent.height = (srcRect->bottom - srcRect->top);
}
else {
imageResolve.extent.width = uint32_t(dst->desc.width);
imageResolve.extent.height = dst->desc.height;
}
assert(dst->desc.mipLevels > 0);
assert(src->desc.mipLevels == dst->desc.mipLevels);
for (uint32_t i = 0; i < dst->desc.mipLevels; i++) {
imageResolve.srcSubresource.mipLevel = i;
imageResolve.dstSubresource.mipLevel = i;
imageResolves.emplace_back(imageResolve);
}
vkCmdResolveImage(vk, src->vk, srcLayout, dst->vk, dstLayout, uint32_t(imageResolves.size()), imageResolves.data());
}
void VulkanCommandList::buildBottomLevelAS(const RenderAccelerationStructure *dstAccelerationStructure, RenderBufferReference scratchBuffer, const RenderBottomLevelASBuildInfo &buildInfo) {
assert(dstAccelerationStructure != nullptr);
assert(scratchBuffer.ref != nullptr);
const VulkanAccelerationStructure *interfaceAccelerationStructure = static_cast<const VulkanAccelerationStructure *>(dstAccelerationStructure);
assert(interfaceAccelerationStructure->type == RenderAccelerationStructureType::BOTTOM_LEVEL);
const VulkanBuffer *interfaceScratchBuffer = static_cast<const VulkanBuffer *>(scratchBuffer.ref);
assert((interfaceScratchBuffer->desc.flags & RenderBufferFlag::ACCELERATION_STRUCTURE_SCRATCH) && "Scratch buffer must be allowed.");
VkBufferDeviceAddressInfo scratchAddressInfo = {};
scratchAddressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
scratchAddressInfo.buffer = interfaceScratchBuffer->vk;
VkAccelerationStructureBuildGeometryInfoKHR buildGeometryInfo = {};
buildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
buildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
buildGeometryInfo.flags = toRTASBuildFlags(buildInfo.preferFastBuild, buildInfo.preferFastTrace);
buildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
buildGeometryInfo.dstAccelerationStructure = interfaceAccelerationStructure->vk;
buildGeometryInfo.scratchData.deviceAddress = vkGetBufferDeviceAddress(device->vk, &scratchAddressInfo) + scratchBuffer.offset;
buildGeometryInfo.pGeometries = reinterpret_cast<const VkAccelerationStructureGeometryKHR *>(buildInfo.buildData.data());
buildGeometryInfo.geometryCount = buildInfo.meshCount;
VkAccelerationStructureBuildRangeInfoKHR buildRangeInfo = {};
buildRangeInfo.primitiveCount = buildInfo.primitiveCount;
buildRangeInfo.primitiveOffset = 0;
buildRangeInfo.firstVertex = 0;
buildRangeInfo.transformOffset = 0;
VkAccelerationStructureBuildRangeInfoKHR *buildRangeInfoPtr = &buildRangeInfo;
vkCmdBuildAccelerationStructuresKHR(vk, 1, &buildGeometryInfo, &buildRangeInfoPtr);
}
void VulkanCommandList::buildTopLevelAS(const RenderAccelerationStructure *dstAccelerationStructure, RenderBufferReference scratchBuffer, RenderBufferReference instancesBuffer, const RenderTopLevelASBuildInfo &buildInfo) {
assert(dstAccelerationStructure != nullptr);
assert(scratchBuffer.ref != nullptr);
assert(instancesBuffer.ref != nullptr);
const VulkanAccelerationStructure *interfaceAccelerationStructure = static_cast<const VulkanAccelerationStructure *>(dstAccelerationStructure);
assert(interfaceAccelerationStructure->type == RenderAccelerationStructureType::TOP_LEVEL);
const VulkanBuffer *interfaceScratchBuffer = static_cast<const VulkanBuffer *>(scratchBuffer.ref);
assert((interfaceScratchBuffer->desc.flags & RenderBufferFlag::ACCELERATION_STRUCTURE_SCRATCH) && "Scratch buffer must be allowed.");
VkBufferDeviceAddressInfo scratchAddressInfo = {};
scratchAddressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
scratchAddressInfo.buffer = interfaceScratchBuffer->vk;
const VulkanBuffer *interfaceInstancesBuffer = static_cast<const VulkanBuffer *>(instancesBuffer.ref);
assert((interfaceInstancesBuffer->desc.flags & RenderBufferFlag::ACCELERATION_STRUCTURE_INPUT) && "Acceleration structure input must be allowed.");
VkBufferDeviceAddressInfo instancesAddressInfo = {};
instancesAddressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
instancesAddressInfo.buffer = interfaceInstancesBuffer->vk;
VkAccelerationStructureGeometryKHR topGeometry = {};
topGeometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
topGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
VkAccelerationStructureGeometryInstancesDataKHR &instancesData = topGeometry.geometry.instances;
instancesData.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
instancesData.data.deviceAddress = vkGetBufferDeviceAddress(device->vk, &instancesAddressInfo) + instancesBuffer.offset;
VkAccelerationStructureBuildGeometryInfoKHR buildGeometryInfo = {};
buildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
buildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
buildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
buildGeometryInfo.dstAccelerationStructure = interfaceAccelerationStructure->vk;
buildGeometryInfo.scratchData.deviceAddress = vkGetBufferDeviceAddress(device->vk, &scratchAddressInfo) + scratchBuffer.offset;
buildGeometryInfo.pGeometries = &topGeometry;
buildGeometryInfo.geometryCount = 1;
VkAccelerationStructureBuildRangeInfoKHR buildRangeInfo = {};
buildRangeInfo.primitiveCount = buildInfo.instanceCount;
buildRangeInfo.primitiveOffset = 0;
buildRangeInfo.firstVertex = 0;
buildRangeInfo.transformOffset = 0;
VkAccelerationStructureBuildRangeInfoKHR *buildRangeInfoPtr = &buildRangeInfo;
vkCmdBuildAccelerationStructuresKHR(vk, 1, &buildGeometryInfo, &buildRangeInfoPtr);
}
void VulkanCommandList::discardTexture(const RenderTexture* texture) {
// Not required in Vulkan.
}
void VulkanCommandList::resetQueryPool(const RenderQueryPool *queryPool, uint32_t queryFirstIndex, uint32_t queryCount) {
assert(queryPool != nullptr);
const VulkanQueryPool *interfaceQueryPool = static_cast<const VulkanQueryPool *>(queryPool);
vkCmdResetQueryPool(vk, interfaceQueryPool->vk, queryFirstIndex, queryCount);
}
void VulkanCommandList::writeTimestamp(const RenderQueryPool *queryPool, uint32_t queryIndex) {
assert(queryPool != nullptr);
const VulkanQueryPool *interfaceQueryPool = static_cast<const VulkanQueryPool *>(queryPool);
vkCmdWriteTimestamp(vk, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, interfaceQueryPool->vk, queryIndex);
}
void VulkanCommandList::checkActiveRenderPass() {
assert(targetFramebuffer != nullptr);
if (activeRenderPass == VK_NULL_HANDLE) {
VkRenderPassBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
beginInfo.renderPass = targetFramebuffer->renderPass;
beginInfo.framebuffer = targetFramebuffer->vk;
beginInfo.renderArea.extent.width = targetFramebuffer->width;
beginInfo.renderArea.extent.height = targetFramebuffer->height;
vkCmdBeginRenderPass(vk, &beginInfo, VkSubpassContents::VK_SUBPASS_CONTENTS_INLINE);
activeRenderPass = targetFramebuffer->renderPass;
}
}
void VulkanCommandList::endActiveRenderPass() {
if (activeRenderPass != VK_NULL_HANDLE) {
vkCmdEndRenderPass(vk);
activeRenderPass = VK_NULL_HANDLE;
}
}
void VulkanCommandList::setDescriptorSet(VkPipelineBindPoint bindPoint, const VulkanPipelineLayout *pipelineLayout, const RenderDescriptorSet *descriptorSet, uint32_t setIndex) {
assert(pipelineLayout != nullptr);
assert(descriptorSet != nullptr);
assert(setIndex < pipelineLayout->descriptorSetLayouts.size());
const VulkanDescriptorSet *interfaceSet = static_cast<const VulkanDescriptorSet *>(descriptorSet);
vkCmdBindDescriptorSets(vk, bindPoint, pipelineLayout->vk, setIndex, 1, &interfaceSet->vk, 0, nullptr);
}
// VulkanCommandFence
VulkanCommandFence::VulkanCommandFence(VulkanDevice *device) {
assert(device != nullptr);
this->device = device;
VkFenceCreateInfo fenceInfo = {};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
VkResult res = vkCreateFence(device->vk, &fenceInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateFence failed with error code 0x%X.\n", res);
return;
}
}
VulkanCommandFence::~VulkanCommandFence() {
if (vk != VK_NULL_HANDLE) {
vkDestroyFence(device->vk, vk, nullptr);
}
}
// VulkanCommandSemaphore
VulkanCommandSemaphore::VulkanCommandSemaphore(VulkanDevice *device) {
assert(device != nullptr);
this->device = device;
VkSemaphoreCreateInfo semaphoreInfo = {};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkResult res = vkCreateSemaphore(device->vk, &semaphoreInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateSemaphore failed with error code 0x%X.\n", res);
return;
}
}
VulkanCommandSemaphore::~VulkanCommandSemaphore() {
if (vk != VK_NULL_HANDLE) {
vkDestroySemaphore(device->vk, vk, nullptr);
}
}
// VulkanCommandQueue
VulkanCommandQueue::VulkanCommandQueue(VulkanDevice *device, RenderCommandListType commandListType) {
assert(device != nullptr);
assert(commandListType != RenderCommandListType::UNKNOWN);
this->device = device;
familyIndex = device->queueFamilyIndices[toFamilyIndex(commandListType)];
device->queueFamilies[familyIndex].add(this);
}
VulkanCommandQueue::~VulkanCommandQueue() {
device->queueFamilies[familyIndex].remove(this);
}
std::unique_ptr<RenderSwapChain> VulkanCommandQueue::createSwapChain(RenderWindow renderWindow, uint32_t bufferCount, RenderFormat format, uint32_t maxFrameLatency) {
return std::make_unique<VulkanSwapChain>(this, renderWindow, bufferCount, format, maxFrameLatency);
}
void VulkanCommandQueue::executeCommandLists(const RenderCommandList **commandLists, uint32_t commandListCount, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount, RenderCommandSemaphore **signalSemaphores, uint32_t signalSemaphoreCount, RenderCommandFence *signalFence) {
assert(commandLists != nullptr);
assert(commandListCount > 0);
thread_local std::vector<VkSemaphore> waitSemaphoreVector;
thread_local std::vector<VkSemaphore> signalSemaphoreVector;
thread_local std::vector<VkCommandBuffer> commandBuffers;
waitSemaphoreVector.clear();
signalSemaphoreVector.clear();
commandBuffers.clear();
for (uint32_t i = 0; i < waitSemaphoreCount; i++) {
VulkanCommandSemaphore *interfaceSemaphore = static_cast<VulkanCommandSemaphore *>(waitSemaphores[i]);
waitSemaphoreVector.emplace_back(interfaceSemaphore->vk);
}
for (uint32_t i = 0; i < signalSemaphoreCount; i++) {
VulkanCommandSemaphore *interfaceSemaphore = static_cast<VulkanCommandSemaphore *>(signalSemaphores[i]);
signalSemaphoreVector.emplace_back(interfaceSemaphore->vk);
}
for (uint32_t i = 0; i < commandListCount; i++) {
assert(commandLists[i] != nullptr);
const VulkanCommandList *interfaceCommandList = static_cast<const VulkanCommandList *>(commandLists[i]);
commandBuffers.emplace_back(interfaceCommandList->vk);
}
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pCommandBuffers = commandBuffers.data();
submitInfo.commandBufferCount = uint32_t(commandBuffers.size());
const VkPipelineStageFlags waitStages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
if (!waitSemaphoreVector.empty()) {
submitInfo.pWaitSemaphores = waitSemaphoreVector.data();
submitInfo.waitSemaphoreCount = uint32_t(waitSemaphoreVector.size());
submitInfo.pWaitDstStageMask = &waitStages;
}
if (!signalSemaphoreVector.empty()) {
submitInfo.pSignalSemaphores = signalSemaphoreVector.data();
submitInfo.signalSemaphoreCount = uint32_t(signalSemaphoreVector.size());
}
VkFence submitFence = VK_NULL_HANDLE;
if (signalFence != nullptr) {
VulkanCommandFence *interfaceFence = static_cast<VulkanCommandFence *>(signalFence);
submitFence = interfaceFence->vk;
}
VkResult res;
{
const std::scoped_lock queueLock(*queue->mutex);
res = vkQueueSubmit(queue->vk, 1, &submitInfo, submitFence);
}
if (res != VK_SUCCESS) {
fprintf(stderr, "vkQueueSubmit failed with error code 0x%X.\n", res);
return;
}
}
void VulkanCommandQueue::waitForCommandFence(RenderCommandFence *fence) {
assert(fence != nullptr);
VulkanCommandFence *interfaceFence = static_cast<VulkanCommandFence *>(fence);
VkResult res = vkWaitForFences(device->vk, 1, &interfaceFence->vk, VK_TRUE, UINT64_MAX);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkWaitForFences failed with error code 0x%X.\n", res);
return;
}
vkResetFences(device->vk, 1, &interfaceFence->vk);
}
// VulkanPool
VulkanPool::VulkanPool(VulkanDevice *device, const RenderPoolDesc &desc) {
assert(device != nullptr);
this->device = device;
VmaAllocationCreateInfo memoryInfo = {};
switch (desc.heapType) {
case RenderHeapType::DEFAULT:
memoryInfo.preferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
break;
case RenderHeapType::UPLOAD:
memoryInfo.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
break;
case RenderHeapType::READBACK:
memoryInfo.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
break;
default:
assert(false && "Unknown heap type.");
break;
}
uint32_t memoryTypeIndex = 0;
VkResult res = vmaFindMemoryTypeIndex(device->allocator, UINT32_MAX, &memoryInfo, &memoryTypeIndex);
if (res != VK_SUCCESS) {
fprintf(stderr, "vmaFindMemoryTypeIndex failed with error code 0x%X.\n", res);
return;
}
VmaPoolCreateInfo createInfo = {};
createInfo.memoryTypeIndex = memoryTypeIndex;
createInfo.minBlockCount = desc.minBlockCount;
createInfo.maxBlockCount = desc.maxBlockCount;
createInfo.flags |= desc.useLinearAlgorithm ? VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT : 0;
res = vmaCreatePool(device->allocator, &createInfo, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vmaCreatePool failed with error code 0x%X.\n", res);
return;
}
}
VulkanPool::~VulkanPool() {
if (vk != VK_NULL_HANDLE) {
vmaDestroyPool(device->allocator, vk);
}
}
std::unique_ptr<RenderBuffer> VulkanPool::createBuffer(const RenderBufferDesc &desc) {
return std::make_unique<VulkanBuffer>(device, this, desc);
}
std::unique_ptr<RenderTexture> VulkanPool::createTexture(const RenderTextureDesc &desc) {
return std::make_unique<VulkanTexture>(device, this, desc);
}
// VulkanQueueFamily
void VulkanQueueFamily::add(VulkanCommandQueue *virtualQueue) {
assert(virtualQueue != nullptr);
// Insert virtual queue into the queue with the least amount of virtual queues.
uint32_t queueIndex = 0;
uint32_t lowestCount = UINT_MAX;
for (uint32_t i = 0; i < queues.size(); i++) {
uint32_t virtualQueueCount = uint32_t(queues[i].virtualQueues.size());
if (virtualQueueCount < lowestCount) {
queueIndex = i;
lowestCount = virtualQueueCount;
}
}
if (queues[queueIndex].mutex == nullptr) {
queues[queueIndex].mutex = std::make_unique<std::mutex>();
}
virtualQueue->queue = &queues[queueIndex];
virtualQueue->queueIndex = queueIndex;
queues[queueIndex].virtualQueues.insert(virtualQueue);
}
void VulkanQueueFamily::remove(VulkanCommandQueue *virtualQueue) {
assert(virtualQueue != nullptr);
queues[virtualQueue->queueIndex].virtualQueues.erase(virtualQueue);
}
// VulkanDevice
VulkanDevice::VulkanDevice(VulkanInterface *renderInterface, const std::string &preferredDeviceName) {
assert(renderInterface != nullptr);
this->renderInterface = renderInterface;
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(renderInterface->instance, &deviceCount, nullptr);
if (deviceCount == 0) {
fprintf(stderr, "Unable to find devices that support Vulkan.\n");
return;
}
std::vector<VkPhysicalDevice> physicalDevices(deviceCount);
vkEnumeratePhysicalDevices(renderInterface->instance, &deviceCount, physicalDevices.data());
uint32_t currentDeviceTypeScore = 0;
uint32_t deviceTypeScoreTable[] = {
0, // VK_PHYSICAL_DEVICE_TYPE_OTHER
3, // VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU
4, // VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
2, // VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU
1 // VK_PHYSICAL_DEVICE_TYPE_CPU
};
for (uint32_t i = 0; i < deviceCount; i++) {
VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(physicalDevices[i], &deviceProperties);
uint32_t deviceTypeIndex = deviceProperties.deviceType;
if (deviceTypeIndex > 4) {
continue;
}
std::string deviceName(deviceProperties.deviceName);
uint32_t deviceTypeScore = deviceTypeScoreTable[deviceTypeIndex];
bool preferDeviceTypeScore = (deviceTypeScore > currentDeviceTypeScore);
bool preferUserChoice = preferredDeviceName == deviceName;
bool preferOption = preferDeviceTypeScore || preferUserChoice;
if (preferOption) {
physicalDevice = physicalDevices[i];
description.name = deviceName;
description.type = toDeviceType(deviceProperties.deviceType);
description.driverVersion = deviceProperties.driverVersion;
description.vendor = RenderDeviceVendor(deviceProperties.vendorID);
currentDeviceTypeScore = deviceTypeScore;
if (preferUserChoice) {
break;
}
}
}
if (physicalDevice == VK_NULL_HANDLE) {
fprintf(stderr, "Unable to find a device with the required features.\n");
return;
}
// Check for extensions.
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, availableExtensions.data());
std::unordered_set<std::string> missingRequiredExtensions = RequiredDeviceExtensions;
std::unordered_set<std::string> supportedOptionalExtensions;
# if DLSS_ENABLED
const std::unordered_set<std::string> dlssExtensions = DLSS::getRequiredDeviceExtensionsVulkan(this);
# endif
for (uint32_t i = 0; i < extensionCount; i++) {
const std::string extensionName(availableExtensions[i].extensionName);
missingRequiredExtensions.erase(extensionName);
if (OptionalDeviceExtensions.find(extensionName) != OptionalDeviceExtensions.end()) {
supportedOptionalExtensions.insert(extensionName);
}
# if DLSS_ENABLED
else if (dlssExtensions.find(extensionName) != dlssExtensions.end()) {
supportedOptionalExtensions.insert(extensionName);
}
# endif
}
if (!missingRequiredExtensions.empty()) {
for (const std::string &extension : missingRequiredExtensions) {
fprintf(stderr, "Missing required extension: %s.\n", extension.c_str());
}
fprintf(stderr, "Unable to create device. Required extensions are missing.\n");
return;
}
// Store properties.
vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties);
// Check for supported features.
void *featuresChain = nullptr;
VkPhysicalDeviceDescriptorIndexingFeatures indexingFeatures = {};
indexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES;
featuresChain = &indexingFeatures;
VkPhysicalDeviceScalarBlockLayoutFeatures layoutFeatures = {};
layoutFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES;
layoutFeatures.pNext = featuresChain;
featuresChain = &layoutFeatures;
VkPhysicalDevicePresentIdFeaturesKHR presentIdFeatures = {};
VkPhysicalDevicePresentWaitFeaturesKHR presentWaitFeatures = {};
const bool presentWaitSupported = supportedOptionalExtensions.find(VK_KHR_PRESENT_ID_EXTENSION_NAME) != supportedOptionalExtensions.end() && supportedOptionalExtensions.find(VK_KHR_PRESENT_WAIT_EXTENSION_NAME) != supportedOptionalExtensions.end();
if (presentWaitSupported) {
presentIdFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR;
presentIdFeatures.pNext = featuresChain;
featuresChain = &presentIdFeatures;
presentWaitFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR;
presentWaitFeatures.pNext = featuresChain;
featuresChain = &presentWaitFeatures;
}
VkPhysicalDeviceRobustness2FeaturesEXT robustnessFeatures = {};
robustnessFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT;
robustnessFeatures.pNext = featuresChain;
featuresChain = &robustnessFeatures;
VkPhysicalDeviceBufferDeviceAddressFeatures bufferDeviceAddressFeatures = {};
bufferDeviceAddressFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES;
bufferDeviceAddressFeatures.pNext = featuresChain;
featuresChain = &bufferDeviceAddressFeatures;
VkPhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures = {};
portabilityFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR;
portabilityFeatures.pNext = featuresChain;
featuresChain = &portabilityFeatures;
VkPhysicalDeviceFeatures2 deviceFeatures = {};
deviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
deviceFeatures.pNext = featuresChain;
vkGetPhysicalDeviceFeatures2(physicalDevice, &deviceFeatures);
void *createDeviceChain = nullptr;
VkPhysicalDeviceRayTracingPipelineFeaturesKHR rtPipelineFeatures = {};
VkPhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeatures = {};
const bool rtSupported = supportedOptionalExtensions.find(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME) != supportedOptionalExtensions.end();
if (rtSupported) {
rtPipelineProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR;
VkPhysicalDeviceProperties2 deviceProperties2 = {};
deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
deviceProperties2.pNext = &rtPipelineProperties;
vkGetPhysicalDeviceProperties2(physicalDevice, &deviceProperties2);
rtPipelineFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR;
rtPipelineFeatures.rayTracingPipeline = true;
createDeviceChain = &rtPipelineFeatures;
accelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
accelerationStructureFeatures.pNext = createDeviceChain;
accelerationStructureFeatures.accelerationStructure = true;
createDeviceChain = &accelerationStructureFeatures;
}
const bool sampleLocationsSupported = supportedOptionalExtensions.find(VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME) != supportedOptionalExtensions.end();
if (sampleLocationsSupported) {
sampleLocationProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLE_LOCATIONS_PROPERTIES_EXT;
VkPhysicalDeviceProperties2 deviceProperties2 = {};
deviceProperties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
deviceProperties2.pNext = &sampleLocationProperties;
vkGetPhysicalDeviceProperties2(physicalDevice, &deviceProperties2);
}
const bool descriptorIndexing = indexingFeatures.descriptorBindingPartiallyBound && indexingFeatures.descriptorBindingVariableDescriptorCount && indexingFeatures.runtimeDescriptorArray;
if (descriptorIndexing) {
indexingFeatures.pNext = createDeviceChain;
createDeviceChain = &indexingFeatures;
}
const bool scalarBlockLayout = layoutFeatures.scalarBlockLayout;
if (scalarBlockLayout) {
layoutFeatures.pNext = createDeviceChain;
createDeviceChain = &layoutFeatures;
}
const bool presentWait = presentIdFeatures.presentId && presentWaitFeatures.presentWait;
if (presentWait) {
presentIdFeatures.pNext = createDeviceChain;
createDeviceChain = &presentIdFeatures;
presentWaitFeatures.pNext = createDeviceChain;
createDeviceChain = &presentWaitFeatures;
}
const bool nullDescriptor = robustnessFeatures.nullDescriptor;
if (nullDescriptor) {
robustnessFeatures.pNext = createDeviceChain;
createDeviceChain = &robustnessFeatures;
}
const bool bufferDeviceAddress = bufferDeviceAddressFeatures.bufferDeviceAddress;
if (bufferDeviceAddress) {
bufferDeviceAddressFeatures.pNext = createDeviceChain;
createDeviceChain = &bufferDeviceAddressFeatures;
}
const bool portabilitySubset = supportedOptionalExtensions.find(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) != supportedOptionalExtensions.end();
if (portabilitySubset) {
portabilityFeatures.pNext = createDeviceChain;
createDeviceChain = &portabilityFeatures;
}
// Retrieve the information for the queue families.
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyCount);
std::vector<bool> queueFamilyUsed(queueFamilyCount, false);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data());
auto pickFamilyQueue = [&](RenderCommandListType type, VkQueueFlags flags) {
uint32_t familyIndex = 0;
uint32_t familySetBits = sizeof(uint32_t) * 8;
uint32_t familyQueueCount = 0;
bool familyUsed = false;
for (uint32_t i = 0; i < queueFamilyCount; i++) {
const VkQueueFamilyProperties &props = queueFamilyProperties[i];
// The family queue flags must contain all the flags required by the command list type.
if ((props.queueFlags & flags) != flags) {
continue;
}
// Prefer picking the queues with the least amount of bits set that match the mask we're looking for.
// If the queue families have matching capabilities but one is already used, prefer the unused one.
uint32_t setBits = numberOfSetBits(props.queueFlags);
bool used = queueFamilyUsed[i];
if ((setBits < familySetBits) || ((setBits == familySetBits) && ((props.queueCount > familyQueueCount) || (familyUsed && !used)))) {
familyIndex = i;
familySetBits = setBits;
familyQueueCount = props.queueCount;
familyUsed = used;
}
}
queueFamilyIndices[toFamilyIndex(type)] = familyIndex;
queueFamilyUsed[familyIndex] = true;
};
// Pick the family queues for each type of command list.
pickFamilyQueue(RenderCommandListType::DIRECT, VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT);
pickFamilyQueue(RenderCommandListType::COMPUTE, VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT);
pickFamilyQueue(RenderCommandListType::COPY, VK_QUEUE_TRANSFER_BIT);
// Create the struct to store the virtual queues.
queueFamilies.resize(queueFamilyCount);
// Create the logical device with the desired family queues.
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::vector<float> queuePriorities(MaxQueuesPerFamilyCount, 1.0f);
queueCreateInfos.reserve(queueFamilyCount);
for (uint32_t i = 0; i < queueFamilyCount; i++) {
if (queueFamilyUsed[i]) {
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueCount = std::min(queueFamilyProperties[i].queueCount, MaxQueuesPerFamilyCount);
queueCreateInfo.queueFamilyIndex = i;
queueCreateInfo.pQueuePriorities = queuePriorities.data();
queueCreateInfos.emplace_back(queueCreateInfo);
queueFamilies[i].queues.resize(queueCreateInfo.queueCount);
}
}
std::vector<const char *> enabledExtensions;
for (const std::string &extension : RequiredDeviceExtensions) {
enabledExtensions.push_back(extension.c_str());
}
for (const std::string &extension : supportedOptionalExtensions) {
enabledExtensions.push_back(extension.c_str());
}
VkDeviceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.pNext = createDeviceChain;
createInfo.pQueueCreateInfos = queueCreateInfos.data();
createInfo.queueCreateInfoCount = uint32_t(queueCreateInfos.size());
createInfo.ppEnabledExtensionNames = enabledExtensions.data();
createInfo.enabledExtensionCount = uint32_t(enabledExtensions.size());
createInfo.pEnabledFeatures = &deviceFeatures.features;
VkResult res = vkCreateDevice(physicalDevice, &createInfo, nullptr, &vk);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateDevice failed with error code 0x%X.\n", res);
return;
}
for (uint32_t i = 0; i < queueFamilyCount; i++) {
for (uint32_t j = 0; j < queueFamilies[i].queues.size(); j++) {
vkGetDeviceQueue(vk, i, j, &queueFamilies[i].queues[j].vk);
}
}
VmaVulkanFunctions vmaFunctions = {};
vmaFunctions.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
vmaFunctions.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
vmaFunctions.vkAllocateMemory = vkAllocateMemory;
vmaFunctions.vkBindBufferMemory = vkBindBufferMemory;
vmaFunctions.vkBindImageMemory = vkBindImageMemory;
vmaFunctions.vkCreateBuffer = vkCreateBuffer;
vmaFunctions.vkCreateImage = vkCreateImage;
vmaFunctions.vkDestroyBuffer = vkDestroyBuffer;
vmaFunctions.vkDestroyImage = vkDestroyImage;
vmaFunctions.vkFlushMappedMemoryRanges = vkFlushMappedMemoryRanges;
vmaFunctions.vkFreeMemory = vkFreeMemory;
vmaFunctions.vkGetBufferMemoryRequirements = vkGetBufferMemoryRequirements;
vmaFunctions.vkGetImageMemoryRequirements = vkGetImageMemoryRequirements;
vmaFunctions.vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties;
vmaFunctions.vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties;
vmaFunctions.vkInvalidateMappedMemoryRanges = vkInvalidateMappedMemoryRanges;
vmaFunctions.vkMapMemory = vkMapMemory;
vmaFunctions.vkUnmapMemory = vkUnmapMemory;
vmaFunctions.vkCmdCopyBuffer = vkCmdCopyBuffer;
VmaAllocatorCreateInfo allocatorInfo = {};
allocatorInfo.flags |= bufferDeviceAddress ? VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT : 0;
allocatorInfo.physicalDevice = physicalDevice;
allocatorInfo.device = vk;
allocatorInfo.pVulkanFunctions = &vmaFunctions;
allocatorInfo.instance = renderInterface->instance;
allocatorInfo.vulkanApiVersion = renderInterface->appInfo.apiVersion;
res = vmaCreateAllocator(&allocatorInfo, &allocator);
if (res != VK_SUCCESS) {
fprintf(stderr, "vmaCreateAllocator failed with error code 0x%X.\n", res);
release();
return;
}
// Find the biggest device local memory available on the device.
VkDeviceSize memoryHeapSize = 0;
const VkPhysicalDeviceMemoryProperties *memoryProps = nullptr;
vmaGetMemoryProperties(allocator, &memoryProps);
constexpr VkMemoryPropertyFlags uploadHeapPropertyFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
bool hasHostVisibleDeviceLocalMemory = false;
for (uint32_t i = 0; i < memoryProps->memoryTypeCount; i++) {
if ((memoryProps->memoryTypes[i].propertyFlags & uploadHeapPropertyFlags) == uploadHeapPropertyFlags) {
hasHostVisibleDeviceLocalMemory = true;
}
}
for (uint32_t i = 0; i < memoryProps->memoryHeapCount; i++) {
if (memoryProps->memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) {
memoryHeapSize = std::max(memoryProps->memoryHeaps[i].size, memoryHeapSize);
}
}
// Fill description.
description.dedicatedVideoMemory = memoryHeapSize;
// Fill capabilities.
capabilities.geometryShader = deviceFeatures.features.geometryShader;
capabilities.raytracing = rtSupported;
capabilities.raytracingStateUpdate = false;
capabilities.sampleLocations = sampleLocationsSupported;
capabilities.descriptorIndexing = descriptorIndexing;
capabilities.scalarBlockLayout = scalarBlockLayout;
capabilities.presentWait = presentWait;
capabilities.displayTiming = supportedOptionalExtensions.find(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME) != supportedOptionalExtensions.end();
capabilities.preferHDR = memoryHeapSize > (512 * 1024 * 1024);
#if defined(__APPLE__)
// MoltenVK supports triangle fans but does so via compute shaders to translate to lists, since it has to
// support all cases including indirect draw. This results in renderpass restarts that can harm performance,
// so force disable native triangle fan support and rely on the game to emulate fans if needed.
capabilities.triangleFan = false;
#else
capabilities.triangleFan = true;
#endif
capabilities.dynamicDepthBias = true;
capabilities.uma = (description.type == RenderDeviceType::INTEGRATED) && hasHostVisibleDeviceLocalMemory;
capabilities.gpuUploadHeap = capabilities.uma;
// Fill Vulkan-only capabilities.
loadStoreOpNoneSupported = supportedOptionalExtensions.find(VK_EXT_LOAD_STORE_OP_NONE_EXTENSION_NAME) != supportedOptionalExtensions.end();
nullDescriptorSupported = nullDescriptor;
if (!nullDescriptorSupported) {
nullBuffer = createBuffer(RenderBufferDesc::DefaultBuffer(16, RenderBufferFlag::VERTEX));
}
}
VulkanDevice::~VulkanDevice() {
release();
}
std::unique_ptr<RenderCommandList> VulkanDevice::createCommandList(RenderCommandListType type) {
return std::make_unique<VulkanCommandList>(this, type);
}
std::unique_ptr<RenderDescriptorSet> VulkanDevice::createDescriptorSet(const RenderDescriptorSetDesc &desc) {
return std::make_unique<VulkanDescriptorSet>(this, desc);
}
std::unique_ptr<RenderShader> VulkanDevice::createShader(const void *data, uint64_t size, const char *entryPointName, RenderShaderFormat format) {
return std::make_unique<VulkanShader>(this, data, size, entryPointName, format);
}
std::unique_ptr<RenderSampler> VulkanDevice::createSampler(const RenderSamplerDesc &desc) {
return std::make_unique<VulkanSampler>(this, desc);
}
std::unique_ptr<RenderPipeline> VulkanDevice::createComputePipeline(const RenderComputePipelineDesc &desc) {
return std::make_unique<VulkanComputePipeline>(this, desc);
}
std::unique_ptr<RenderPipeline> VulkanDevice::createGraphicsPipeline(const RenderGraphicsPipelineDesc &desc) {
return std::make_unique<VulkanGraphicsPipeline>(this, desc);
}
std::unique_ptr<RenderPipeline> VulkanDevice::createRaytracingPipeline(const RenderRaytracingPipelineDesc &desc, const RenderPipeline *previousPipeline) {
return std::make_unique<VulkanRaytracingPipeline>(this, desc, previousPipeline);
}
std::unique_ptr<RenderCommandQueue> VulkanDevice::createCommandQueue(RenderCommandListType type) {
return std::make_unique<VulkanCommandQueue>(this, type);
}
std::unique_ptr<RenderBuffer> VulkanDevice::createBuffer(const RenderBufferDesc &desc) {
return std::make_unique<VulkanBuffer>(this, nullptr, desc);
}
std::unique_ptr<RenderTexture> VulkanDevice::createTexture(const RenderTextureDesc &desc) {
return std::make_unique<VulkanTexture>(this, nullptr, desc);
}
std::unique_ptr<RenderAccelerationStructure> VulkanDevice::createAccelerationStructure(const RenderAccelerationStructureDesc &desc) {
return std::make_unique<VulkanAccelerationStructure>(this, desc);
}
std::unique_ptr<RenderPool> VulkanDevice::createPool(const RenderPoolDesc &desc) {
return std::make_unique<VulkanPool>(this, desc);
}
std::unique_ptr<RenderPipelineLayout> VulkanDevice::createPipelineLayout(const RenderPipelineLayoutDesc &desc) {
return std::make_unique<VulkanPipelineLayout>(this, desc);
}
std::unique_ptr<RenderCommandFence> VulkanDevice::createCommandFence() {
return std::make_unique<VulkanCommandFence>(this);
}
std::unique_ptr<RenderCommandSemaphore> VulkanDevice::createCommandSemaphore() {
return std::make_unique<VulkanCommandSemaphore>(this);
}
std::unique_ptr<RenderFramebuffer> VulkanDevice::createFramebuffer(const RenderFramebufferDesc &desc) {
return std::make_unique<VulkanFramebuffer>(this, desc);
}
std::unique_ptr<RenderQueryPool> VulkanDevice::createQueryPool(uint32_t queryCount) {
return std::make_unique<VulkanQueryPool>(this, queryCount);
}
void VulkanDevice::setBottomLevelASBuildInfo(RenderBottomLevelASBuildInfo &buildInfo, const RenderBottomLevelASMesh *meshes, uint32_t meshCount, bool preferFastBuild, bool preferFastTrace) {
assert(meshes != nullptr);
assert(meshCount > 0);
uint32_t primitiveCount = 0;
thread_local std::vector<uint32_t> geometryPrimitiveCounts;
geometryPrimitiveCounts.resize(meshCount);
buildInfo.buildData.resize(sizeof(VkAccelerationStructureGeometryKHR) * meshCount);
VkAccelerationStructureGeometryKHR *geometries = reinterpret_cast<VkAccelerationStructureGeometryKHR *>(buildInfo.buildData.data());
for (uint32_t i = 0; i < meshCount; i++) {
const RenderBottomLevelASMesh &mesh = meshes[i];
VkAccelerationStructureGeometryKHR &geometry = geometries[i];
geometry = {};
geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
geometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
geometry.flags = VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR;
geometry.flags |= mesh.isOpaque ? VK_GEOMETRY_OPAQUE_BIT_KHR : 0;
const VulkanBuffer *interfaceVertexBuffer = static_cast<const VulkanBuffer *>(mesh.vertexBuffer.ref);
const VulkanBuffer *interfaceIndexBuffer = static_cast<const VulkanBuffer *>(mesh.indexBuffer.ref);
assert((interfaceIndexBuffer == nullptr) || ((interfaceIndexBuffer->desc.flags & RenderBufferFlag::ACCELERATION_STRUCTURE_INPUT) && "Acceleration structure input allowed on index buffer."));
assert((interfaceVertexBuffer == nullptr) || ((interfaceVertexBuffer->desc.flags & RenderBufferFlag::ACCELERATION_STRUCTURE_INPUT) && "Acceleration structure input allowed on vertex buffer."));
VkAccelerationStructureGeometryTrianglesDataKHR &triangles = geometry.geometry.triangles;
triangles = {};
triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
triangles.vertexFormat = toVk(mesh.vertexFormat);
triangles.vertexStride = mesh.vertexStride;
triangles.maxVertex = mesh.vertexCount - 1;
if (interfaceVertexBuffer != nullptr) {
VkBufferDeviceAddressInfo vertexAddressInfo = {};
vertexAddressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
vertexAddressInfo.buffer = interfaceVertexBuffer->vk;
triangles.vertexData.deviceAddress = vkGetBufferDeviceAddress(vk, &vertexAddressInfo) + mesh.vertexBuffer.offset;
}
if (interfaceIndexBuffer != nullptr) {
triangles.indexType = toIndexType(mesh.indexFormat);
VkBufferDeviceAddressInfo indexAddressInfo = {};
indexAddressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
indexAddressInfo.buffer = interfaceIndexBuffer->vk;
triangles.indexData.deviceAddress = vkGetBufferDeviceAddress(vk, &indexAddressInfo) + mesh.indexBuffer.offset;
geometryPrimitiveCounts[i] = mesh.indexCount / 3;
}
else {
triangles.indexType = VK_INDEX_TYPE_NONE_KHR;
geometryPrimitiveCounts[i] = mesh.vertexCount / 3;
}
primitiveCount += geometryPrimitiveCounts[i];
}
VkAccelerationStructureBuildGeometryInfoKHR buildGeometryInfo = {};
buildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
buildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
buildGeometryInfo.flags = toRTASBuildFlags(preferFastBuild, preferFastTrace);
buildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
buildGeometryInfo.pGeometries = geometries;
buildGeometryInfo.geometryCount = meshCount;
VkAccelerationStructureBuildSizesInfoKHR buildSizesInfo = {};
buildSizesInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
vkGetAccelerationStructureBuildSizesKHR(vk, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &buildGeometryInfo, geometryPrimitiveCounts.data(), &buildSizesInfo);
buildInfo.meshCount = meshCount;
buildInfo.primitiveCount = primitiveCount;
buildInfo.preferFastBuild = preferFastBuild;
buildInfo.preferFastTrace = preferFastTrace;
buildInfo.scratchSize = roundUp(buildSizesInfo.buildScratchSize, AccelerationStructureBufferAlignment);
buildInfo.accelerationStructureSize = roundUp(buildSizesInfo.accelerationStructureSize, AccelerationStructureBufferAlignment);
}
void VulkanDevice::setTopLevelASBuildInfo(RenderTopLevelASBuildInfo &buildInfo, const RenderTopLevelASInstance *instances, uint32_t instanceCount, bool preferFastBuild, bool preferFastTrace) {
assert(instances != nullptr);
assert(instanceCount > 0);
// Build the instance data to be uploaded.
buildInfo.instancesBufferData.resize(sizeof(VkAccelerationStructureInstanceKHR) * instanceCount, 0);
VkAccelerationStructureInstanceKHR *bufferInstances = reinterpret_cast<VkAccelerationStructureInstanceKHR *>(buildInfo.instancesBufferData.data());
for (uint32_t i = 0; i < instanceCount; i++) {
const RenderTopLevelASInstance &instance = instances[i];
const VulkanBuffer *interfaceBottomLevelAS = static_cast<const VulkanBuffer *>(instance.bottomLevelAS.ref);
assert(interfaceBottomLevelAS != nullptr);
VkAccelerationStructureInstanceKHR &bufferInstance = bufferInstances[i];
bufferInstance.instanceCustomIndex = instance.instanceID;
bufferInstance.mask = instance.instanceMask;
bufferInstance.instanceShaderBindingTableRecordOffset = instance.instanceContributionToHitGroupIndex;
bufferInstance.flags = instance.cullDisable ? VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR : 0;
memcpy(bufferInstance.transform.matrix, instance.transform.m, sizeof(bufferInstance.transform.matrix));
VkBufferDeviceAddressInfo blasAddressInfo = {};
blasAddressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
blasAddressInfo.buffer = interfaceBottomLevelAS->vk;
bufferInstance.accelerationStructureReference = vkGetBufferDeviceAddress(vk, &blasAddressInfo) + instance.bottomLevelAS.offset;
}
// Retrieve the size the TLAS will require.
VkAccelerationStructureGeometryKHR topGeometry = {};
topGeometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
topGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
VkAccelerationStructureGeometryInstancesDataKHR &instancesData = topGeometry.geometry.instances;
instancesData.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
VkAccelerationStructureBuildGeometryInfoKHR buildGeometryInfo = {};
buildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
buildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
buildGeometryInfo.flags = toRTASBuildFlags(preferFastBuild, preferFastTrace);
buildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
buildGeometryInfo.pGeometries = &topGeometry;
buildGeometryInfo.geometryCount = 1;
VkAccelerationStructureBuildSizesInfoKHR buildSizesInfo = {};
buildSizesInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
vkGetAccelerationStructureBuildSizesKHR(vk, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &buildGeometryInfo, &instanceCount, &buildSizesInfo);
buildInfo.instanceCount = instanceCount;
buildInfo.preferFastBuild = preferFastBuild;
buildInfo.preferFastTrace = preferFastTrace;
buildInfo.scratchSize = roundUp(buildSizesInfo.buildScratchSize, AccelerationStructureBufferAlignment);
buildInfo.accelerationStructureSize = roundUp(buildSizesInfo.accelerationStructureSize, AccelerationStructureBufferAlignment);
}
void VulkanDevice::setShaderBindingTableInfo(RenderShaderBindingTableInfo &tableInfo, const RenderShaderBindingGroups &groups, const RenderPipeline *pipeline, RenderDescriptorSet **descriptorSets, uint32_t descriptorSetCount) {
assert(pipeline != nullptr);
assert((descriptorSets != nullptr) && "Vulkan doesn't require descriptor sets, but they should be passed to keep consistency with D3D12.");
const VulkanRaytracingPipeline *raytracingPipeline = static_cast<const VulkanRaytracingPipeline *>(pipeline);
assert((raytracingPipeline->type == VulkanPipeline::Type::Raytracing) && "Only raytracing pipelines can be used to build shader binding tables.");
assert((raytracingPipeline->descriptorSetCount <= descriptorSetCount) && "There must be enough descriptor sets available for the pipeline.");
const uint32_t handleSize = rtPipelineProperties.shaderGroupHandleSize;
thread_local std::vector<uint8_t> groupHandles;
groupHandles.clear();
groupHandles.resize(raytracingPipeline->groupCount * handleSize, 0);
VkResult res = vkGetRayTracingShaderGroupHandlesKHR(vk, raytracingPipeline->vk, 0, raytracingPipeline->groupCount, groupHandles.size(), groupHandles.data());
if (res != VK_SUCCESS) {
fprintf(stderr, "vkGetRayTracingShaderGroupHandlesKHR failed with error code 0x%X.\n", res);
return;
}
const uint32_t handleSizeAligned = roundUp(handleSize, rtPipelineProperties.shaderGroupHandleAlignment);
const uint32_t regionAlignment = roundUp(handleSizeAligned, rtPipelineProperties.shaderGroupBaseAlignment);
uint64_t tableSize = 0;
auto setGroup = [&](RenderShaderBindingGroupInfo &groupInfo, const RenderShaderBindingGroup &renderGroup) {
groupInfo.startIndex = 0;
if (renderGroup.pipelineProgramsCount == 0) {
groupInfo.stride = 0;
groupInfo.offset = 0;
groupInfo.size = 0;
}
else {
groupInfo.stride = regionAlignment;
groupInfo.offset = tableSize;
groupInfo.size = groupInfo.stride * renderGroup.pipelineProgramsCount;
tableSize += groupInfo.size;
}
};
setGroup(tableInfo.groups.rayGen, groups.rayGen);
setGroup(tableInfo.groups.miss, groups.miss);
setGroup(tableInfo.groups.hitGroup, groups.hitGroup);
setGroup(tableInfo.groups.callable, groups.callable);
tableSize = roundUp(tableSize, ShaderBindingTableAlignment);
tableInfo.tableBufferData.clear();
tableInfo.tableBufferData.resize(tableSize, 0);
auto copyGroupData = [&](RenderShaderBindingGroupInfo &groupInfo, const RenderShaderBindingGroup &renderGroup) {
for (uint32_t i = 0; i < renderGroup.pipelineProgramsCount; i++) {
const uint8_t *shaderId = groupHandles.data() + renderGroup.pipelinePrograms[i].programIndex * handleSize;
const uint64_t tableOffset = groupInfo.offset + i * groupInfo.stride;
memcpy(&tableInfo.tableBufferData[tableOffset], shaderId, handleSize);
}
};
copyGroupData(tableInfo.groups.rayGen, groups.rayGen);
copyGroupData(tableInfo.groups.miss, groups.miss);
copyGroupData(tableInfo.groups.hitGroup, groups.hitGroup);
copyGroupData(tableInfo.groups.callable, groups.callable);
}
const RenderDeviceCapabilities &VulkanDevice::getCapabilities() const {
return capabilities;
}
const RenderDeviceDescription &VulkanDevice::getDescription() const {
return description;
}
RenderSampleCounts VulkanDevice::getSampleCountsSupported(RenderFormat format) const {
const bool isDepthFormat = (format == RenderFormat::D16_UNORM) || (format == RenderFormat::D32_FLOAT);
if (isDepthFormat) {
return RenderSampleCounts(physicalDeviceProperties.limits.framebufferDepthSampleCounts);
}
else {
return RenderSampleCounts(physicalDeviceProperties.limits.framebufferColorSampleCounts);
}
}
void VulkanDevice::waitIdle() const {
vkDeviceWaitIdle(vk);
}
void VulkanDevice::release() {
if (allocator != VK_NULL_HANDLE) {
vmaDestroyAllocator(allocator);
allocator = VK_NULL_HANDLE;
}
if (vk != VK_NULL_HANDLE) {
vkDestroyDevice(vk, nullptr);
vk = VK_NULL_HANDLE;
}
}
bool VulkanDevice::isValid() const {
return vk != nullptr;
}
// VulkanInterface
#if SDL_VULKAN_ENABLED
VulkanInterface::VulkanInterface(RenderWindow sdlWindow) {
#else
VulkanInterface::VulkanInterface() {
#endif
VkResult res = volkInitialize();
if (res != VK_SUCCESS) {
fprintf(stderr, "volkInitialize failed with error code 0x%X.\n", res);
return;
}
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "plume";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "plume";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_2;
VkInstanceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
createInfo.ppEnabledLayerNames = nullptr;
createInfo.enabledLayerCount = 0;
# ifdef __APPLE__
createInfo.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
# endif
// Check for extensions.
uint32_t extensionCount;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, availableExtensions.data());
std::unordered_set<std::string> requiredExtensions = RequiredInstanceExtensions;
std::unordered_set<std::string> supportedOptionalExtensions;
# if DLSS_ENABLED
const std::unordered_set<std::string> dlssExtensions = DLSS::getRequiredInstanceExtensionsVulkan();
# endif
# if SDL_VULKAN_ENABLED
// Push the extensions specified by SDL as required.
// SDL2 has this awkward requirement for the window to pull the extensions from.
// This can be removed when upgrading to SDL3.
if (sdlWindow != nullptr) {
uint32_t sdlVulkanExtensionCount = 0;
if (SDL_Vulkan_GetInstanceExtensions(sdlWindow, &sdlVulkanExtensionCount, nullptr)) {
std::vector<char *> sdlVulkanExtensions;
sdlVulkanExtensions.resize(sdlVulkanExtensionCount);
if (SDL_Vulkan_GetInstanceExtensions(sdlWindow, &sdlVulkanExtensionCount, (const char **)(sdlVulkanExtensions.data()))) {
for (char *sdlVulkanExtension : sdlVulkanExtensions) {
requiredExtensions.insert(sdlVulkanExtension);
}
}
}
}
# endif
std::unordered_set<std::string> missingRequiredExtensions = requiredExtensions;
for (uint32_t i = 0; i < extensionCount; i++) {
const std::string extensionName(availableExtensions[i].extensionName);
missingRequiredExtensions.erase(extensionName);
if (OptionalInstanceExtensions.find(extensionName) != OptionalInstanceExtensions.end()) {
supportedOptionalExtensions.insert(extensionName);
}
# if DLSS_ENABLED
else if (dlssExtensions.find(extensionName) != dlssExtensions.end()) {
supportedOptionalExtensions.insert(extensionName);
}
# endif
}
if (!missingRequiredExtensions.empty()) {
for (const std::string &extension : missingRequiredExtensions) {
fprintf(stderr, "Missing required extension: %s.\n", extension.c_str());
}
fprintf(stderr, "Unable to create instance. Required extensions are missing.\n");
return;
}
std::vector<const char *> enabledExtensions;
for (const std::string &extension : requiredExtensions) {
enabledExtensions.push_back(extension.c_str());
}
for (const std::string &extension : supportedOptionalExtensions) {
enabledExtensions.push_back(extension.c_str());
}
createInfo.ppEnabledExtensionNames = enabledExtensions.data();
createInfo.enabledExtensionCount = uint32_t(enabledExtensions.size());
# ifdef VULKAN_VALIDATION_LAYER_ENABLED
// Search for validation layer and enabled it.
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
std::vector<VkLayerProperties> availableLayers(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
const char validationLayerName[] = "VK_LAYER_KHRONOS_validation";
const char *enabledLayerNames[] = { validationLayerName };
for (const VkLayerProperties &layerProperties : availableLayers) {
if (strcmp(layerProperties.layerName, validationLayerName) == 0) {
createInfo.ppEnabledLayerNames = enabledLayerNames;
createInfo.enabledLayerCount = 1;
break;
}
}
# endif
res = vkCreateInstance(&createInfo, nullptr, &instance);
if (res != VK_SUCCESS) {
fprintf(stderr, "vkCreateInstance failed with error code 0x%X.\n", res);
return;
}
volkLoadInstance(instance);
// Fill capabilities.
capabilities.shaderFormat = RenderShaderFormat::SPIRV;
// Fill device names.
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
if (deviceCount > 0) {
std::vector<VkPhysicalDevice> physicalDevices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data());
for (uint32_t i = 0; i < deviceCount; i++) {
VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(physicalDevices[i], &deviceProperties);
uint32_t deviceTypeIndex = deviceProperties.deviceType;
if (deviceTypeIndex <= 4) {
deviceNames.emplace_back(deviceProperties.deviceName);
}
}
}
}
VulkanInterface::~VulkanInterface() {
if (instance != nullptr) {
vkDestroyInstance(instance, nullptr);
}
}
std::unique_ptr<RenderDevice> VulkanInterface::createDevice(const std::string &preferredDeviceName) {
std::unique_ptr<VulkanDevice> createdDevice = std::make_unique<VulkanDevice>(this, preferredDeviceName);
return createdDevice->isValid() ? std::move(createdDevice) : nullptr;
}
const RenderInterfaceCapabilities &VulkanInterface::getCapabilities() const {
return capabilities;
}
const std::vector<std::string> &VulkanInterface::getDeviceNames() const {
return deviceNames;
}
bool VulkanInterface::isValid() const {
return instance != nullptr;
}
// Global creation function.
#if SDL_VULKAN_ENABLED
std::unique_ptr<RenderInterface> CreateVulkanInterface(RenderWindow sdlWindow) {
std::unique_ptr<VulkanInterface> createdInterface = std::make_unique<VulkanInterface>(sdlWindow);
return createdInterface->isValid() ? std::move(createdInterface) : nullptr;
}
#else
std::unique_ptr<RenderInterface> CreateVulkanInterface() {
std::unique_ptr<VulkanInterface> createdInterface = std::make_unique<VulkanInterface>();
return createdInterface->isValid() ? std::move(createdInterface) : nullptr;
}
#endif
};