All checks were successful
ci/woodpecker/push/woodpecker.json Pipeline was successful
1851 lines
103 KiB
C++
1851 lines
103 KiB
C++
|
|
/*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <cstddef>
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <string_view>
|
|
#include <chrono>
|
|
|
|
#define VULKAN_HPP_NO_CONSTRUCTORS
|
|
#include <vulkan/vulkan_raii.hpp>
|
|
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_vulkan.h>
|
|
|
|
#include <SDL2pp/SDL2pp.hh>
|
|
|
|
#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 <vk_mem_alloc.hpp>
|
|
|
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
|
#include <glm/glm.hpp>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
#include <embed_resources.hpp>
|
|
|
|
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<std::pair<vma::UniqueBuffer, vma::UniqueAllocation>>;
|
|
using VulkanImage = std::optional<std::pair<vma::UniqueImage, vma::UniqueAllocation>>;
|
|
|
|
public: // Window/Device setup
|
|
std::optional<SDL2pp::SDL> libsdl;
|
|
std::optional<SDL2pp::Window> window;
|
|
std::optional<vk::raii::Context> vk_ctx;
|
|
std::optional<vk::raii::Instance> vk_inst;
|
|
std::optional<vk::raii::DebugUtilsMessengerEXT> vk_debug_utils_messenger;
|
|
// std::optional<vk::raii::DebugReportCallbackEXT> vk_debug_report_callback;
|
|
std::optional<vk::raii::PhysicalDevice> vk_gfx_card;
|
|
std::optional<vk::raii::SurfaceKHR> vk_screen_surface;
|
|
std::optional<vk::raii::Device> vk_gpu;
|
|
|
|
struct VulkanDeviceQueriedInfo {
|
|
std::optional<vk::raii::SurfaceKHR> screen_surface;
|
|
std::optional<std::uint32_t> graphics_family;
|
|
std::optional<std::uint32_t> present_family;
|
|
bool surface_has_present_modes = false;
|
|
bool surface_has_format_modes = false;
|
|
std::optional<vk::SampleCountFlagBits> msaa_samples;
|
|
std::vector<std::string_view> supported_features_vma_device;
|
|
std::vector<vma::AllocatorCreateFlagBits> 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::raii::RenderPass> vk_render_pass;
|
|
|
|
std::optional<vk::raii::Queue> vk_queue_graphics;
|
|
std::optional<vk::raii::Queue> vk_queue_present;
|
|
|
|
std::optional<vk::raii::SwapchainKHR> vk_swapchain;
|
|
vk::Format vk_swapchain_image_format;
|
|
vk::Extent2D vk_swapchain_extent;
|
|
std::vector<vk::raii::ImageView> vk_swapchain_image_views;
|
|
std::vector<vk::raii::Framebuffer> vk_swapchain_framebuffers;
|
|
|
|
uint vk_current_frame_index = 0;
|
|
std::optional<vk::raii::CommandPool> vk_command_pool;
|
|
std::vector<vk::raii::CommandBuffer> vk_command_buffers;
|
|
std::vector<vk::raii::Fence> vk_fences_in_flight;
|
|
std::vector<vk::raii::Semaphore> vk_semephores_image_available;
|
|
std::vector<vk::raii::Semaphore> 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<const std::uint32_t*>(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<const std::uint32_t*>(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<vk::VertexInputAttributeDescription, 4> GetAttributeDescriptions() {
|
|
constexpr std::array<vk::VertexInputAttributeDescription, 4> 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<std::uint16_t> triangle = { 0, 1, 2 };
|
|
static inline const std::vector<std::uint16_t> 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<const std::uint32_t*>(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<vk::VertexInputAttributeDescription, 4> GetAttributeDescriptions() {
|
|
constexpr std::array<vk::VertexInputAttributeDescription, 4> 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<std::uint16_t> 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::raii::ImageView> vk_texture_view;
|
|
std::optional<vk::raii::Sampler> vk_texture_sampler;
|
|
|
|
public:
|
|
std::optional<vk::raii::DescriptorSet> vk_descriptor_sets_second;
|
|
std::optional<vk::raii::DescriptorSet> vk_descriptor_sets_third;
|
|
|
|
public:
|
|
template <class DataT>
|
|
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::uint32_t>(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<vk::Offset3D, 2> image_offsets_src {
|
|
vk::Offset3D { 0, 0, 0 },
|
|
vk::Offset3D { static_cast<int32_t>(texture_size.width), static_cast<int32_t>(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<vk::Offset3D, 2> image_offsets_dest {
|
|
vk::Offset3D { 0, 0, 0 },
|
|
vk::Offset3D { static_cast<int32_t>(texture_size.width) > 1 ? static_cast<int32_t>(texture_size.width) / 2 : 1, static_cast<int32_t>(texture_size.height) > 1 ? static_cast<int32_t>(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<float>(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 <typename VertexT>
|
|
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 <typename VertexT>
|
|
class VulkanRenderPipeline {
|
|
private:
|
|
const VulkanExampleApplication* parent;
|
|
std::optional<vk::raii::ShaderModule> vk_shader_vertex;
|
|
std::optional<vk::raii::ShaderModule> vk_shader_frag;
|
|
|
|
std::optional<vk::raii::PipelineLayout> vk_pipeline_layout;
|
|
std::optional<vk::raii::Pipeline> vk_pipeline;
|
|
|
|
public:
|
|
constexpr VulkanRenderPipeline(const VulkanExampleApplication* _parent)
|
|
: parent(_parent) { assert(_parent != nullptr); }
|
|
|
|
public:
|
|
std::optional<vk::raii::DescriptorPool> vk_descriptor_pool;
|
|
std::vector<vk::raii::DescriptorSet> vk_descriptor_sets;
|
|
std::optional<vk::raii::DescriptorSetLayout> 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<VulkanBuffer> vk_buffers_uniform;
|
|
std::vector<std::uint8_t*> 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<vk::DescriptorSetLayoutBinding> {
|
|
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<std::uint32_t>(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<float, 4> {
|
|
0.0f,
|
|
0.0f, // Optional
|
|
0.0f, // Optional
|
|
0.0f // Optional
|
|
} // Optional
|
|
};
|
|
|
|
constexpr std::array<vk::DynamicState, 2> dynamic_states = {
|
|
vk::DynamicState::eViewport,
|
|
vk::DynamicState::eScissor
|
|
};
|
|
const vk::PipelineDynamicStateCreateInfo dynamic_state_create_info {
|
|
.dynamicStateCount = static_cast<std::uint32_t>(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<std::uint8_t*>(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<std::uint32_t>(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<vk::DescriptorPoolSize, 2> { descriptor_pool_size_sampler, descriptor_pool_size_ubo };
|
|
else
|
|
return std::array<vk::DescriptorPoolSize, 1> { 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<std::uint32_t>(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<vk::DescriptorSetLayout> 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<vk::DescriptorSetLayout>(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<std::uint32_t>(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<vk::WriteDescriptorSet> write_descriptor_sets;
|
|
write_descriptor_sets.reserve(this->parent->vk_max_frames_in_flight);
|
|
|
|
std::vector<vk::DescriptorBufferInfo> 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<VertexT>& vertices, const std::vector<IndexT>& 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<float, std::chrono::seconds::period>(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<vk::DescriptorSet, 2> { *this->vk_descriptor_sets[frame_index], this->current_texture->GetDescriptorSet<VertexT>() };
|
|
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<std::uint32_t>(this->vk_buffer_index_size), 1, 0, 0, 0);
|
|
}
|
|
};
|
|
|
|
VulkanRenderPipeline<Vertex2> vk_pipeline_second;
|
|
VulkanRenderPipeline<Vertex3> vk_pipeline_third;
|
|
|
|
static inline const std::vector<Vertex2> 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<Vertex3> 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::raii::ImageView> vk_color_view;
|
|
|
|
private:
|
|
vk::Format vk_depth_format;
|
|
VulkanImage vk_depth_image;
|
|
std::optional<vk::raii::ImageView> vk_depth_view;
|
|
|
|
private:
|
|
std::optional<Texture> 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<vk::LayerProperties> 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<const char*> required_extensions = [this]() -> std::vector<const char*> {
|
|
std::uint32_t extension_count = 0;
|
|
SDL_Vulkan_GetInstanceExtensions(this->window->Get(), &extension_count, nullptr);
|
|
std::vector<const char*> 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<std::uint32_t>(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<const VkDebugReportCallbackCreateInfoEXT&>(debug_callback_create_info), 0, &debug_report_callback);
|
|
this->vk_debug_report_callback = std::make_unique<vk::raii::DebugReportCallbackEXT>(*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<VulkanDeviceQueriedInfo> 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<VkInstance>(static_cast<vk::Instance>(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<std::pair<std::vector<std::string_view>, vma::AllocatorCreateFlagBits>, 5> {
|
|
std::make_pair(std::vector<std::string_view> { "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<vk::DeviceQueueCreateInfo> device_queue_create_infos = [](const std::uint32_t graphics_family, const std::uint32_t present_family) {
|
|
std::vector<vk::DeviceQueueCreateInfo> device_queues_we_need;
|
|
device_queues_we_need.reserve(2);
|
|
const auto GetQueuesToUse = [&graphics_family, &present_family]() {
|
|
std::vector<std::uint32_t> 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<const char*> enabled_extensions_device = [this]() -> std::vector<const char*> {
|
|
std::vector<const char*> 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<std::uint32_t>(device_queue_create_infos.size()),
|
|
.pQueueCreateInfos = device_queue_create_infos.data(),
|
|
.enabledExtensionCount = static_cast<std::uint32_t>(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<vk::AttachmentDescription, 3> attachment_descriptions = { attachment_description_color, attachment_description_color_resolve, attachment_description_depth };
|
|
const vk::RenderPassCreateInfo render_pass_create_info {
|
|
.attachmentCount = static_cast<std::uint32_t>(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<std::uint32_t>(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<std::uint32_t>(embeded_debug_north_png_rgba.width), .height = static_cast<std::uint32_t>(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<vk::SurfaceFormatKHR>& 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<vk::PresentModeKHR>& 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<std::uint32_t>::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<std::uint32_t>(sdl_width), capabilities.minImageExtent.width, capabilities.maxImageExtent.width),
|
|
std::clamp(static_cast<std::uint32_t>(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<std::uint32_t, 2> 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<vk::Format>& 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<vk::ImageView, 3> 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 <typename Func>
|
|
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<vk::Fence, 1> 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<std::uint64_t>::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<std::uint64_t>::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<vk::Semaphore, 1> wait_semaphores = { *this->vk_semephores_image_available[current_frame_index] };
|
|
constexpr auto wait_stages = vk::PipelineStageFlags(vk::PipelineStageFlagBits::eColorAttachmentOutput);
|
|
const std::array<vk::Semaphore, 1> 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<vk::SubmitInfo, 1> submit_queue = { submit_info };
|
|
this->vk_queue_graphics->submit(submit_queue, *this->vk_fences_in_flight[current_frame_index]);
|
|
|
|
const std::array<vk::SwapchainKHR, 1> 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<vk::ClearValue, 3> clear_colors = { vk::ClearValue { .color = std::array<float, 4> { 0.0f, 0.0f, 0.0f, 1.0f } }, vk::ClearValue { .color = std::array<float, 4> { 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<const char*, 1> required_vulkan_device_extensions = { vk::KHRSwapchainExtensionName };
|
|
static constexpr std::array<const char*, 1> 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<vk::Format, 4> 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<std::uint8_t*>(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;
|
|
}
|