/* * 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; }