Skip to content

Instantly share code, notes, and snippets.

@xenobrain
Created March 19, 2021 16:00
Show Gist options
  • Save xenobrain/796545b4b22728e756be6c286e1776cb to your computer and use it in GitHub Desktop.
Save xenobrain/796545b4b22728e756be6c286e1776cb to your computer and use it in GitHub Desktop.
HelloTriangle
#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