Created
March 19, 2021 16:00
-
-
Save xenobrain/796545b4b22728e756be6c286e1776cb to your computer and use it in GitHub Desktop.
HelloTriangle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <vulkan/vulkan.hpp> | |
#include <shaderc/shaderc.hpp> | |
#include <GLFW/glfw3.h> | |
auto constexpr window_title {"Vulkan Prototype"}; | |
auto constexpr window_width {1280}; | |
auto constexpr window_height {720}; | |
auto static error_callback(int error, char const* description) -> void { printf("glfw error: %s", description); } | |
auto static key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) -> void { | |
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) glfwSetWindowShouldClose(window, GLFW_TRUE); | |
} | |
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE | |
auto main() -> int { | |
try { | |
// Window | |
glfwSetErrorCallback(error_callback); | |
glfwInit(); | |
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); | |
auto const& window{ glfwCreateWindow(1280, 720, "Vulkan", nullptr, nullptr) }; | |
glfwSetKeyCallback(window, key_callback); | |
// Loader | |
auto const& loader{ vk::DynamicLoader{} }; | |
VULKAN_HPP_DEFAULT_DISPATCHER.init(loader.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr")); | |
// Instance | |
auto instance{ vk::createInstanceUnique({vk::StructureChain<vk::InstanceCreateInfo,vk::ValidationFeaturesEXT,vk::DebugUtilsMessengerCreateInfoEXT>{ | |
{ | |
vk::InstanceCreateFlags{}, | |
[application_info {vk::ApplicationInfo{}}] {return &application_info; }(), | |
std::vector<char const*>{"VK_LAYER_KHRONOS_validation"}, | |
[] { | |
auto extension_count{0u}; | |
auto const& required_extensions {glfwGetRequiredInstanceExtensions(&extension_count)}; | |
auto extensions {std::vector<char const*>(required_extensions, required_extensions + extension_count)}; | |
extensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); | |
return extensions; | |
}() | |
}, | |
{ | |
std::vector<vk::ValidationFeatureEnableEXT>{vk::ValidationFeatureEnableEXT::eGpuAssisted, vk::ValidationFeatureEnableEXT::eBestPractices, vk::ValidationFeatureEnableEXT::eSynchronizationValidation } | |
}, | |
{ | |
vk::DebugUtilsMessengerCreateFlagsEXT{}, | |
vk::DebugUtilsMessageSeverityFlagsEXT{vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning}, | |
vk::DebugUtilsMessageTypeFlagsEXT{vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation}, | |
[](auto const severity, auto const type, auto const* callback, auto*) -> vk::Bool32 { | |
printf("\n%s: %s\t%s\n", | |
vk::to_string(static_cast<vk::DebugUtilsMessageSeverityFlagBitsEXT>(severity)).c_str(), | |
vk::to_string(static_cast<vk::DebugUtilsMessageTypeFlagBitsEXT>(type)).c_str(), | |
callback->pMessage); | |
return vk::Bool32{false}; } | |
} | |
}.get<vk::InstanceCreateInfo>()}) }; | |
VULKAN_HPP_DEFAULT_DISPATCHER.init(instance.get()); | |
auto physical_device{ instance->enumeratePhysicalDevices().front() }; | |
// Surface | |
auto const& surface{ [&instance, &window] | |
{ | |
auto* c_surface {VkSurfaceKHR{}}; | |
auto const& result = static_cast<vk::Result>(glfwCreateWindowSurface(instance.get(), window, {}, &c_surface)); | |
if (result != vk::Result::eSuccess) throw std::runtime_error(vk::to_string(result)); | |
return vk::UniqueSurfaceKHR{c_surface, vk::ObjectDestroy<vk::Instance, VULKAN_HPP_DEFAULT_DISPATCHER_TYPE>(instance.get()) }; | |
}() }; | |
auto const& surface_formats {physical_device.getSurfaceFormatsKHR(surface.get())}; | |
auto const& surface_capabilities {physical_device.getSurfaceCapabilitiesKHR(surface.get())}; | |
auto const& surface_present_modes {physical_device.getSurfacePresentModesKHR(surface.get())}; | |
// Device | |
auto queue_family_properties{ physical_device.getQueueFamilyProperties() }; | |
auto graphics_family_queue_index {[&physical_device, &surface, &queue_family_properties] | |
{ | |
return static_cast<std::uint32_t>(std::distance(queue_family_properties.begin(), | |
std::find_if(queue_family_properties.begin(), queue_family_properties.end(), | |
[&physical_device, &surface, &queue_family_properties](vk::QueueFamilyProperties const& qfp) { | |
return qfp.queueFlags & vk::QueueFlagBits::eGraphics | |
&& physical_device.getSurfaceSupportKHR(static_cast<std::uint32_t>( | |
std::distance(queue_family_properties.begin(), | |
std::find(queue_family_properties.begin(), queue_family_properties.end(), qfp))), surface.get()); | |
}))); | |
}() | |
}; | |
auto device = physical_device.createDeviceUnique(vk::DeviceCreateInfo{ | |
vk::DeviceCreateFlags{}, | |
[queue_create_infos {std::vector<vk::DeviceQueueCreateInfo>{ | |
{vk::DeviceQueueCreateFlags{}, graphics_family_queue_index, 1u, [priority{0.f}]{ return &priority; }() } | |
}}] { return queue_create_infos; }(), | |
{}, | |
[device_extensions {std::vector<char const*>{ | |
VK_KHR_SWAPCHAIN_EXTENSION_NAME | |
}}] { return device_extensions; }() | |
}); | |
VULKAN_HPP_DEFAULT_DISPATCHER.init(device.get()); | |
auto const& memory_properties{ physical_device.getMemoryProperties() }; | |
// Swapchain | |
auto const& swapchain_format{ vk::Format::eB8G8R8A8Unorm }; | |
auto const& swapchain{ device->createSwapchainKHRUnique(vk::SwapchainCreateInfoKHR{ | |
vk::SwapchainCreateFlagsKHR{}, // flags | |
surface.get(), // surface | |
3, // min image count | |
swapchain_format, // format | |
vk::ColorSpaceKHR::eSrgbNonlinear, // image color space | |
vk::Extent2D{window_width, window_height}, // extent | |
1, // image array layers | |
vk::ImageUsageFlagBits::eColorAttachment, // image usage | |
vk::SharingMode::eExclusive, // image sharing mode | |
[queue_family_indices { // queue family indices array | |
std::vector<std::uint32_t>{ | |
graphics_family_queue_index | |
}}] { return queue_family_indices; }(), | |
vk::SurfaceTransformFlagBitsKHR::eIdentity, // pre transform (can be more advanced) | |
vk::CompositeAlphaFlagBitsKHR::eOpaque, // composite alpha (this can be more advanced) | |
vk::PresentModeKHR::eMailbox, // present mode | |
vk::Bool32{true}, // clipped | |
nullptr // old swapchain | |
}) }; | |
auto swapchain_images{ device->getSwapchainImagesKHR(swapchain.get()) }; | |
auto swapchain_image_views{ [&device, &swapchain_images, &swapchain_format] | |
{ | |
auto swapchain_image_views {std::vector<vk::UniqueImageView>{}}; | |
for (auto const& image : swapchain_images) | |
{ | |
swapchain_image_views.emplace_back(device->createImageViewUnique(vk::ImageViewCreateInfo{ | |
vk::ImageViewCreateFlags{}, | |
image, | |
vk::ImageViewType::e2D, | |
swapchain_format, | |
vk::ComponentMapping{ | |
vk::ComponentSwizzle::eIdentity, | |
vk::ComponentSwizzle::eIdentity, | |
vk::ComponentSwizzle::eIdentity, | |
vk::ComponentSwizzle::eIdentity | |
}, | |
vk::ImageSubresourceRange{ | |
vk::ImageAspectFlagBits::eColor, // aspect mask | |
0, // base mip level | |
1, // level count | |
0, // base array layer | |
1 // layout count | |
} | |
})); | |
} | |
return swapchain_image_views; | |
}() }; | |
// Shaders | |
auto constexpr vertex_shader_source {R"( | |
#version 450 | |
#extension GL_ARB_separate_shader_objects : enable | |
out gl_PerVertex { vec4 gl_Position; }; | |
layout(location = 0) out vec3 fragColor; | |
vec2 positions[3] = vec2[]( | |
vec2( 0.0f, -0.5f), | |
vec2( 0.5f, 0.5f), | |
vec2(-0.5f, 0.5f) | |
); | |
vec3 colors[3] = vec3[]( | |
vec3(1.0f, 0.0f, 0.0f), | |
vec3(0.0f, 1.0f, 0.0f), | |
vec3(0.0f, 0.0f, 1.0f) | |
); | |
void main() { | |
gl_Position = vec4(positions[gl_VertexIndex], 0.f, 1.f); | |
fragColor = colors[gl_VertexIndex]; | |
})" | |
}; | |
auto constexpr fragment_shader_source {R"( | |
#version 450 | |
#extension GL_ARB_separate_shader_objects : enable | |
layout(location = 0) in vec3 fragColor; | |
layout(location = 0) out vec4 outColor; | |
void main() { | |
outColor = vec4(fragColor, 1.f); | |
})" | |
}; | |
auto const& compiler{ shaderc::Compiler{} }; | |
auto compile_options{ shaderc::CompileOptions{}}; | |
compile_options.SetOptimizationLevel(shaderc_optimization_level_performance); | |
auto const& vertex_shader_compile_result {compiler.CompileGlslToSpv(vertex_shader_source, shaderc_glsl_vertex_shader, "vertex_shader", compile_options)}; | |
if (vertex_shader_compile_result.GetCompilationStatus() != shaderc_compilation_status_success) | |
throw std::runtime_error(vertex_shader_compile_result.GetErrorMessage()); | |
auto const& vertex_shader_code {std::vector<std::uint32_t>{vertex_shader_compile_result.begin(), vertex_shader_compile_result.end()} }; | |
auto const& vertex_shader_module {device->createShaderModuleUnique(vk::ShaderModuleCreateInfo{vk::ShaderModuleCreateFlags{}, vertex_shader_code})}; | |
auto const& fragment_shader_compile_result {compiler.CompileGlslToSpv(fragment_shader_source, shaderc_glsl_fragment_shader, "fragment shader", compile_options)}; | |
if (fragment_shader_compile_result.GetCompilationStatus() != shaderc_compilation_status_success) | |
throw std::runtime_error(vertex_shader_compile_result.GetErrorMessage()); | |
auto const& fragment_shader_code {std::vector<std::uint32_t>{fragment_shader_compile_result.begin(), fragment_shader_compile_result.end()}}; | |
auto const& fragment_shader_module {device->createShaderModuleUnique(vk::ShaderModuleCreateInfo{vk::ShaderModuleCreateFlags{}, fragment_shader_code})}; | |
auto const& viewport {vk::Viewport{0.f, 0.f, static_cast<float>(window_width), static_cast<float>(window_height), 0.f, 0.f}}; | |
auto const& render_pass {device->createRenderPassUnique(vk::RenderPassCreateInfo{ | |
vk::RenderPassCreateFlags{}, // flags | |
[attachments {std::vector<vk::AttachmentDescription>{ // attachments | |
vk::AttachmentDescription{ // Color attachment | |
vk::AttachmentDescriptionFlags{}, // flags | |
swapchain_format, // format | |
vk::SampleCountFlagBits::e1, // samples | |
vk::AttachmentLoadOp::eClear, // load op (configurable) | |
vk::AttachmentStoreOp::eStore, // store op | |
vk::AttachmentLoadOp::eDontCare, // stencil load op | |
vk::AttachmentStoreOp::eDontCare, // stencil store op | |
vk::ImageLayout::eUndefined, // initial layout | |
vk::ImageLayout::ePresentSrcKHR // final layout (configurable) | |
}}}] { | |
return attachments; | |
}(), | |
[subpass {std::vector<vk::SubpassDescription>{ // subpass descriptions | |
vk::SubpassDescription{ | |
vk::SubpassDescriptionFlags{}, // flags | |
vk::PipelineBindPoint::eGraphics, // pipeline bind point | |
{}, //[input_attachments {std::vector<vk::AttachmentReference>{}}] { return input_attachments; }() , // input attachments array | |
[color_attachments {std::vector<vk::AttachmentReference>{vk::AttachmentReference{ | |
0, // count (is this correct?) | |
vk::ImageLayout::eColorAttachmentOptimal} | |
}}] { return color_attachments; }(), // color attachments array (need to add this) | |
{}, //[resolve_attachments {std::vector<vk::AttachmentReference>{}}] { return resolve_attachments; }(), // resolve attachments array | |
nullptr, // depth-stencil attachment (singular), | |
{}//[preserve_attachments {std::vector<std::uint32_t>{}}] { return preserve_attachments; }() // preserve attachments array | |
} | |
}}] { | |
return subpass; | |
}() | |
})}; | |
auto const& pipeline_layout {device->createPipelineLayoutUnique({})}; | |
auto const& graphics_pipeline {[&device, &pipeline_layout, &render_pass, &vertex_shader_module, &fragment_shader_module, &viewport] | |
{ | |
return device->createGraphicsPipelineUnique({}, vk::GraphicsPipelineCreateInfo{ | |
vk::PipelineCreateFlags{}, | |
[shader_stage_info {std::vector<vk::PipelineShaderStageCreateInfo>{ | |
vk::PipelineShaderStageCreateInfo{ | |
vk::PipelineShaderStageCreateFlags{}, | |
vk::ShaderStageFlagBits::eVertex, | |
vertex_shader_module.get(), | |
"main" | |
}, | |
vk::PipelineShaderStageCreateInfo{ | |
vk::PipelineShaderStageCreateFlags{}, | |
vk::ShaderStageFlagBits::eFragment, | |
fragment_shader_module.get(), | |
"main" | |
}}}] { return shader_stage_info; }(), | |
[vertex_input_state {vk::PipelineVertexInputStateCreateInfo{ | |
vk::PipelineVertexInputStateCreateFlags{}, | |
{}, | |
{} | |
}}] { return &vertex_input_state; }(), | |
[input_assembly_state {vk::PipelineInputAssemblyStateCreateInfo{ | |
vk::PipelineInputAssemblyStateCreateFlags{}, // flags | |
vk::PrimitiveTopology::eTriangleList, // topology | |
vk::Bool32{false} // primitive restart? | |
}}] { return &input_assembly_state; }(), | |
[tessellation_state {vk::PipelineTessellationStateCreateInfo{ | |
vk::PipelineTessellationStateCreateFlags{} | |
}}] { return &tessellation_state; }(), | |
[viewport_state {vk::PipelineViewportStateCreateInfo{ | |
vk::PipelineViewportStateCreateFlags{}, | |
std::vector<vk::Viewport>{viewport}, | |
std::vector<vk::Rect2D>{vk::Rect2D{vk::Offset2D{0, 0}, vk::Extent2D{window_width, window_height}}} | |
}}] { return &viewport_state; }(), | |
[rasterization_state {vk::PipelineRasterizationStateCreateInfo{ | |
vk::PipelineRasterizationStateCreateFlags{}, | |
vk::Bool32{false}, // depth clamp enabled? | |
vk::Bool32{false}, // rasterizer discard enabled? | |
vk::PolygonMode::eFill, // polygon mode | |
{}, // cull mode | |
vk::FrontFace::eCounterClockwise, // front face | |
{}, // depth bias enabled? | |
{}, // depth bias constant factor (float) | |
{}, // depth bias clamp (float) | |
{}, // depth bias slope factor (float) | |
1.0f // line width (float) | |
}}] { return &rasterization_state; }(), | |
[multisample_state {vk::PipelineMultisampleStateCreateInfo{ | |
vk::PipelineMultisampleStateCreateFlags{}, | |
vk::SampleCountFlagBits::e1 | |
}}] { return &multisample_state; }(), | |
{[depth_stencil_state {vk::PipelineDepthStencilStateCreateInfo{ | |
vk::PipelineDepthStencilStateCreateFlags{}, | |
vk::Bool32{false}, //depth buffered? | |
vk::Bool32{false}, // depth buffered? | |
vk::CompareOp::eLessOrEqual, // depth compare op | |
vk::Bool32{false}, // depth test enabled? | |
vk::Bool32{false}, // stencil test enabled? | |
vk::StencilOpState{vk::StencilOp::eKeep, vk::StencilOp::eKeep, vk::StencilOp::eKeep, vk::CompareOp::eAlways}, | |
vk::StencilOpState{vk::StencilOp::eKeep, vk::StencilOp::eKeep, vk::StencilOp::eKeep, vk::CompareOp::eAlways} | |
}}] { return &depth_stencil_state; }()}, | |
[color_blend_state {vk::PipelineColorBlendStateCreateInfo{ | |
vk::PipelineColorBlendStateCreateFlags{}, | |
vk::Bool32{false}, // logic op enabled? | |
vk::LogicOp::eNoOp, | |
std::vector<vk::PipelineColorBlendAttachmentState>{ { | |
vk::Bool32{false}, // blend enabled? | |
vk::BlendFactor::eZero, // (default) src color blend factor | |
vk::BlendFactor::eZero, // (default) dst color blend factor, | |
vk::BlendOp::eAdd, // (default) color blend op | |
vk::BlendFactor::eZero, // (default) src alpha blend factor | |
vk::BlendFactor::eZero, // (default) dst alpha blend factor | |
vk::BlendOp::eAdd, // (default) alpha blend op | |
vk::ColorComponentFlags{ | |
vk::ColorComponentFlagBits::eR | | |
vk::ColorComponentFlagBits::eG | | |
vk::ColorComponentFlagBits::eB | | |
vk::ColorComponentFlagBits::eA | |
} | |
}}}}] { return &color_blend_state; }(), | |
[dynamic_state {vk::PipelineDynamicStateCreateInfo{ | |
vk::PipelineDynamicStateCreateFlags{}, | |
std::vector<vk::DynamicState>{ | |
vk::DynamicState::eViewport, | |
vk::DynamicState::eScissor | |
} | |
}}] { return &dynamic_state; }(), | |
pipeline_layout.get(), | |
render_pass.get(), | |
0u // subpass | |
}).value; | |
}()}; | |
auto framebuffers {[&device, &swapchain_image_views, &render_pass] | |
{ | |
auto framebuffers {std::vector<vk::UniqueFramebuffer>{}}; | |
for (auto const& image_view : swapchain_image_views) | |
{ | |
framebuffers.emplace_back(device->createFramebufferUnique(vk::FramebufferCreateInfo{ | |
vk::FramebufferCreateFlags{}, | |
render_pass.get(), | |
[attachment {std::array<vk::ImageView,1>{image_view.get()}}] { return attachment; }(), // attachments | |
window_width, // swapchain extent | |
window_height, // swapchain extent | |
1 // layers | |
})); | |
} | |
return framebuffers; | |
}()}; | |
auto command_pool {device->createCommandPoolUnique(vk::CommandPoolCreateInfo{ | |
vk::CommandPoolCreateFlags{}, | |
graphics_family_queue_index | |
})}; | |
auto command_buffers {device->allocateCommandBuffersUnique(vk::CommandBufferAllocateInfo{ | |
command_pool.get(), | |
vk::CommandBufferLevel::ePrimary, | |
static_cast<std::uint32_t>(framebuffers.size())}) | |
}; | |
auto graphics_queue {device->getQueue(graphics_family_queue_index, 0u)}; | |
auto present_queue {device->getQueue(graphics_family_queue_index, 0u)}; // also supports present | |
auto image_available_semaphore {device->createSemaphoreUnique({})}; | |
auto render_finished_semaphore {device->createSemaphoreUnique({})}; | |
auto record_command_buffer{[&render_pass, &graphics_pipeline, &viewport](vk::UniqueCommandBuffer& command_buffer, vk::UniqueFramebuffer& framebuffer) | |
{ | |
command_buffer->begin(vk::CommandBufferBeginInfo{}); | |
command_buffer->beginRenderPass(vk::RenderPassBeginInfo{ | |
render_pass.get(), | |
framebuffer.get(), // is this working as expected? | |
vk::Rect2D{vk::Offset2D{0,0}, vk::Extent2D{window_width, window_height}}, | |
[clear_values {std::vector<vk::ClearValue>{ | |
vk::ClearColorValue{std::array<float,4>{.392f, .584f, .929f, 1.f}} // correct parenthesis? | |
}}]{ return clear_values; }() | |
}, vk::SubpassContents::eInline); | |
command_buffer->bindPipeline(vk::PipelineBindPoint::eGraphics, graphics_pipeline.get()); | |
command_buffer->setViewport(0u, std::array<vk::Viewport, 1>{viewport}); | |
command_buffer->setScissor(0u, vk::Rect2D{ vk::Offset2D{0u, 0u}, vk::Extent2D{window_width, window_height} }); | |
command_buffer->draw(3, 1, 0, 0); | |
command_buffer->endRenderPass(); | |
command_buffer->end(); | |
return std::move(command_buffer); | |
}}; | |
std::transform(command_buffers.begin(), command_buffers.end(), framebuffers.begin(), command_buffers.begin(), record_command_buffer); | |
while (!glfwWindowShouldClose(window)) | |
{ | |
glfwPollEvents(); | |
auto const& image_index{ device->acquireNextImageKHR( | |
swapchain.get(), // swapchain | |
std::numeric_limits<std::uint64_t>::max(), // timeout | |
image_available_semaphore.get(), // semaphore | |
nullptr) // fence | |
.value | |
}; | |
auto draw_fence {device->createFenceUnique(vk::FenceCreateInfo{})}; | |
graphics_queue.submit(vk::SubmitInfo{ | |
[wait_semaphores {std::array<vk::Semaphore,1>{image_available_semaphore.get()}}] { | |
return wait_semaphores; | |
}(), | |
[wait_stage_masks {std::array<vk::PipelineStageFlags,1>{vk::PipelineStageFlagBits::eColorAttachmentOutput}}] { | |
return wait_stage_masks; | |
}(), | |
[submit_command_buffers {std::array<vk::CommandBuffer,1>{command_buffers.at(image_index).get()}}] { | |
return submit_command_buffers; | |
}(), | |
[signal_semaphores {std::array<vk::Semaphore,1>{render_finished_semaphore.get()}}] { | |
return signal_semaphores; | |
}() | |
}, | |
draw_fence.get()); | |
while (device->waitForFences(draw_fence.get(), vk::Bool32{ true }, std::numeric_limits<std::uint64_t>::max()) == vk::Result::eTimeout); | |
auto present_result = present_queue.presentKHR(vk::PresentInfoKHR{ | |
[wait_semaphores {std::array<vk::Semaphore,1>{render_finished_semaphore.get()}}] {return wait_semaphores; }(), | |
[swapchains {std::array<vk::SwapchainKHR,1>{swapchain.get()}}] { return swapchains; }(), | |
[image_indices {std::array<std::uint32_t,1>{image_index}}] { return image_indices; }() | |
}); | |
if (present_result != vk::Result::eSuccess) | |
throw std::runtime_error(vk::to_string(present_result)); | |
device->waitIdle(); | |
} | |
} catch (std::exception const& exception) | |
{ | |
OutputDebugStringA(exception.what()); | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} | |
// Design notes: | |
// | |
// Lambdas are used to *lower* the level of abstraction and put implementations at the call site | |
// This is done because I actually am interested in the implementation and it helps comprehension not have to jump around | |
// | |
// Even though copy constructors can be used in the create() functions to avoid redundant typing of struct names, writing them out aids intellisense and provides self-documentation |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment