/*
* VulkZample
* Copyright (C) 2024 Rebekah Rowe
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#define VULKAN_HPP_NO_CONSTRUCTORS
#include
#include
#include
#include
#define VMA_IMPLEMENTATION
#define VMA_DEBUG_INITIALIZE_ALLOCATIONS 1 // Obviously its the C++ way, just initialize it since its ez // https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/debugging_memory_usage.html
#define VMA_DEBUG_DETECT_CORRUPTION 1 // heap overflow, enable!
#include
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include
#include
#include
static constexpr bool fatal_errors = true; // keep this true for tests
#define ENABLE_VULKAN_VALIDATION true
static constexpr bool vulkan_enable_validation_layers =
#if defined(ENABLE_VULKAN_VALIDATION)
true;
#else
false;
#endif
#if defined(ENABLE_VULKAN_VALIDATION)
static VKAPI_ATTR VkBool32 VKAPI_CALL VulkanExampleApplication_VulkanDebugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_type,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
void* user_data);
/*static VKAPI_ATTR VkBool32 VKAPI_CALL VulkanExampleApplication_VulkanReportCallback(
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT type,
uint64_t obj,
size_t location,
int32_t code,
const char* layer_prefix,
const char* msg,
void* user_data);*/
#endif
class VulkanExampleApplication {
private:
using VulkanBuffer = std::optional>;
using VulkanImage = std::optional>;
public: // Window/Device setup
std::optional libsdl;
std::optional window;
std::optional vk_ctx;
std::optional vk_inst;
std::optional vk_debug_utils_messenger;
// std::optional vk_debug_report_callback;
std::optional vk_gfx_card;
std::optional vk_screen_surface;
std::optional vk_gpu;
struct VulkanDeviceQueriedInfo {
std::optional screen_surface;
std::optional graphics_family;
std::optional present_family;
bool surface_has_present_modes = false;
bool surface_has_format_modes = false;
std::optional msaa_samples;
std::vector supported_features_vma_device;
std::vector supported_features_vma_allocator;
bool IsGoodCard() const {
return this->graphics_family.has_value() && this->present_family.has_value() && this->screen_surface && surface_has_present_modes && surface_has_format_modes;
}
} vk_physical_card_info;
private:
vma::UniqueAllocator vk_allocator;
private: // Swapchain/Framebuffer
std::optional vk_render_pass;
std::optional vk_queue_graphics;
std::optional vk_queue_present;
std::optional vk_swapchain;
vk::Format vk_swapchain_image_format;
vk::Extent2D vk_swapchain_extent;
std::vector vk_swapchain_image_views;
std::vector vk_swapchain_framebuffers;
uint vk_current_frame_index = 0;
std::optional vk_command_pool;
std::vector vk_command_buffers;
std::vector vk_fences_in_flight;
std::vector vk_semephores_image_available;
std::vector vk_semephores_render_finished;
private:
enum DrawMode : std::uint32_t {
kPlain = 1,
kTextured = 2,
kFreetype = 3
};
static constexpr vk::ShaderModuleCreateInfo GetSharedShaderFragment() {
return { .codeSize = embeded_shader_frag_glsl_spv.size, .pCode = reinterpret_cast(embeded_shader_frag_glsl_spv.begin) };
}
using Color = glm::vec4;
using TextureCoordinates = glm::vec2;
struct Vertex2 {
glm::vec2 pos;
Color color;
TextureCoordinates texture_coordinates;
DrawMode draw_mode;
static constexpr vk::ShaderModuleCreateInfo GetShaderInfoFragment() {
return VulkanExampleApplication::GetSharedShaderFragment();
};
static constexpr vk::ShaderModuleCreateInfo GetShaderInfoVertex() {
return { .codeSize = embeded_shader_second_vertex_glsl_spv.size, .pCode = reinterpret_cast(embeded_shader_second_vertex_glsl_spv.begin) };
};
static constexpr vk::VertexInputBindingDescription GetBindingDescription() {
constexpr vk::VertexInputBindingDescription binding_description {
.binding = 0,
.stride = sizeof(Vertex2),
.inputRate = vk::VertexInputRate::eVertex
};
return binding_description;
}
static constexpr std::array GetAttributeDescriptions() {
constexpr std::array attribute_descriptions {
vk::VertexInputAttributeDescription { // https://docs.vulkan.org/tutorial/latest/04_Vertex_buffers/00_Vertex_input_description.html
.location = 0,
.binding = 0,
.format = vk::Format::eR32G32Sfloat,
.offset = offsetof(Vertex2, pos) },
vk::VertexInputAttributeDescription {
.location = 2,
.binding = 0,
.format = vk::Format::eR32G32B32A32Sfloat,
.offset = offsetof(Vertex2, color) },
vk::VertexInputAttributeDescription {
.location = 4,
.binding = 0,
.format = vk::Format::eR32G32Sfloat,
.offset = offsetof(Vertex2, texture_coordinates) },
vk::VertexInputAttributeDescription {
.location = 6,
.binding = 0,
.format = vk::Format::eR32Uint,
.offset = offsetof(Vertex2, draw_mode) }
};
return attribute_descriptions;
}
struct IndexMap {
static inline const std::vector triangle = { 0, 1, 2 };
static inline const std::vector rectangle = { 0, 1, 2, 2, 3, 0 };
};
};
struct Vertex3 {
glm::vec3 pos;
Color color;
TextureCoordinates texture_coordinates;
DrawMode draw_mode;
static constexpr vk::ShaderModuleCreateInfo GetShaderInfoFragment() {
return VulkanExampleApplication::GetSharedShaderFragment();
};
static constexpr vk::ShaderModuleCreateInfo GetShaderInfoVertex() {
return { .codeSize = embeded_shader_third_vertex_glsl_spv.size, .pCode = reinterpret_cast(embeded_shader_third_vertex_glsl_spv.begin) };
}
static constexpr vk::VertexInputBindingDescription GetBindingDescription() {
constexpr vk::VertexInputBindingDescription binding_description {
.binding = 0,
.stride = sizeof(Vertex3),
.inputRate = vk::VertexInputRate::eVertex
};
return binding_description;
}
static constexpr std::array GetAttributeDescriptions() {
constexpr std::array attribute_descriptions {
vk::VertexInputAttributeDescription { // https://docs.vulkan.org/tutorial/latest/04_Vertex_buffers/00_Vertex_input_description.html
.location = 0,
.binding = 0,
.format = vk::Format::eR32G32B32Sfloat,
.offset = offsetof(Vertex3, pos) },
vk::VertexInputAttributeDescription {
.location = 2,
.binding = 0,
.format = vk::Format::eR32G32B32A32Sfloat,
.offset = offsetof(Vertex3, color) },
vk::VertexInputAttributeDescription {
.location = 4,
.binding = 0,
.format = vk::Format::eR32G32Sfloat,
.offset = offsetof(Vertex3, texture_coordinates) },
vk::VertexInputAttributeDescription {
.location = 6,
.binding = 0,
.format = vk::Format::eR32Uint,
.offset = offsetof(Vertex3, draw_mode) }
};
return attribute_descriptions;
}
struct IndexMap {
static inline const std::vector rectangle = {
0, 1, 2, 2, 3, 0, // Wraps a quad-box's 2 triangles around itself. // https://docs.vulkan.org/tutorial/latest/04_Vertex_buffers/03_Index_buffer.html
4, 5, 6, 6, 7, 4
};
};
};
class Texture {
private:
const VulkanExampleApplication* parent;
private:
std::uint32_t vk_texture_mip_levels;
VulkanImage vk_texture_image;
public:
std::optional vk_texture_view;
std::optional vk_texture_sampler;
public:
std::optional vk_descriptor_sets_second;
std::optional vk_descriptor_sets_third;
public:
template
constexpr Texture(const VulkanExampleApplication* _parent, const DataT texture_buffer_data, const std::size_t texture_buffer_size, const vk::Extent2D texture_buffer_extent)
: parent(_parent) {
assert(parent != nullptr);
{ // Texture
const auto texture_format = vk::Format::eR8G8B8A8Srgb;
const auto texture_buffer_staging = this->parent->CreateBuffer(texture_buffer_size, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
this->vk_texture_mip_levels = static_cast(std::floor(std::log2(std::max(texture_buffer_extent.width, texture_buffer_extent.height)))) + 1;
this->parent->vk_allocator->copyMemoryToAllocation(embeded_debug_north_png_rgba.data.begin, *texture_buffer_staging->second, 0, texture_buffer_size);
this->vk_texture_image = this->parent->CreateImage(texture_buffer_extent, texture_format, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, this->vk_texture_mip_levels, vk::SampleCountFlagBits::e1);
this->parent->SingleTimeSubmitCommand([&](const auto& command_buffer) {
this->parent->TransitionImageLayout(command_buffer, *this->vk_texture_image->first, texture_format, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal, this->vk_texture_mip_levels);
this->parent->CopyBuffer(command_buffer, *texture_buffer_staging->first, *this->vk_texture_image->first, texture_buffer_extent);
const auto GenerateMipmaps = [this](const vk::CommandBuffer& command_buffer, const vk::Image& src_image, vk::Format src_format, vk::Extent2D texture_size, std::uint32_t mip_levels) {
assert(mip_levels);
if (!(this->parent->vk_gfx_card->getFormatProperties(src_format).optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear))
throw std::runtime_error("texture image format does not support linear blitting!");
vk::ImageMemoryBarrier image_memory_barrier {
.srcQueueFamilyIndex = vk::QueueFamilyIgnored,
.dstQueueFamilyIndex = vk::QueueFamilyIgnored,
.image = src_image,
.subresourceRange {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
for (std::uint32_t i = 1; i < mip_levels; i++) {
image_memory_barrier.subresourceRange.baseMipLevel = i - 1;
image_memory_barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead,
image_memory_barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
image_memory_barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal,
command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer, {}, nullptr, nullptr, image_memory_barrier);
const std::array image_offsets_src {
vk::Offset3D { 0, 0, 0 },
vk::Offset3D { static_cast(texture_size.width), static_cast(texture_size.height), 1 }
};
const vk::ImageSubresourceLayers image_subresource_layers_src {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = i - 1,
.baseArrayLayer = 0,
.layerCount = 1
};
const std::array image_offsets_dest {
vk::Offset3D { 0, 0, 0 },
vk::Offset3D { static_cast(texture_size.width) > 1 ? static_cast(texture_size.width) / 2 : 1, static_cast(texture_size.height) > 1 ? static_cast(texture_size.height) / 2 : 1, 1 }
};
const vk::ImageSubresourceLayers image_subresource_layers_dest {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = i,
.baseArrayLayer = 0,
.layerCount = 1
};
const vk::ImageBlit image_blit {
.srcSubresource = image_subresource_layers_src,
.srcOffsets = image_offsets_src,
.dstSubresource = image_subresource_layers_dest,
.dstOffsets = image_offsets_dest
};
command_buffer.blitImage(src_image, vk::ImageLayout::eTransferSrcOptimal, src_image, vk::ImageLayout::eTransferDstOptimal, image_blit, vk::Filter::eLinear);
if (texture_size.width > 1)
texture_size.width /= 2;
if (texture_size.height > 1)
texture_size.height /= 2;
image_memory_barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
image_memory_barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
image_memory_barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, nullptr, nullptr, image_memory_barrier);
}
image_memory_barrier.subresourceRange.baseMipLevel = mip_levels - 1;
image_memory_barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
image_memory_barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
image_memory_barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
image_memory_barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
command_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eFragmentShader, {}, nullptr, nullptr, image_memory_barrier);
};
GenerateMipmaps(command_buffer, *this->vk_texture_image->first, texture_format, texture_buffer_extent, this->vk_texture_mip_levels);
});
this->vk_texture_view = this->parent->CreateImageView(*this->vk_texture_image->first, texture_format, vk::ImageAspectFlagBits::eColor, this->vk_texture_mip_levels);
}
{ // Sampler
const vk::SamplerCreateInfo sampler_create_info {
.magFilter = vk::Filter::eNearest,
.minFilter = vk::Filter::eLinear,
.mipmapMode = vk::SamplerMipmapMode::eLinear,
.addressModeU = vk::SamplerAddressMode::eRepeat, // https://docs.vulkan.org/tutorial/latest/06_Texture_mapping/01_Image_view_and_sampler.html#_samplers
.addressModeV = vk::SamplerAddressMode::eRepeat,
.addressModeW = vk::SamplerAddressMode::eRepeat,
.mipLodBias = 0.0f,
.anisotropyEnable = vk::True,
.maxAnisotropy = std::max(4.0f, this->parent->vk_gfx_card->getProperties().limits.maxSamplerAnisotropy),
.compareEnable = vk::False,
.compareOp = vk::CompareOp::eAlways,
.minLod = 0.0f,
.maxLod = vk::LodClampNone,
.borderColor = vk::BorderColor::eIntOpaqueBlack,
.unnormalizedCoordinates = vk::False,
};
this->vk_texture_sampler.emplace(*this->parent->vk_gpu, sampler_create_info);
}
{
this->vk_descriptor_sets_second = this->parent->vk_pipeline_second.CreateImageDescriptorSet(*this->vk_texture_view, *this->vk_texture_sampler);
this->vk_descriptor_sets_third = this->parent->vk_pipeline_third.CreateImageDescriptorSet(*this->vk_texture_view, *this->vk_texture_sampler);
}
}
template
const vk::DescriptorSet& GetDescriptorSet() const {
if constexpr (typeid(VertexT) == typeid(Vertex2))
return **this->vk_descriptor_sets_second;
if constexpr (typeid(VertexT) == typeid(Vertex3))
return **this->vk_descriptor_sets_third;
else
assert(false && "Texture: No descriptor set availiable for this type of vertex.");
}
};
template
class VulkanRenderPipeline {
private:
const VulkanExampleApplication* parent;
std::optional vk_shader_vertex;
std::optional vk_shader_frag;
std::optional vk_pipeline_layout;
std::optional vk_pipeline;
public:
constexpr VulkanRenderPipeline(const VulkanExampleApplication* _parent)
: parent(_parent) { assert(_parent != nullptr); }
public:
std::optional vk_descriptor_pool;
std::vector vk_descriptor_sets;
std::optional vk_descriptor_set_layouts;
const Texture* current_texture = nullptr;
VulkanBuffer vk_buffer_vertex; // in order to get a clean destruction sequence, instantiate the DeviceMemory for the vertex buffer first // https://github.com/KhronosGroup/Vulkan-Hpp/blob/6f72ceca515d59f40d64b64cf2734f6261e1f9f2/RAII_Samples/13_InitVertexBuffer/13_InitVertexBuffer.cpp
VulkanBuffer vk_buffer_index; // used to make rendering models more efficent by sharing values
std::size_t vk_buffer_index_size = 0;
private:
static constexpr bool UsesProjection() {
return decltype(VertexT().pos)::length() == 3;
}
// https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/
struct UniformBufferObject_Projection { // https://docs.vulkan.org/tutorial/latest/05_Uniform_buffers/01_Descriptor_pool_and_sets.html#_alignment_requirements
alignas(16) glm::mat4 model;
alignas(16) glm::mat4 view;
alignas(16) glm::mat4 proj;
};
std::vector vk_buffers_uniform;
std::vector vk_buffers_uniform_mapped;
public:
using IndexT = std::uint16_t;
void CreatePipeline(const vk::raii::Device& gpu, const vk::RenderPass& render_pass, const vk::SampleCountFlagBits& msaa_samples = vk::SampleCountFlagBits::e1) {
{ // Load Shaders
this->vk_shader_vertex.emplace(gpu, VertexT::GetShaderInfoVertex());
this->vk_shader_frag.emplace(gpu, VertexT::GetShaderInfoFragment());
}
{ // descriptor set layouts
const auto descriptor_set_layout_bindings = []() -> std::vector {
constexpr vk::DescriptorSetLayoutBinding descriptor_set_layout_binding_sampler {
.binding = 0,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eFragment,
.pImmutableSamplers = nullptr // Optional
};
if constexpr (UsesProjection()) {
constexpr vk::DescriptorSetLayoutBinding descriptor_set_layout_binding_ubo {
.binding = 1,
.descriptorType = vk::DescriptorType::eUniformBuffer,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eVertex,
.pImmutableSamplers = nullptr // Optional
};
return { descriptor_set_layout_binding_sampler, descriptor_set_layout_binding_ubo };
}
return { descriptor_set_layout_binding_sampler };
}();
const vk::DescriptorSetLayoutCreateInfo descriptor_set_layout_create_info {
.bindingCount = static_cast(descriptor_set_layout_bindings.size()),
.pBindings = descriptor_set_layout_bindings.data()
};
this->vk_descriptor_set_layouts.emplace(gpu, descriptor_set_layout_create_info);
}
{ // Init graphics pipeline/renderpass
const vk::PipelineShaderStageCreateInfo shader_stage_create_info_vertex {
.stage = vk::ShaderStageFlagBits::eVertex,
.module = **this->vk_shader_vertex,
.pName = "main"
};
const vk::PipelineShaderStageCreateInfo shader_stage_create_info_frag {
.stage = vk::ShaderStageFlagBits::eFragment,
.module = **this->vk_shader_frag,
.pName = "main"
};
const vk::PipelineShaderStageCreateInfo shader_stages_create_info[] = { shader_stage_create_info_vertex, shader_stage_create_info_frag };
constexpr auto vertex_binding_description = VertexT::GetBindingDescription();
constexpr auto vertex_attribute_descriptions = VertexT::GetAttributeDescriptions();
const vk::PipelineVertexInputStateCreateInfo vertex_input_create_info {
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = &vertex_binding_description, // Optional
.vertexAttributeDescriptionCount = vertex_attribute_descriptions.size(),
.pVertexAttributeDescriptions = vertex_attribute_descriptions.data() // Optional
};
constexpr vk::PipelineInputAssemblyStateCreateInfo input_assembly_create_info {
.topology = vk::PrimitiveTopology::eTriangleList,
.primitiveRestartEnable = vk::False
};
constexpr vk::PipelineViewportStateCreateInfo viewport_state { // https://docs.vulkan.org/tutorial/latest/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.html
.viewportCount = 1,
.scissorCount = 1
};
constexpr vk::PipelineRasterizationStateCreateInfo rasterizer_create_info { // https://docs.vulkan.org/tutorial/latest/03_Drawing_a_triangle/02_Graphics_pipeline_basics/02_Fixed_functions.html // these require GPU features...
.depthClampEnable = vk::False,
.rasterizerDiscardEnable = vk::False,
.polygonMode = vk::PolygonMode::eFill,
.cullMode = vk::CullModeFlagBits::eBack,
.frontFace = UsesProjection() ? vk::FrontFace::eCounterClockwise : vk::FrontFace::eClockwise,
.depthBiasEnable = vk::False,
.depthBiasConstantFactor = 0.0f, // Optional
.depthBiasClamp = 0.0f, // Optional
.depthBiasSlopeFactor = 0.0f, // Optional
.lineWidth = 1.0f
};
const vk::PipelineMultisampleStateCreateInfo multisampling_create_info {
.rasterizationSamples = msaa_samples,
.sampleShadingEnable = vk::False,
.minSampleShading = 1.0f, // Optional
.pSampleMask = nullptr, // Optional
.alphaToCoverageEnable = vk::False, // Optional
.alphaToOneEnable = vk::False // Optional
};
constexpr vk::PipelineDepthStencilStateCreateInfo depth_stencil_create_info {
.depthTestEnable = vk::True,
.depthWriteEnable = vk::True,
.depthCompareOp = vk::CompareOp::eLess,
.depthBoundsTestEnable = vk::False,
.minDepthBounds = 0.0f, // Optional
.maxDepthBounds = 1.0f, // Optional
};
constexpr vk::PipelineColorBlendAttachmentState color_blend_attachment {
.blendEnable = vk::False,
.srcColorBlendFactor = vk::BlendFactor::eOne, // Optional
.dstColorBlendFactor = vk::BlendFactor::eZero, // Optional
.colorBlendOp = vk::BlendOp::eAdd, // Optional
.srcAlphaBlendFactor = vk::BlendFactor::eOne, // Optional
.dstAlphaBlendFactor = vk::BlendFactor::eZero, // Optional
.alphaBlendOp = vk::BlendOp::eAdd, // Optional
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA
};
const vk::PipelineColorBlendStateCreateInfo color_blending_create_info {
.logicOpEnable = vk::False,
.logicOp = vk::LogicOp::eCopy, // Optional
.attachmentCount = 1,
.pAttachments = &color_blend_attachment,
.blendConstants = std::array {
0.0f,
0.0f, // Optional
0.0f, // Optional
0.0f // Optional
} // Optional
};
constexpr std::array dynamic_states = {
vk::DynamicState::eViewport,
vk::DynamicState::eScissor
};
const vk::PipelineDynamicStateCreateInfo dynamic_state_create_info {
.dynamicStateCount = static_cast(dynamic_states.size()),
.pDynamicStates = dynamic_states.data()
};
const vk::PipelineLayoutCreateInfo pipeline_layout_create_info {
.setLayoutCount = 1, // Optional
.pSetLayouts = &**this->vk_descriptor_set_layouts, // Optional
//.pushConstantRangeCount = 0, // Optional
//.pPushConstantRanges = nullptr // Optional
};
this->vk_pipeline_layout.emplace(gpu, pipeline_layout_create_info);
const vk::GraphicsPipelineCreateInfo pipeline_create_info {
.stageCount = 2,
.pStages = shader_stages_create_info,
.pVertexInputState = &vertex_input_create_info,
.pInputAssemblyState = &input_assembly_create_info,
.pViewportState = &viewport_state,
.pRasterizationState = &rasterizer_create_info,
.pMultisampleState = &multisampling_create_info,
.pDepthStencilState = &depth_stencil_create_info, // Optional
.pColorBlendState = &color_blending_create_info,
.pDynamicState = &dynamic_state_create_info,
.layout = **this->vk_pipeline_layout,
.renderPass = render_pass,
.subpass = 0,
.basePipelineHandle = nullptr, // Optional
.basePipelineIndex = -1 // Optional
};
this->vk_pipeline.emplace(gpu, nullptr, pipeline_create_info);
}
if constexpr (UsesProjection()) { // create and map uniform buffer object
assert(this->vk_buffers_uniform.empty());
assert(this->vk_buffers_uniform_mapped.empty());
this->vk_buffers_uniform.reserve(this->parent->vk_max_frames_in_flight);
this->vk_buffers_uniform_mapped.reserve(this->parent->vk_max_frames_in_flight);
const vk::DeviceSize ubo_buffer_size = sizeof(UniformBufferObject_Projection);
for (std::size_t i = 0; i < this->parent->vk_max_frames_in_flight; i++) {
vma::AllocationInfo alloc_info;
auto uniform_buffer = this->parent->CreateBuffer(ubo_buffer_size, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, &alloc_info);
this->vk_buffers_uniform_mapped.emplace_back(static_cast(alloc_info.pMappedData));
this->vk_buffers_uniform.emplace_back(std::move(uniform_buffer));
}
}
{ // create descriptor pool
const std::size_t ubo_count = 256;
const vk::DescriptorPoolSize descriptor_pool_size_ubo {
.type = vk::DescriptorType::eUniformBuffer,
.descriptorCount = static_cast(this->parent->vk_max_frames_in_flight) * 2
};
const std::size_t maximum_texture_count = 256;
const vk::DescriptorPoolSize descriptor_pool_size_sampler {
.type = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = maximum_texture_count
};
const auto descriptor_pool_sizes = [&]() {
if constexpr (UsesProjection())
return std::array { descriptor_pool_size_sampler, descriptor_pool_size_ubo };
else
return std::array { descriptor_pool_size_sampler };
}();
const vk::DescriptorPoolCreateInfo descriptor_pool_create_info {
.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
.maxSets = maximum_texture_count + (!UsesProjection() ? 0 : ubo_count),
.poolSizeCount = static_cast(descriptor_pool_sizes.size()),
.pPoolSizes = descriptor_pool_sizes.data(),
};
assert(descriptor_pool_create_info.flags & vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet); // requirement, to soothe validation layer complaints
this->vk_descriptor_pool.emplace(*this->parent->vk_gpu, descriptor_pool_create_info);
}
{ // update descriptor sets
std::vector descriptor_set_layouts;
descriptor_set_layouts.reserve(this->parent->vk_max_frames_in_flight + 1);
descriptor_set_layouts.emplace_back(*this->vk_descriptor_set_layouts);
if constexpr (UsesProjection()) {
const auto addition_layouts = std::vector(this->parent->vk_max_frames_in_flight, *this->vk_descriptor_set_layouts);
descriptor_set_layouts.insert(descriptor_set_layouts.end(), addition_layouts.begin(), addition_layouts.end());
}
const vk::DescriptorSetAllocateInfo descriptor_set_allocate_info {
.descriptorPool = **this->vk_descriptor_pool,
.descriptorSetCount = static_cast(descriptor_set_layouts.size()),
.pSetLayouts = descriptor_set_layouts.data()
};
this->vk_descriptor_sets = vk::raii::DescriptorSets(*this->parent->vk_gpu, descriptor_set_allocate_info);
}
if constexpr (UsesProjection()) { // update ubo descripor sets
std::vector write_descriptor_sets;
write_descriptor_sets.reserve(this->parent->vk_max_frames_in_flight);
std::vector buffer_infos;
buffer_infos.reserve(this->parent->vk_max_frames_in_flight);
for (std::size_t i = 0; i < this->parent->vk_max_frames_in_flight; i++) {
const vk::DescriptorBufferInfo descriptor_buffer_info_ubo {
.buffer = *this->vk_buffers_uniform.at(i)->first,
.offset = 0,
.range = sizeof(UniformBufferObject_Projection),
};
const vk::DescriptorBufferInfo& descriptor_buffer_info_handle = buffer_infos.emplace_back(descriptor_buffer_info_ubo);
const vk::WriteDescriptorSet descriptor_write_ubo {
.dstSet = *this->vk_descriptor_sets[i],
.dstBinding = 1,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eUniformBuffer,
.pImageInfo = nullptr, // Optional
.pBufferInfo = &descriptor_buffer_info_handle,
.pTexelBufferView = nullptr, // Optional
};
write_descriptor_sets.emplace_back(descriptor_write_ubo);
}
std::cout << "UBO DESCRIPTORS" << std::endl;
this->parent->vk_gpu->updateDescriptorSets(write_descriptor_sets, nullptr);
}
}
vk::raii::DescriptorSet CreateImageDescriptorSet(const vk::raii::ImageView& texture_view, const vk::raii::Sampler& texture_sampler) const {
const vk::DescriptorSetAllocateInfo descriptor_set_allocate_info {
.descriptorPool = **this->vk_descriptor_pool,
.descriptorSetCount = 1,
.pSetLayouts = &**this->vk_descriptor_set_layouts
};
vk::raii::DescriptorSet descriptor_set = std::move(vk::raii::DescriptorSets(*this->parent->vk_gpu, descriptor_set_allocate_info).front());
const vk::DescriptorImageInfo descriptor_image_info_texture {
.sampler = texture_sampler,
.imageView = texture_view,
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
};
const vk::WriteDescriptorSet descriptor_write_texture {
.dstSet = *descriptor_set,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.pImageInfo = &descriptor_image_info_texture
};
std::cout << "SAMPLER_TEXTURES DESCRIPTORS" << std::endl;
this->parent->vk_gpu->updateDescriptorSets({ descriptor_write_texture }, nullptr);
return descriptor_set;
}
void ReplaceModelInfo(const std::vector& vertices, const std::vector& indexes) {
const auto vertex_buffer_size = sizeof(VertexT) * vertices.size();
const auto vertex_buffer_staging = this->parent->CreateBuffer(vertex_buffer_size, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
this->parent->vk_allocator->copyMemoryToAllocation(vertices.data(), *vertex_buffer_staging->second, 0, vertex_buffer_size);
this->vk_buffer_vertex = this->parent->CreateBuffer(vertex_buffer_size, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal);
this->parent->CopyBuffer(*vertex_buffer_staging->first, *this->vk_buffer_vertex->first, vertex_buffer_size);
// index
const auto index_array_size = indexes.size();
const auto index_buffer_size = sizeof(IndexT) * index_array_size;
const auto index_buffer_staging = this->parent->CreateBuffer(index_buffer_size, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
this->parent->vk_allocator->copyMemoryToAllocation(indexes.data(), *index_buffer_staging->second, 0, index_buffer_size);
this->vk_buffer_index = this->parent->CreateBuffer(index_buffer_size, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal);
this->parent->CopyBuffer(*index_buffer_staging->first, *this->vk_buffer_index->first, index_buffer_size);
this->vk_buffer_index_size = index_array_size;
}
void UpdateUniformBuffer(const std::uint32_t current_image) const {
if constexpr (decltype(VertexT().pos)::length() == 3) {
static auto start_time = std::chrono::high_resolution_clock::now();
const auto current_time = std::chrono::high_resolution_clock::now();
const float time = std::chrono::duration(current_time - start_time).count();
UniformBufferObject_Projection ubo; // https://docs.vulkan.org/tutorial/latest/05_Uniform_buffers/00_Descriptor_set_layout_and_buffer.html#_updating_uniform_data
{
ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));
ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
ubo.proj = glm::perspective(glm::radians(45.0f), this->parent->vk_swapchain_extent.width / (float)this->parent->vk_swapchain_extent.height, 0.1f, 10.0f);
ubo.proj[1][1] *= -1; // "GLM was originally designed for OpenGL, where the Y coordinate of the clip coordinates is inverted." - so it will flip the image to correct.
}
memcpy(this->vk_buffers_uniform_mapped[current_image], &ubo, sizeof(ubo));
} else {
static_assert(false && "Uniform Buffer Object only available using 3d");
}
}
void BindTexture(const Texture& t) {
this->current_texture = &t;
}
void Bind(const vk::raii::CommandBuffer& command_buffer, const std::size_t frame_index) const {
assert(this->current_texture != nullptr);
command_buffer.bindPipeline(vk::PipelineBindPoint::eGraphics, **this->vk_pipeline);
const auto descriptor_sets = std::array { *this->vk_descriptor_sets[frame_index], this->current_texture->GetDescriptorSet() };
command_buffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *this->vk_pipeline_layout, 0, descriptor_sets, nullptr);
command_buffer.bindVertexBuffers(0, { *this->vk_buffer_vertex->first }, { 0 });
command_buffer.bindIndexBuffer(*this->vk_buffer_index->first, 0, vk::IndexType::eUint16);
}
void Draw(const vk::raii::CommandBuffer& command_buffer) const {
command_buffer.drawIndexed(static_cast(this->vk_buffer_index_size), 1, 0, 0, 0);
}
};
VulkanRenderPipeline vk_pipeline_second;
VulkanRenderPipeline vk_pipeline_third;
static inline const std::vector vertices_rect_sample = {
{ { -0.2f, -0.2f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f }, DrawMode::kTextured },
{ { 0.2f, -0.2f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 0.0f }, DrawMode::kTextured },
{ { 0.2f, 0.2f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f }, DrawMode::kTextured },
{ { -0.2f, 0.2f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.0f, 1.0f }, DrawMode::kTextured }
};
static inline const std::vector vertices_cube_sample = {
{ { -0.5f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f }, DrawMode::kTextured },
{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f }, { 1.0f, 0.0f }, DrawMode::kTextured },
{ { 0.5f, 0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f }, { 1.0f, 1.0f }, DrawMode::kTextured },
{ { -0.5f, 0.5f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.0f, 1.0f }, DrawMode::kTextured },
{ { -0.5f, -0.5f, -0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f }, DrawMode::kTextured },
{ { 0.5f, -0.5f, -0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f }, { 1.0f, 0.0f }, DrawMode::kTextured },
{ { 0.5f, 0.5f, -0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f }, { 1.0f, 1.0f }, DrawMode::kTextured },
{ { -0.5f, 0.5f, -0.5f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.0f, 1.0f }, DrawMode::kTextured }
};
private:
vk::SampleCountFlagBits vk_msaa_samples_wanted = vk::SampleCountFlagBits::e8;
vk::SampleCountFlagBits vk_msaa_samples;
VulkanImage vk_color_image;
std::optional vk_color_view;
private:
vk::Format vk_depth_format;
VulkanImage vk_depth_image;
std::optional vk_depth_view;
private:
std::optional vk_texture;
public:
VulkanExampleApplication()
: vk_pipeline_second(this)
, vk_pipeline_third(this) {
try { // try me // all the code is in init :O boo hoo, ill shove all ur ram out of scope ASAP i can, dont seperate them, just scope them instead dumb dumb.
{ // Window init
this->libsdl.emplace(SDL_INIT_VIDEO);
SDL_Vulkan_LoadLibrary(nullptr); // optional
this->window.emplace(this->app_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 480, SDL_WINDOW_VULKAN | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
}
{ // Vulkan Init
this->vk_ctx.emplace();
#if defined(ENABLE_VULKAN_VALIDATION)
const std::vector available_layers = this->vk_ctx->enumerateInstanceLayerProperties();
for (const auto& required_layer_name : required_vulkan_validation_layers) {
const auto find_wanted_layer = std::find_if(available_layers.begin(), available_layers.end(), [&](const auto& layer_to_test) { return std::string_view(layer_to_test.layerName) == required_layer_name; });
if (find_wanted_layer == available_layers.end())
throw std::runtime_error("validation layers requested, but not available!");
}
#endif
}
{ // Setup vulkan
constexpr vk::ApplicationInfo app_info {
.pApplicationName = this->app_name,
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
.pEngineName = "No Engine",
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
.apiVersion = VK_API_VERSION_1_3
};
const std::vector required_extensions = [this]() -> std::vector {
std::uint32_t extension_count = 0;
SDL_Vulkan_GetInstanceExtensions(this->window->Get(), &extension_count, nullptr);
std::vector ret_extensions(extension_count);
SDL_Vulkan_GetInstanceExtensions(this->window->Get(), &extension_count, ret_extensions.data());
if (vulkan_enable_validation_layers)
ret_extensions.emplace_back(vk::EXTDebugUtilsExtensionName);
return ret_extensions;
}();
const vk::InstanceCreateInfo instance_create_info {
#if defined(ENABLE_VULKAN_VALIDATION)
.pNext = &debug_message_info,
#endif
.pApplicationInfo = &app_info,
.enabledLayerCount = required_vulkan_validation_layers.size(),
.ppEnabledLayerNames = required_vulkan_validation_layers.data(),
.enabledExtensionCount = static_cast(required_extensions.size()),
.ppEnabledExtensionNames = required_extensions.data()
};
this->vk_inst.emplace(*this->vk_ctx, instance_create_info);
#if defined(ENABLE_VULKAN_VALIDATION)
this->vk_debug_utils_messenger.emplace(*this->vk_inst, debug_message_info);
// SDL specific, optional (does it even work?)
/*constexpr vk::DebugReportCallbackCreateInfoEXT debug_callback_create_info = {
.flags = vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning,
.pfnCallback = VulkanReportCallback
};
static const auto SDL2_vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)SDL_Vulkan_GetVkGetInstanceProcAddr(); // https://gist.github.com/YukiSnowy/dc31f47448ac61dd6aedee18b5d53858
assert(SDL2_vkCreateDebugReportCallbackEXT);
VkDebugReportCallbackEXT debug_report_callback;
SDL2_vkCreateDebugReportCallbackEXT(**this->vk_inst, &static_cast(debug_callback_create_info), 0, &debug_report_callback);
this->vk_debug_report_callback = std::make_unique(*this->vk_inst, debug_report_callback);*/
#endif
}
{ // Pick device
auto physical_cards = vk::raii::PhysicalDevices(*this->vk_inst);
if (physical_cards.empty())
throw std::runtime_error("failed to find GFX CARD with Vulkan support!");
int gfx_card_score = 0;
std::optional found_search_info;
decltype(physical_cards.begin()) found_physical_card = physical_cards.end();
for (auto i = physical_cards.begin(); i != physical_cards.end(); i++) {
const auto& gfx_card = *i;
int cur_score = 0;
const auto device_properties = gfx_card.getProperties();
const auto device_features = gfx_card.getFeatures();
const auto queue_families = gfx_card.getQueueFamilyProperties();
const bool is_swrast = device_properties.deviceType == vk::PhysicalDeviceType::eCpu;
const bool is_gpurast = device_properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu;
if (!(((allow_swrast && is_swrast) || is_gpurast) && device_features.geometryShader))
continue;
if (is_swrast)
cur_score += 5;
if (is_gpurast)
cur_score += 15;
const auto found_graphics_family = std::find_if(queue_families.begin(), queue_families.end(), [](const auto& queue_family_to_test) {
return queue_family_to_test.queueFlags & vk::QueueFlagBits::eGraphics;
});
if (found_graphics_family == queue_families.end())
continue;
VulkanDeviceQueriedInfo gfx_card_info;
const auto ReturnScore = [&]() {
if (gfx_card_info.IsGoodCard()) {
if (cur_score > gfx_card_score) {
gfx_card_score = cur_score;
found_search_info = std::move(gfx_card_info);
found_physical_card = i;
}
}
};
const auto found_graphics_family_idx = std::distance(queue_families.begin(), found_graphics_family);
gfx_card_info.graphics_family = found_graphics_family_idx;
auto found_screen_surface = [](const SDL2pp::Window& window, const vk::raii::Instance& vk_inst) -> vk::raii::SurfaceKHR {
VkSurfaceKHR _screen_surface;
SDL_Vulkan_CreateSurface(window.Get(), static_cast(static_cast(vk_inst)), &_screen_surface);
return vk::raii::SurfaceKHR(vk_inst, _screen_surface);
}(*this->window, *this->vk_inst);
const auto found_present_family = gfx_card.getSurfaceSupportKHR(found_graphics_family_idx, *found_screen_surface) ? found_graphics_family : [&]() -> decltype(queue_families)::const_iterator { // https://github.com/KhronosGroup/Vulkan-Hpp/blob/6f72ceca515d59f40d64b64cf2734f6261e1f9f2/RAII_Samples/05_InitSwapchain/05_InitSwapchain.cpp#L50
decltype(queue_families)::const_iterator queue_family_to_test;
for (queue_family_to_test = queue_families.begin(); queue_family_to_test != queue_families.end(); queue_family_to_test++) {
const auto queue_family_idx_to_test = std::distance(queue_families.begin(), queue_family_to_test);
if ((queue_family_to_test->queueFlags & vk::QueueFlagBits::eGraphics) && gfx_card.getSurfaceSupportKHR(queue_family_idx_to_test, *found_screen_surface))
break;
}
if (queue_family_to_test == queue_families.end()) {
for (queue_family_to_test = queue_families.begin(); queue_family_to_test != queue_families.end(); queue_family_to_test++) {
const auto queue_family_idx_to_test = std::distance(queue_families.begin(), queue_family_to_test);
if (gfx_card.getSurfaceSupportKHR(queue_family_idx_to_test, *found_screen_surface))
break;
}
}
return queue_family_to_test;
}();
if (found_present_family == queue_families.end()) {
ReturnScore();
continue;
}
const auto found_present_family_idx = std::distance(queue_families.begin(), found_present_family);
gfx_card_info.present_family = found_present_family_idx;
cur_score += 5;
const auto available_extensions = gfx_card.enumerateDeviceExtensionProperties();
const auto DeviceSupportsExtension = [&available_extensions](const std::string_view required_ext_name) -> bool {
return available_extensions.end() != std::find_if(available_extensions.begin(), available_extensions.end(), [&](const auto& ext_to_test) { return std::string_view(ext_to_test.extensionName) == required_ext_name; });
};
{
bool has_all_required_extensions = true;
for (const std::string_view ext_name_required : required_vulkan_device_extensions) {
if (!DeviceSupportsExtension(ext_name_required)) {
has_all_required_extensions = false;
}
}
if (!has_all_required_extensions) {
ReturnScore();
continue;
}
cur_score += 2;
}
const auto vma_feature_sets = std::array, vma::AllocatorCreateFlagBits>, 5> {
std::make_pair(std::vector { "VK_KHR_dedicated_allocation", "VK_KHR_get_memory_requirements2" }, vma::AllocatorCreateFlagBits::eKhrDedicatedAllocation),
{ { "VK_KHR_bind_memory2" }, vma::AllocatorCreateFlagBits::eKhrBindMemory2 },
{ { "VK_KHR_maintenance4" }, vma::AllocatorCreateFlagBits::eKhrMaintenance4 },
{ { "VK_KHR_maintenance5" }, vma::AllocatorCreateFlagBits::eKhrMaintenance5 },
{ { "VK_EXT_memory_budget", "VK_KHR_get_physical_device_properties2" }, vma::AllocatorCreateFlagBits::eExtMemoryBudget }
};
for (const auto& wanted_vma_feature_set : vma_feature_sets) {
bool has_all_required_features = true;
for (const auto& required_device_feature : wanted_vma_feature_set.first)
if (!DeviceSupportsExtension(required_device_feature))
has_all_required_features = false;
if (has_all_required_features) {
for (const auto& required_device_feature : wanted_vma_feature_set.first)
gfx_card_info.supported_features_vma_device.emplace_back(required_device_feature);
gfx_card_info.supported_features_vma_allocator.emplace_back(wanted_vma_feature_set.second);
cur_score += 2;
}
}
if (!device_features.samplerAnisotropy) {
ReturnScore();
continue;
}
cur_score += 1;
const auto surface_present_modes = gfx_card.getSurfacePresentModesKHR(*found_screen_surface);
const auto surface_formats = gfx_card.getSurfaceFormatsKHR(*found_screen_surface);
if (surface_formats.empty() || surface_present_modes.empty()) {
ReturnScore();
continue;
}
cur_score += 5;
const auto GetMaxUseableSampleCount = [&device_properties]() -> vk::SampleCountFlagBits {
vk::SampleCountFlags counts = device_properties.limits.framebufferColorSampleCounts & device_properties.limits.framebufferDepthSampleCounts;
if (counts & vk::SampleCountFlagBits::e64)
return vk::SampleCountFlagBits::e64;
if (counts & vk::SampleCountFlagBits::e32)
return vk::SampleCountFlagBits::e32;
if (counts & vk::SampleCountFlagBits::e16)
return vk::SampleCountFlagBits::e16;
if (counts & vk::SampleCountFlagBits::e8)
return vk::SampleCountFlagBits::e8;
if (counts & vk::SampleCountFlagBits::e4)
return vk::SampleCountFlagBits::e4;
if (counts & vk::SampleCountFlagBits::e2)
return vk::SampleCountFlagBits::e2;
return vk::SampleCountFlagBits::e1;
};
gfx_card_info.screen_surface.emplace(std::move(found_screen_surface));
gfx_card_info.surface_has_format_modes = !surface_formats.empty();
gfx_card_info.surface_has_present_modes = !surface_present_modes.empty();
gfx_card_info.msaa_samples = GetMaxUseableSampleCount();
ReturnScore();
}
if (found_physical_card == physical_cards.end() || !found_search_info.has_value() || !found_search_info->IsGoodCard())
throw std::runtime_error("Unable to choose a Suitable GRAPHICS DEVICE from the ones availiable!");
this->vk_gfx_card.emplace(std::move(*found_physical_card));
this->vk_screen_surface.emplace(std::move(*found_search_info->screen_surface));
this->vk_physical_card_info = std::move(*found_search_info);
}
{ // Setup device
static constexpr float queue_priority = 1.0f;
const std::vector device_queue_create_infos = [](const std::uint32_t graphics_family, const std::uint32_t present_family) {
std::vector device_queues_we_need;
device_queues_we_need.reserve(2);
const auto GetQueuesToUse = [&graphics_family, &present_family]() {
std::vector wanted_queues = { graphics_family };
wanted_queues.reserve(2);
if (graphics_family != present_family)
wanted_queues.emplace_back(present_family);
return wanted_queues;
};
for (const std::uint32_t queue_family : GetQueuesToUse()) {
const vk::DeviceQueueCreateInfo device_queue_create_info {
.queueFamilyIndex = queue_family,
.queueCount = 1,
.pQueuePriorities = &queue_priority
};
device_queues_we_need.emplace_back(device_queue_create_info);
}
return device_queues_we_need;
}(this->vk_physical_card_info.graphics_family.value(), this->vk_physical_card_info.present_family.value());
const std::vector enabled_extensions_device = [this]() -> std::vector {
std::vector enabled_extensions(required_vulkan_device_extensions.begin(), required_vulkan_device_extensions.end());
for (const auto& i : this->vk_physical_card_info.supported_features_vma_device)
enabled_extensions.emplace_back(i.data());
return enabled_extensions;
}();
constexpr vk::PhysicalDeviceFeatures device_features {
.samplerAnisotropy = vk::True
};
const vk::DeviceCreateInfo device_create_info {
.queueCreateInfoCount = static_cast(device_queue_create_infos.size()),
.pQueueCreateInfos = device_queue_create_infos.data(),
.enabledExtensionCount = static_cast(enabled_extensions_device.size()),
.ppEnabledExtensionNames = enabled_extensions_device.data(),
.pEnabledFeatures = &device_features,
};
this->vk_gpu.emplace(*this->vk_gfx_card, device_create_info);
this->vk_queue_graphics.emplace(*this->vk_gpu, this->vk_physical_card_info.graphics_family.value(), 0);
this->vk_queue_present.emplace(*this->vk_gpu, this->vk_physical_card_info.present_family.value(), 0);
}
{ // initialize the vulkan memory allocator
// TODO: enable extensions... https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/quick_start.html#quick_start_initialization
const auto dispatcher_inst = this->vk_inst->getDispatcher();
const auto dispatcher_gpu = this->vk_gpu->getDispatcher();
const vma::VulkanFunctions vulkan_functions {
.vkGetInstanceProcAddr = dispatcher_inst->vkGetInstanceProcAddr,
.vkGetDeviceProcAddr = dispatcher_gpu->vkGetDeviceProcAddr,
.vkGetPhysicalDeviceProperties = dispatcher_inst->vkGetPhysicalDeviceProperties,
.vkGetPhysicalDeviceMemoryProperties = dispatcher_inst->vkGetPhysicalDeviceMemoryProperties,
.vkAllocateMemory = dispatcher_gpu->vkAllocateMemory,
.vkFreeMemory = dispatcher_gpu->vkFreeMemory,
.vkMapMemory = dispatcher_gpu->vkMapMemory,
.vkUnmapMemory = dispatcher_gpu->vkUnmapMemory,
.vkFlushMappedMemoryRanges = dispatcher_gpu->vkFlushMappedMemoryRanges,
.vkInvalidateMappedMemoryRanges = dispatcher_gpu->vkInvalidateMappedMemoryRanges,
.vkBindBufferMemory = dispatcher_gpu->vkBindBufferMemory,
.vkBindImageMemory = dispatcher_gpu->vkBindImageMemory,
.vkGetBufferMemoryRequirements = dispatcher_gpu->vkGetBufferMemoryRequirements,
.vkGetImageMemoryRequirements = dispatcher_gpu->vkGetImageMemoryRequirements,
.vkCreateBuffer = dispatcher_gpu->vkCreateBuffer,
.vkDestroyBuffer = dispatcher_gpu->vkDestroyBuffer,
.vkCreateImage = dispatcher_gpu->vkCreateImage,
.vkDestroyImage = dispatcher_gpu->vkDestroyImage,
.vkCmdCopyBuffer = dispatcher_gpu->vkCmdCopyBuffer,
.vkGetBufferMemoryRequirements2KHR = dispatcher_gpu->vkGetBufferMemoryRequirements2KHR,
};
const vma::AllocatorCreateInfo allocator_create_info = {
.flags = [&]() -> vma::AllocatorCreateFlags {
vma::AllocatorCreateFlags enabled_flags;
for (const auto& wanted_feature : this->vk_physical_card_info.supported_features_vma_allocator)
enabled_flags |= wanted_feature;
return enabled_flags;
}(),
.physicalDevice = **this->vk_gfx_card,
.device = **this->vk_gpu,
.pVulkanFunctions = &vulkan_functions,
.instance = **this->vk_inst
};
this->vk_allocator = vma::createAllocatorUnique(allocator_create_info);
}
{
this->CreateSwapchain();
}
{
this->CreateBufferColor();
}
{ // find depth format, needed before a few things
this->CreateBufferDepth();
}
{ // init renderpass
const vk::AttachmentDescription attachment_description_color {
.format = this->vk_swapchain_image_format,
.samples = this->vk_msaa_samples,
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eStore,
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
.initialLayout = vk::ImageLayout::eUndefined,
.finalLayout = vk::ImageLayout::eColorAttachmentOptimal
};
const vk::AttachmentDescription attachment_description_color_resolve {
.format = this->vk_swapchain_image_format,
.samples = vk::SampleCountFlagBits::e1,
.loadOp = vk::AttachmentLoadOp::eDontCare,
.storeOp = vk::AttachmentStoreOp::eStore,
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
.initialLayout = vk::ImageLayout::eUndefined,
.finalLayout = vk::ImageLayout::ePresentSrcKHR
};
const vk::AttachmentDescription attachment_description_depth {
.format = this->vk_depth_format,
.samples = this->vk_msaa_samples,
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eDontCare,
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
.initialLayout = vk::ImageLayout::eUndefined,
.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal
};
constexpr vk::AttachmentReference attachment_ref_color {
.attachment = 0,
.layout = vk::ImageLayout::eColorAttachmentOptimal,
};
constexpr vk::AttachmentReference attachment_ref_color_resolve {
.attachment = 1,
.layout = vk::ImageLayout::eColorAttachmentOptimal
};
constexpr vk::AttachmentReference attachment_ref_depth {
.attachment = 2,
.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal
};
const vk::SubpassDescription subpass_description {
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
.colorAttachmentCount = 1,
.pColorAttachments = &attachment_ref_color,
.pResolveAttachments = &attachment_ref_color_resolve,
.pDepthStencilAttachment = &attachment_ref_depth
};
constexpr vk::SubpassDependency subpass_dependency {
.srcSubpass = vk::SubpassExternal,
.dstSubpass = 0,
.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eLateFragmentTests,
.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests,
.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite
};
const std::array attachment_descriptions = { attachment_description_color, attachment_description_color_resolve, attachment_description_depth };
const vk::RenderPassCreateInfo render_pass_create_info {
.attachmentCount = static_cast(attachment_descriptions.size()),
.pAttachments = attachment_descriptions.data(),
.subpassCount = 1,
.pSubpasses = &subpass_description,
.dependencyCount = 1,
.pDependencies = &subpass_dependency
};
this->vk_render_pass.emplace(*this->vk_gpu, render_pass_create_info);
}
{
this->vk_pipeline_second.CreatePipeline(*this->vk_gpu, *this->vk_render_pass, this->vk_msaa_samples);
this->vk_pipeline_third.CreatePipeline(*this->vk_gpu, *this->vk_render_pass, this->vk_msaa_samples);
}
{
this->CreateBufferFrame();
}
{ // command pool setup
const vk::CommandPoolCreateInfo command_pool_create_info { // https://docs.vulkan.org/tutorial/latest/03_Drawing_a_triangle/03_Drawing/01_Command_buffers.html
.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
.queueFamilyIndex = this->vk_physical_card_info.graphics_family.value()
};
this->vk_command_pool.emplace(*this->vk_gpu, command_pool_create_info);
const vk::CommandBufferAllocateInfo command_buffer_alloc_info {
.commandPool = **this->vk_command_pool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = static_cast(this->vk_max_frames_in_flight)
};
this->vk_command_buffers = vk::raii::CommandBuffers(*this->vk_gpu, command_buffer_alloc_info);
}
{
this->TransitionImageLayout(*this->vk_depth_image->first, this->vk_depth_format, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal);
}
{ // fill sample vertex data
this->vk_pipeline_second.ReplaceModelInfo(vertices_rect_sample, Vertex2::IndexMap::rectangle);
this->vk_pipeline_third.ReplaceModelInfo(vertices_cube_sample, Vertex3::IndexMap::rectangle);
}
{ // syncronizing vars
assert(this->vk_semephores_image_available.empty());
assert(this->vk_semephores_render_finished.empty());
assert(this->vk_fences_in_flight.empty());
this->vk_semephores_image_available.reserve(this->vk_max_frames_in_flight);
this->vk_semephores_render_finished.reserve(this->vk_max_frames_in_flight);
this->vk_fences_in_flight.reserve(this->vk_max_frames_in_flight);
for (size_t i = 0; i < this->vk_max_frames_in_flight; i++) {
this->vk_semephores_image_available.emplace_back(*this->vk_gpu, vk::SemaphoreCreateInfo {});
this->vk_semephores_render_finished.emplace_back(*this->vk_gpu, vk::SemaphoreCreateInfo {});
this->vk_fences_in_flight.emplace_back(*this->vk_gpu, vk::FenceCreateInfo { .flags = vk::FenceCreateFlagBits::eSignaled });
}
}
{ // texture
this->vk_texture.emplace(this, embeded_debug_north_png_rgba.data.begin, embeded_debug_north_png_rgba.data.size, vk::Extent2D { .width = static_cast(embeded_debug_north_png_rgba.width), .height = static_cast(embeded_debug_north_png_rgba.height) });
this->vk_pipeline_second.BindTexture(*this->vk_texture);
this->vk_pipeline_third.BindTexture(*this->vk_texture);
}
} catch (const vk::SystemError& error) {
std::cout << "Received Vulkan-Specific Error during process init: " << error.what() << std::endl
<< "Going to rethrow!" << std::endl;
throw error;
} catch (const std::exception& error) {
std::cout << "Received Error during process init: " << error.what() << std::endl
<< "Going to rethrow!" << std::endl;
throw error;
} catch (...) {
std::cout << "Received Unknown Error during process init..." << std::endl;
throw "Unknown Exception, throwing...";
}
}
private:
void RecreateSwapchain() {
this->vk_gpu->waitIdle();
this->CreateSwapchain();
this->CreateBufferColor();
this->CreateBufferDepth();
this->CreateBufferFrame();
}
void CreateSwapchain() {
assert(this->vk_gfx_card);
assert(this->vk_screen_surface);
assert(this->vk_gpu);
const vk::SurfaceFormatKHR surface_format = [](const std::vector& available_formats) -> vk::SurfaceFormatKHR {
vk::SurfaceFormatKHR chosen_format = available_formats.at(0);
if (available_formats.size() == 1) {
if (available_formats[0].format == vk::Format::eUndefined) {
chosen_format.format = required_vulkan_default_format;
chosen_format.colorSpace = required_vulkan_default_screen_colorspace;
}
} else {
for (const auto& format_we_want : required_vulkan_screen_formats) {
const auto find = std::find_if(available_formats.begin(), available_formats.end(), [&](const auto& format_to_test) { return format_to_test.format == format_we_want && format_to_test.colorSpace == required_vulkan_screen_colorspace; });
if (find != available_formats.end()) {
chosen_format = *find;
break;
}
}
}
return chosen_format;
}(this->vk_gfx_card->getSurfaceFormatsKHR(*this->vk_screen_surface));
const vk::PresentModeKHR present_mode = [](const std::vector& available_present_modes) -> vk::PresentModeKHR {
for (const auto& present_mode_to_test : available_present_modes)
if (present_mode_to_test == vk::PresentModeKHR::eMailbox)
return present_mode_to_test;
return vk::PresentModeKHR::eFifo;
}(this->vk_gfx_card->getSurfacePresentModesKHR(*this->vk_screen_surface));
const auto surface_capabilities = this->vk_gfx_card->getSurfaceCapabilitiesKHR(*this->vk_screen_surface);
const vk::Extent2D extent = [this](const vk::SurfaceCapabilitiesKHR& capabilities) -> vk::Extent2D {
if (capabilities.currentExtent.width != std::numeric_limits::max()) {
return capabilities.currentExtent;
} else {
int sdl_width = 0, sdl_height = 0;
SDL_Vulkan_GetDrawableSize(this->window->Get(), &sdl_width, &sdl_height); // using equivalent of glfwGetFramebufferSize(window, &width, &height); // https://wiki.libsdl.org/SDL2/SDL_Vulkan_GetDrawableSize
return {
std::clamp(static_cast(sdl_width), capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
std::clamp(static_cast(sdl_height), capabilities.minImageExtent.height, capabilities.maxImageExtent.height)
};
}
}(surface_capabilities);
const std::uint32_t image_count = [&surface_capabilities]() {
std::uint32_t image_count = surface_capabilities.minImageCount + 1;
if (surface_capabilities.maxImageCount > 0 && image_count > surface_capabilities.maxImageCount)
image_count = surface_capabilities.maxImageCount;
return image_count;
}();
const auto swapchain_create_info = [&]() -> vk::SwapchainCreateInfoKHR {
vk::SwapchainCreateInfoKHR swapchain_create_info {
.surface = **this->vk_screen_surface,
.minImageCount = image_count,
.imageFormat = surface_format.format,
.imageColorSpace = surface_format.colorSpace,
.imageExtent = extent,
.imageArrayLayers = 1,
.imageUsage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc,
.preTransform = surface_capabilities.currentTransform,
.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque, // for window manager, we dont need transparency with windows...
.presentMode = present_mode,
.clipped = vk::True,
.oldSwapchain = this->vk_swapchain ? **this->vk_swapchain : nullptr
};
const std::array queue_family_indices = { this->vk_physical_card_info.graphics_family.value(), this->vk_physical_card_info.present_family.value() };
if ((this->vk_physical_card_info.graphics_family.has_value() && this->vk_physical_card_info.present_family.has_value()) && this->vk_physical_card_info.graphics_family.value() != this->vk_physical_card_info.present_family.value()) { // https://docs.vulkan.org/tutorial/latest/03_Drawing_a_triangle/01_Presentation/01_Swap_chain.html#_creating_the_swap_chain
swapchain_create_info.imageSharingMode = vk::SharingMode::eConcurrent;
swapchain_create_info.queueFamilyIndexCount = queue_family_indices.size();
swapchain_create_info.pQueueFamilyIndices = queue_family_indices.data();
} else {
swapchain_create_info.imageSharingMode = vk::SharingMode::eExclusive;
swapchain_create_info.queueFamilyIndexCount = 0; // Optional
swapchain_create_info.pQueueFamilyIndices = nullptr; // Optional
}
return swapchain_create_info;
}();
auto new_swapchain = vk::raii::SwapchainKHR(*this->vk_gpu, swapchain_create_info);
this->vk_swapchain.emplace(std::move(new_swapchain));
auto swapchain_images = this->vk_swapchain->getImages();
this->vk_swapchain_image_format = surface_format.format;
this->vk_swapchain_extent = extent;
this->vk_swapchain_image_views.clear();
this->vk_swapchain_image_views.reserve(swapchain_images.size());
for (const auto& image : swapchain_images) // requires images
this->vk_swapchain_image_views.emplace_back(this->CreateImageView(image, this->vk_swapchain_image_format, vk::ImageAspectFlagBits::eColor));
}
void CreateBufferColor() {
assert(this->vk_swapchain);
const auto ClampToMaxSupportedMSAA = [this](vk::SampleCountFlagBits sample) -> vk::SampleCountFlagBits {
const auto max_clamp = this->vk_physical_card_info.msaa_samples;
if (sample > max_clamp)
sample = *max_clamp;
return sample;
};
this->vk_msaa_samples = ClampToMaxSupportedMSAA(this->vk_msaa_samples_wanted);
this->vk_color_image = this->CreateImage(this->vk_swapchain_extent, this->vk_swapchain_image_format, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransientAttachment | vk::ImageUsageFlagBits::eColorAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, 1, this->vk_msaa_samples);
this->vk_color_view = this->CreateImageView(*this->vk_color_image->first, this->vk_swapchain_image_format, vk::ImageAspectFlagBits::eColor);
}
void CreateBufferDepth() {
/*const auto HasStencilComponent = [](vk::Format format) {
return format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint;
};*/
const auto FindSupportedFormat = [&](const std::vector& wanted_candidates, vk::ImageTiling wanted_tiling, vk::FormatFeatureFlags wanted_features) -> vk::Format {
for (const auto& format : wanted_candidates) {
const vk::FormatProperties props = this->vk_gfx_card->getFormatProperties(format);
if (wanted_tiling == vk::ImageTiling::eLinear && (props.linearTilingFeatures & wanted_features) == wanted_features)
return format;
else if (wanted_tiling == vk::ImageTiling::eOptimal && (props.optimalTilingFeatures & wanted_features) == wanted_features)
return format;
}
throw std::runtime_error("failed to find supported format!");
};
this->vk_depth_format = FindSupportedFormat({ vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint }, vk::ImageTiling::eOptimal, vk::FormatFeatureFlagBits::eDepthStencilAttachment);
this->vk_depth_image = this->CreateImage(this->vk_swapchain_extent, this->vk_depth_format, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eDepthStencilAttachment, vk::MemoryPropertyFlagBits::eDeviceLocal, 1, this->vk_msaa_samples);
this->vk_depth_view.emplace(this->CreateImageView(*this->vk_depth_image->first, this->vk_depth_format, vk::ImageAspectFlagBits::eDepth));
}
void CreateBufferFrame() {
assert(this->vk_depth_image);
assert(this->vk_render_pass);
this->vk_swapchain_framebuffers.clear();
this->vk_swapchain_framebuffers.reserve(this->vk_swapchain_image_views.size());
for (const auto& i : this->vk_swapchain_image_views) {
const std::array attachments = { **this->vk_color_view, *i, **this->vk_depth_view };
const vk::FramebufferCreateInfo framebuffer_create_info {
.renderPass = **this->vk_render_pass,
.attachmentCount = attachments.size(),
.pAttachments = attachments.data(),
.width = this->vk_swapchain_extent.width,
.height = this->vk_swapchain_extent.height,
.layers = 1
};
this->vk_swapchain_framebuffers.emplace_back(*this->vk_gpu, framebuffer_create_info);
}
}
protected:
vma::AllocationCreateFlags GetAllocationFlags(const vk::BufferUsageFlags& wanted_usage, const vk::MemoryPropertyFlags& wanted_properties) const {
vma::AllocationCreateFlags ret;
if (wanted_properties & vk::MemoryPropertyFlagBits::eHostVisible)
ret |= vma::AllocationCreateFlagBits::eHostAccessSequentialWrite;
else
ret |= vma::AllocationCreateFlagBits::eDedicatedMemory;
if (wanted_usage & vk::BufferUsageFlagBits::eUniformBuffer)
ret |= vma::AllocationCreateFlagBits::eMapped;
return ret;
}
VulkanBuffer CreateBuffer(const vk::DeviceSize& wanted_size, const vk::BufferUsageFlags& wanted_usage, const vk::MemoryPropertyFlags& wanted_properties, vma::AllocationInfo* allocation_info_return = nullptr) const {
const vk::BufferCreateInfo buffer_create_info {
.size = wanted_size,
.usage = wanted_usage,
.sharingMode = vk::SharingMode::eExclusive
};
const vma::AllocationCreateInfo allocation_create_info = {
.flags = this->GetAllocationFlags(wanted_usage, wanted_properties),
.usage = vma::MemoryUsage::eAuto,
.requiredFlags = wanted_properties,
};
return (allocation_info_return != nullptr) ? this->vk_allocator->createBufferUnique(buffer_create_info, allocation_create_info, allocation_info_return) : this->vk_allocator->createBufferUnique(buffer_create_info, allocation_create_info);
};
VulkanImage CreateImage(const vk::Extent2D& image_size, const vk::Format& wanted_format, const vk::ImageTiling& wanted_tiling, const vk::ImageUsageFlags& wanted_usage, const vk::MemoryPropertyFlags& wanted_properties, const std::uint32_t mip_levels = 1, const vk::SampleCountFlagBits& msaa_samples = vk::SampleCountFlagBits::e1) const {
const vk::ImageCreateInfo image_create_info {
.imageType = vk::ImageType::e2D,
.format = wanted_format,
.extent {
.width = image_size.width,
.height = image_size.height,
.depth = 1,
},
.mipLevels = mip_levels,
.arrayLayers = 1,
.samples = msaa_samples,
.tiling = wanted_tiling, // https://docs.vulkan.org/tutorial/latest/06_Texture_mapping/00_Images.html#_texture_image
.usage = wanted_usage,
.sharingMode = vk::SharingMode::eExclusive,
.initialLayout = vk::ImageLayout::eUndefined,
//.flags = 0, // optional
};
const vma::AllocationCreateInfo allocation_create_info = {
.usage = vma::MemoryUsage::eAuto,
.requiredFlags = wanted_properties
};
return this->vk_allocator->createImageUnique(image_create_info, allocation_create_info);
}
vk::raii::ImageView CreateImageView(const vk::Image& image_src, const vk::Format& wanted_format, const vk::ImageAspectFlags& flag_mask, const std::uint32_t mip_levels = 1) const {
const vk::ImageViewCreateInfo imageview_create_info {
.image = image_src,
.viewType = vk::ImageViewType::e2D,
.format = wanted_format,
.components = {
.r = vk::ComponentSwizzle::eIdentity,
.g = vk::ComponentSwizzle::eIdentity,
.b = vk::ComponentSwizzle::eIdentity,
.a = vk::ComponentSwizzle::eIdentity },
.subresourceRange = { .aspectMask = flag_mask, .baseMipLevel = 0, .levelCount = mip_levels, .baseArrayLayer = 0, .layerCount = 1 },
};
return vk::raii::ImageView(*this->vk_gpu, imageview_create_info);
}
void CopyBuffer(const vk::CommandBuffer& command_buffer, const vk::Buffer& src_buffer, const vk::Buffer& dest_buffer, const vk::DeviceSize& size) const {
const vk::BufferCopy copy_region {
//.srcOffset = 0, // Optional
//.dstOffset = 0, // Optional
.size = size
};
command_buffer.copyBuffer(src_buffer, dest_buffer, { copy_region });
}
void CopyBuffer(const vk::Buffer& src_buffer, const vk::Buffer& dest_buffer, const vk::DeviceSize& size) const {
this->SingleTimeSubmitCommand([&](const auto& command_buffer) {
this->CopyBuffer(command_buffer, src_buffer, dest_buffer, size);
});
};
void CopyBuffer(const vk::CommandBuffer& command_buffer, const vk::Buffer& src_buffer, const vk::Image& dest_buffer, const vk::Extent2D& size) const {
const vk::BufferImageCopy copy_region {
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageOffset = { 0, 0, 0 },
.imageExtent = { size.width, size.height, 1 }
};
command_buffer.copyBufferToImage(src_buffer, dest_buffer, vk::ImageLayout::eTransferDstOptimal, copy_region);
}
void CopyBuffer(const vk::Buffer& src_buffer, const vk::Image& dest_buffer, const vk::Extent2D& size) const {
this->SingleTimeSubmitCommand([&](const auto& command_buffer) {
this->CopyBuffer(command_buffer, src_buffer, dest_buffer, size);
});
}
template
void SingleTimeSubmitCommand(const Func& single_time_function) const {
const vk::CommandBufferAllocateInfo command_buffer_allocate_info {
.commandPool = **this->vk_command_pool,
.level = vk::CommandBufferLevel::ePrimary,
.commandBufferCount = 1
};
auto command_buffer = std::move(vk::raii::CommandBuffers(*this->vk_gpu, command_buffer_allocate_info).front());
command_buffer.begin(vk::CommandBufferBeginInfo { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit });
single_time_function(command_buffer);
command_buffer.end();
const vk::SubmitInfo submit_queue {
.commandBufferCount = 1,
.pCommandBuffers = &*command_buffer,
};
this->vk_queue_graphics->submit(submit_queue);
this->vk_queue_graphics->waitIdle();
}
void TransitionImageLayout(const vk::Image& image, vk::Format format, const vk::ImageLayout& old_layout, const vk::ImageLayout& new_layout) const {
this->SingleTimeSubmitCommand([&](const auto& command_buffer) {
this->TransitionImageLayout(command_buffer, image, format, old_layout, new_layout);
});
}
void TransitionImageLayout(const vk::CommandBuffer& command_buffer, const vk::Image& image, vk::Format format, const vk::ImageLayout& old_layout, const vk::ImageLayout& new_layout, const std::uint32_t mip_levels = 1) const {
// https://github.com/KhronosGroup/Vulkan-Hpp/blob/main/RAII_Samples/utils/utils.hpp#L101 // credits to the vulkanhpp project for having fantastic usage examples
// https://docs.vulkan.org/tutorial/latest/06_Texture_mapping/00_Images.html#_transition_barrier_masks
const vk::AccessFlags source_access_mask = [](const vk::ImageLayout& old_layout) -> vk::AccessFlags {
switch (old_layout) {
case vk::ImageLayout::eTransferDstOptimal:
return vk::AccessFlagBits::eTransferWrite;
case vk::ImageLayout::ePreinitialized:
return vk::AccessFlagBits::eHostWrite;
case vk::ImageLayout::eGeneral: // sourceAccessMask is empty
case vk::ImageLayout::eUndefined:
break;
default:
assert(false);
break;
}
return {};
}(old_layout);
const vk::PipelineStageFlags source_stage = [](const vk::ImageLayout& old_layout) -> vk::PipelineStageFlags {
switch (old_layout) {
case vk::ImageLayout::eGeneral:
case vk::ImageLayout::ePreinitialized:
return vk::PipelineStageFlagBits::eHost;
case vk::ImageLayout::eTransferDstOptimal:
return vk::PipelineStageFlagBits::eTransfer;
case vk::ImageLayout::eUndefined:
return vk::PipelineStageFlagBits::eTopOfPipe;
default:
assert(false);
break;
}
return {};
}(old_layout);
const vk::AccessFlags destination_access_mask = [](const vk::ImageLayout& new_layout) -> vk::AccessFlags {
switch (new_layout) {
case vk::ImageLayout::eColorAttachmentOptimal:
return vk::AccessFlagBits::eColorAttachmentWrite;
case vk::ImageLayout::eDepthStencilAttachmentOptimal:
return vk::AccessFlagBits::eDepthStencilAttachmentRead | vk::AccessFlagBits::eDepthStencilAttachmentWrite;
case vk::ImageLayout::eGeneral: // empty destinationAccessMask
case vk::ImageLayout::ePresentSrcKHR:
break;
case vk::ImageLayout::eShaderReadOnlyOptimal:
return vk::AccessFlagBits::eShaderRead;
case vk::ImageLayout::eTransferSrcOptimal:
return vk::AccessFlagBits::eTransferRead;
case vk::ImageLayout::eTransferDstOptimal:
return vk::AccessFlagBits::eTransferWrite;
default:
assert(false);
break;
}
return {};
}(new_layout);
const vk::PipelineStageFlags destination_stage = [](const vk::ImageLayout& new_layout) -> vk::PipelineStageFlags {
switch (new_layout) {
case vk::ImageLayout::eColorAttachmentOptimal:
return vk::PipelineStageFlagBits::eColorAttachmentOutput;
case vk::ImageLayout::eDepthStencilAttachmentOptimal:
return vk::PipelineStageFlagBits::eEarlyFragmentTests;
case vk::ImageLayout::eGeneral:
return vk::PipelineStageFlagBits::eHost;
case vk::ImageLayout::ePresentSrcKHR:
return vk::PipelineStageFlagBits::eBottomOfPipe;
case vk::ImageLayout::eShaderReadOnlyOptimal:
return vk::PipelineStageFlagBits::eFragmentShader;
case vk::ImageLayout::eTransferDstOptimal:
case vk::ImageLayout::eTransferSrcOptimal:
return vk::PipelineStageFlagBits::eTransfer;
default:
assert(false);
break;
}
return {};
}(new_layout);
const vk::ImageAspectFlags aspect_mask = [&format](const vk::ImageLayout& new_layout) -> vk::ImageAspectFlags {
vk::ImageAspectFlags aspect_mask;
if (new_layout == vk::ImageLayout::eDepthStencilAttachmentOptimal) {
aspect_mask = vk::ImageAspectFlagBits::eDepth;
if (format == vk::Format::eD32SfloatS8Uint || format == vk::Format::eD24UnormS8Uint) {
aspect_mask |= vk::ImageAspectFlagBits::eStencil;
}
} else
aspect_mask = vk::ImageAspectFlagBits::eColor;
return aspect_mask;
}(new_layout);
const vk::ImageMemoryBarrier image_memory_barrier {
.srcAccessMask = source_access_mask,
.dstAccessMask = destination_access_mask,
.oldLayout = old_layout,
.newLayout = new_layout,
.srcQueueFamilyIndex = vk::QueueFamilyIgnored,
.dstQueueFamilyIndex = vk::QueueFamilyIgnored,
.image = image,
.subresourceRange = {
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = mip_levels,
.baseArrayLayer = 0,
.layerCount = 1,
}
};
command_buffer.pipelineBarrier(source_stage, destination_stage, {}, nullptr, nullptr, image_memory_barrier);
}
public:
void Run() {
bool running = true;
while (running) {
SDL_Event window_event;
while (SDL_PollEvent(&window_event)) {
if (window_event.type == SDL_QUIT) {
running = false;
break;
}
}
this->DrawFrame();
}
}
void RunTests() {
constexpr auto amount_of_frames_needed = 15000;
std::cout << "Starting Render Tests with: " << std::to_string(amount_of_frames_needed) << " frames!" << std::endl;
std::uintptr_t posted_about = 0;
for (std::uintptr_t drawn_frames = 0; drawn_frames < amount_of_frames_needed; drawn_frames++) {
this->DrawFrame();
const auto amount_of_thousands = drawn_frames / 1000;
if (amount_of_thousands > posted_about) {
std::cout << "DrawFrame Count: " << std::to_string(drawn_frames) << std::endl;
posted_about = amount_of_thousands;
}
}
std::cout << "Completed tests with: " << std::to_string(amount_of_frames_needed) << " frames!" << std::endl;
}
void DrawFrame() {
const auto current_frame_index = this->vk_current_frame_index;
const std::array wait_fences = { *this->vk_fences_in_flight[current_frame_index] };
const vk::Result fence_result = this->vk_gpu->waitForFences(wait_fences, vk::True, std::numeric_limits::max());
if (fence_result != vk::Result::eSuccess)
throw std::runtime_error("failed to wait for frame fence!");
const auto next_image = this->vk_swapchain->acquireNextImage(std::numeric_limits::max(), *this->vk_semephores_image_available[current_frame_index]);
if (next_image.first == vk::Result::eErrorOutOfDateKHR) {
this->RecreateSwapchain();
return;
} else if (next_image.first != vk::Result::eSuccess && next_image.first != vk::Result::eSuboptimalKHR)
throw std::runtime_error("failed to acquire next swap chain image!");
this->vk_gpu->resetFences(wait_fences);
this->vk_current_frame_index = (this->vk_current_frame_index + 1) % this->vk_max_frames_in_flight;
this->vk_allocator->setCurrentFrameIndex(current_frame_index);
const auto& command_buffer = this->vk_command_buffers[current_frame_index];
command_buffer.reset();
this->RecordCommandBuffer(command_buffer, next_image.second, current_frame_index);
const std::array wait_semaphores = { *this->vk_semephores_image_available[current_frame_index] };
constexpr auto wait_stages = vk::PipelineStageFlags(vk::PipelineStageFlagBits::eColorAttachmentOutput);
const std::array signal_semaphores = { *this->vk_semephores_render_finished[current_frame_index] };
this->vk_pipeline_third.UpdateUniformBuffer(current_frame_index);
const vk::SubmitInfo submit_info {
.waitSemaphoreCount = wait_semaphores.size(),
.pWaitSemaphores = wait_semaphores.data(),
.pWaitDstStageMask = &wait_stages,
.commandBufferCount = 1,
.pCommandBuffers = &*command_buffer,
.signalSemaphoreCount = signal_semaphores.size(),
.pSignalSemaphores = signal_semaphores.data()
};
const std::array submit_queue = { submit_info };
this->vk_queue_graphics->submit(submit_queue, *this->vk_fences_in_flight[current_frame_index]);
const std::array swapchains = { *this->vk_swapchain };
const vk::PresentInfoKHR present_info {
.waitSemaphoreCount = signal_semaphores.size(),
.pWaitSemaphores = signal_semaphores.data(),
.swapchainCount = swapchains.size(),
.pSwapchains = swapchains.data(),
.pImageIndices = &next_image.second,
.pResults = nullptr // Optional
};
const vk::Result present_result = this->vk_queue_present->presentKHR(present_info);
if (present_result == vk::Result::eErrorOutOfDateKHR || present_result == vk::Result::eSuboptimalKHR) {
this->RecreateSwapchain();
return;
} else if (present_result != vk::Result::eSuccess)
throw std::runtime_error("failed to present image!");
}
void RecordCommandBuffer(const vk::raii::CommandBuffer& command_buffer, const std::uint32_t image_index, const std::size_t frame_index) const {
constexpr vk::CommandBufferBeginInfo command_buffer_begin_info {
//.flags = vk::CommandBufferUsageFlagBits::, // Optional
//.pInheritanceInfo = nullptr // Optional
};
command_buffer.begin(command_buffer_begin_info);
constexpr std::array clear_colors = { vk::ClearValue { .color = std::array { 0.0f, 0.0f, 0.0f, 1.0f } }, vk::ClearValue { .color = std::array { 0.0f, 0.0f, 0.0f, 1.0f } }, vk::ClearValue { .depthStencil = { 1.0f, 0 } } };
const vk::RenderPassBeginInfo render_pass_info {
.renderPass = **this->vk_render_pass,
.framebuffer = *this->vk_swapchain_framebuffers.at(image_index),
.renderArea = {
.offset = { 0, 0 },
.extent = this->vk_swapchain_extent },
.clearValueCount = clear_colors.size(),
.pClearValues = clear_colors.data()
};
command_buffer.beginRenderPass(render_pass_info, vk::SubpassContents::eInline);
this->RecordDynamic(command_buffer);
this->vk_pipeline_third.Bind(command_buffer, frame_index);
this->vk_pipeline_third.Draw(command_buffer);
this->vk_pipeline_second.Bind(command_buffer, frame_index);
this->vk_pipeline_second.Draw(command_buffer);
command_buffer.endRenderPass();
command_buffer.end();
};
void RecordDynamic(const vk::raii::CommandBuffer& command_buffer) const {
const vk::Viewport viewport {
.x = 0.0f,
.y = 0.0f,
.width = (float)this->vk_swapchain_extent.width,
.height = (float)this->vk_swapchain_extent.height,
.minDepth = 0.0f,
.maxDepth = 1.0f
};
const vk::Rect2D scissor {
.offset = { 0, 0 },
.extent = this->vk_swapchain_extent
};
command_buffer.setViewport(0, viewport);
command_buffer.setScissor(0, scissor);
}
public:
~VulkanExampleApplication() {
this->vk_gpu->waitIdle();
}
private:
static constexpr auto app_name = "VulkZample";
static constexpr bool allow_swrast = true;
#if defined(ENABLE_VULKAN_VALIDATION)
static constexpr auto debug_message_info = vk::DebugUtilsMessengerCreateInfoEXT {
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo,
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
.pfnUserCallback = &VulkanExampleApplication_VulkanDebugCallback
};
#endif
static constexpr int vk_max_frames_in_flight = 2;
static constexpr std::array required_vulkan_device_extensions = { vk::KHRSwapchainExtensionName };
static constexpr std::array required_vulkan_validation_layers = { "VK_LAYER_KHRONOS_validation" };
static constexpr vk::Format required_vulkan_default_format = vk::Format::eR8G8B8A8Unorm; // formats Preffered front to back. FIFO
static constexpr std::array required_vulkan_screen_formats = { vk::Format::eB8G8R8A8Unorm, vk::Format::eR8G8B8A8Unorm, vk::Format::eB8G8R8Unorm, vk::Format::eR8G8B8Unorm }; // used similar ones from, seemed optimal... https://github.com/KhronosGroup/Vulkan-Hpp/blob/6f72ceca515d59f40d64b64cf2734f6261e1f9f2/samples/utils/utils.cpp#L537
static constexpr vk::ColorSpaceKHR required_vulkan_default_screen_colorspace = vk::ColorSpaceKHR::eSrgbNonlinear;
static constexpr vk::ColorSpaceKHR required_vulkan_screen_colorspace = vk::ColorSpaceKHR::eSrgbNonlinear;
};
#if defined(ENABLE_VULKAN_VALIDATION)
static VKAPI_ATTR VkBool32 VKAPI_CALL VulkanExampleApplication_VulkanDebugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
VkDebugUtilsMessageTypeFlagsEXT message_type,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data,
void* user_data) {
std::cerr << "[debug log] validation layer: " << callback_data->pMessage << std::endl;
if (message_severity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
if constexpr (fatal_errors) {
const std::string_view msg = callback_data->pMessage;
if (!msg.starts_with("loader_scanned_icd_add: Could not get 'vkCreateInstance' via 'vk_icdGetInstanceProcAddr'")) {
*reinterpret_cast(5) = 5;
throw std::logic_error(std::string("Received Vulkan-ValidationLayer Error: ") + callback_data->pMessage);
}
}
}
return vk::False;
}
/*static VKAPI_ATTR VkBool32 VKAPI_CALL VulkanExampleApplication_VulkanReportCallback(
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT type,
uint64_t obj,
size_t location,
int32_t code,
const char* layer_prefix,
const char* msg,
void* user_data) {
printf("[debug log] report layer: %s\n", msg);
return vk::False;
}*/
#endif
int main() {
try {
VulkanExampleApplication app;
#if !defined(ENABLE_TESTS)
app.Run();
#else
app.RunTests();
#endif
} catch (const vk::SystemError& error) {
std::cerr << error.what() << std::endl;
return EXIT_FAILURE;
} catch (const std::exception& error) {
std::cerr << error.what() << std::endl;
return EXIT_FAILURE;
} catch (...) {
std::cerr << "Received Unknown Error in Main()..." << std::endl;
throw "Unknown Exception, throwing...";
}
return EXIT_SUCCESS;
}