Browse Source

Initial commit.

master
Draklaw 4 years ago
commit
ed35935f7e
  1. 2
      .gitignore
  2. 57
      CMakeLists.txt
  3. 0
      README.md
  4. 9
      shaders/shader.frag
  5. 20
      shaders/shader.vert
  6. 41
      src/Logger.cpp
  7. 39
      src/Logger.h
  8. 87
      src/VkExpe.cpp
  9. 33
      src/VkExpe.h
  10. 972
      src/VulkanTutorial.cpp
  11. 106
      src/VulkanTutorial.h
  12. 19
      src/main.cpp
  13. 18
      src/utils.cpp
  14. 34
      src/utils.h

2
.gitignore

@ -0,0 +1,2 @@
/.vscode
/build

57
CMakeLists.txt

@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.0.0)
project(vk_expe VERSION 0.1.0)
find_package(Eigen3 3.3 REQUIRED)
find_package(Vulkan REQUIRED)
find_package(SDL2 2.0.18 REQUIRED)
include(CTest)
enable_testing()
function(add_shaders TARGET)
foreach(SRC ${ARGN})
set(DST "${CMAKE_CURRENT_BINARY_DIR}/${SRC}.spv")
list(APPEND SPVS "${DST}")
get_filename_component(DST_DIR "${DST}" DIRECTORY)
file(MAKE_DIRECTORY "${DST_DIR}")
add_custom_command(
OUTPUT "${DST}"
COMMAND "$ENV{VULKAN_SDK}/bin/glslc" "${CMAKE_CURRENT_SOURCE_DIR}/${SRC}" -o "${DST}"
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${SRC}"
)
endforeach()
add_custom_target(
${TARGET}_shaders
DEPENDS ${SPVS}
)
add_dependencies(${TARGET} ${TARGET}_shaders)
endfunction()
add_executable(vk_expe
src/main.cpp
src/utils.cpp
src/Logger.cpp
src/VkExpe.cpp
src/VulkanTutorial.cpp
)
add_shaders(vk_expe
shaders/shader.vert
shaders/shader.frag
)
target_compile_features(vk_expe
PUBLIC cxx_std_17
)
target_link_libraries(vk_expe
PUBLIC Eigen3::Eigen
PUBLIC Vulkan::Vulkan
PUBLIC SDL2::SDL2
)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

0
README.md

9
shaders/shader.frag

@ -0,0 +1,9 @@
#version 450
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}

20
shaders/shader.vert

@ -0,0 +1,20 @@
#version 450
layout(location = 0) out vec3 fragColor;
vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragColor = colors[gl_VertexIndex];
}

41
src/Logger.cpp

@ -0,0 +1,41 @@
#include "Logger.h"
#include <iostream>
LogStream::LogStream(std::ostream& out)
: m_out(out) {
}
LogStream::~LogStream() {
m_out << "\033[m\n" << std::flush;
}
Logger::Logger() {
}
Logger::~Logger() {
}
LogStream Logger::error() {
std::clog << "\033[31m";
return LogStream(std::clog);
}
LogStream Logger::warning() {
std::clog << "\033[33m";
return LogStream(std::clog);
}
LogStream Logger::info() {
// std::clog << "\033[37m";
return LogStream(std::clog);
}
LogStream Logger::debug() {
std::clog << "\033[2m";
return LogStream(std::clog);
}
Logger logger;

39
src/Logger.h

@ -0,0 +1,39 @@
#pragma once
#include <ostream>
class LogStream {
public:
LogStream(std::ostream& out);
LogStream(const LogStream&) = delete;
~LogStream();
LogStream& operator=(const LogStream&) = delete;
template<typename T>
LogStream& operator<<(T&& value) {
m_out << std::forward<T>(value);
return *this;
}
private:
std::ostream& m_out;
};
class Logger {
public:
Logger();
Logger(const Logger&) = delete;
~Logger();
Logger& operator=(const Logger&) = delete;
LogStream error();
LogStream warning();
LogStream info();
LogStream debug();
};
extern Logger logger;

87
src/VkExpe.cpp

@ -0,0 +1,87 @@
#include "VkExpe.h"
#include "utils.h"
#include "Logger.h"
#include <stdexcept>
#include <iostream>
void SdlWindowDeleter::operator()(SDL_Window* window) const {
SDL_DestroyWindow(window);
}
VkExpe::VkExpe(int argc, char** argv) {
}
VkExpe::~VkExpe() {
shutdown();
}
void VkExpe::initialize() {
SDL_Init(SDL_INIT_EVENTS | SDL_INIT_VIDEO);
auto const window = SDL_CreateWindow(
"vk_expe",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
800, 600,
SDL_WINDOW_VULKAN
| SDL_WINDOW_ALLOW_HIGHDPI
| SDL_WINDOW_SHOWN
| SDL_WINDOW_RESIZABLE
);
m_window = WindowUP(
window
);
if(!m_window)
throw std::runtime_error("failed to create window");
m_vulkan.initialize(m_window.get());
}
void VkExpe::shutdown() {
m_vulkan.shutdown();
m_window.reset();
if (SDL_WasInit(0))
SDL_Quit();
}
void VkExpe::run() {
if(m_running)
throw std::runtime_error("calling run while already running");
initialize();
m_running = true;
auto running_guard = make_guard([this] {
m_running = false;
});
SDL_Event event;
while(m_running) {
while(SDL_PollEvent(&event)) {
switch(event.type) {
case SDL_QUIT:
m_running = false;
break;
case SDL_KEYDOWN: {
if(event.key.keysym.scancode == SDL_SCANCODE_ESCAPE)
m_running = false;
break;
}
case SDL_WINDOWEVENT: {
switch(event.window.event) {
case SDL_WINDOWEVENT_RESIZED: {
m_vulkan.invalidate_swapchain();
}
}
}
}
}
m_vulkan.draw_frame();
}
}

33
src/VkExpe.h

@ -0,0 +1,33 @@
#pragma once
#include "VulkanTutorial.h"
#include <SDL2/SDL.h>
#include <memory>
struct SdlWindowDeleter {
void operator()(SDL_Window* window) const;
};
using WindowUP = std::unique_ptr<SDL_Window, SdlWindowDeleter>;
class VkExpe {
public:
VkExpe(int argc, char** argv);
VkExpe(const VkExpe&) = delete;
~VkExpe();
VkExpe& operator=(const VkExpe&) = delete;
void initialize();
void shutdown();
void run();
private:
VulkanTutorial m_vulkan;
WindowUP m_window;
bool m_running = false;
};

972
src/VulkanTutorial.cpp

@ -0,0 +1,972 @@
#include "VulkanTutorial.h"
#include "utils.h"
#include "Logger.h"
#include <SDL2/SDL_vulkan.h>
#include <cassert>
#include <stdexcept>
#include <array>
#include <vector>
#include <algorithm>
#include <cstring>
PFN_vkCreateDebugUtilsMessengerEXT vkeCreateDebugUtilsMessengerEXT = nullptr;
PFN_vkDestroyDebugUtilsMessengerEXT vkeDestroyDebugUtilsMessengerEXT = nullptr;
VulkanTutorial::VulkanTutorial() {
}
VulkanTutorial::~VulkanTutorial() {
shutdown();
}
void VulkanTutorial::initialize(SDL_Window* window) {
m_window = window;
create_instance();
create_surface();
find_physical_device();
create_device();
create_swapchain();
create_render_pass();
create_graphic_pipeline();
create_framebuffers();
create_command_pool();
create_command_buffers();
create_sync_objects();
}
void VulkanTutorial::shutdown() {
if(!m_instance)
return;
vkDeviceWaitIdle(m_device);
for(VkFence fence: m_in_flight_fence)
vkDestroyFence(m_device, fence, nullptr);
m_in_flight_fence.clear();
for(VkSemaphore semaphore: m_render_finished_semaphores)
vkDestroySemaphore(m_device, semaphore, nullptr);
m_render_finished_semaphores.clear();
for(VkSemaphore semaphore: m_image_available_semaphores)
vkDestroySemaphore(m_device, semaphore, nullptr);
m_image_available_semaphores.clear();
if(m_command_pool != VK_NULL_HANDLE) {
vkDestroyCommandPool(m_device, m_command_pool, nullptr);
m_command_pool = VK_NULL_HANDLE;
}
cleanup_swap_chain();
if(m_device) {
vkDestroyDevice(m_device, nullptr);
m_device = nullptr;
}
if(m_surface) {
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
m_surface = nullptr;
}
if(m_debug_messenger != VK_NULL_HANDLE) {
vkeDestroyDebugUtilsMessengerEXT(
m_instance, m_debug_messenger, nullptr);
m_debug_messenger = VK_NULL_HANDLE;
}
vkDestroyInstance(m_instance, nullptr);
m_instance = nullptr;
m_physical_device = nullptr;
m_window = nullptr;
}
void VulkanTutorial::draw_frame() {
vkWaitForFences(m_device, 1, &m_in_flight_fence[m_frame_index], VK_TRUE, UINT64_MAX);
uint32_t image_index;
VkResult result = vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX, m_image_available_semaphores[m_frame_index], VK_NULL_HANDLE, &image_index);
if(result == VK_ERROR_OUT_OF_DATE_KHR) {
recreate_swap_chain();
return;
}
else if(result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
throw std::runtime_error("failed to acquire swapchain image");
if(m_images_in_flight[image_index] != VK_NULL_HANDLE)
vkWaitForFences(m_device, 1, &m_images_in_flight[image_index], VK_TRUE, UINT64_MAX);
m_images_in_flight[image_index] = m_in_flight_fence[image_index];
VkSemaphore wait_semaphores[] = {
m_image_available_semaphores[m_frame_index],
};
VkPipelineStageFlags stages[] = {
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
};
VkSemaphore signal_semaphores[] = {
m_render_finished_semaphores[m_frame_index],
};
VkSubmitInfo submit_info {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 1,
.pWaitSemaphores = wait_semaphores,
.pWaitDstStageMask = stages,
.commandBufferCount = 1,
.pCommandBuffers = &m_command_buffers[image_index],
.signalSemaphoreCount = 1,
.pSignalSemaphores = signal_semaphores,
};
vkResetFences(m_device, 1, &m_in_flight_fence[m_frame_index]);
if(vkQueueSubmit(m_graphic_queue, 1, &submit_info, m_in_flight_fence[m_frame_index]))
throw std::runtime_error("failed to submit draw command buffer");
VkPresentInfoKHR present_info {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = signal_semaphores,
.swapchainCount = 1,
.pSwapchains = &m_swapchain,
.pImageIndices = &image_index,
.pResults = nullptr,
};
result = vkQueuePresentKHR(m_presentation_queue, &present_info);
if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || m_invalid_swapchain)
recreate_swap_chain();
if(result != VK_SUCCESS && result != VK_ERROR_OUT_OF_DATE_KHR && result != VK_SUBOPTIMAL_KHR)
throw std::runtime_error("failed to present swapchain image");
m_frame_index = (m_frame_index + 1) % MAX_FRAMES_IN_FLIGHT;
}
void VulkanTutorial::invalidate_swapchain() {
m_invalid_swapchain = true;
}
std::vector<VkExtensionProperties> VulkanTutorial::available_extensions() {
uint32_t count;
vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);
std::vector<VkExtensionProperties> extensions(count);
vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data());
return extensions;
}
std::vector<VkLayerProperties> VulkanTutorial::available_layers() {
uint32_t count;
vkEnumerateInstanceLayerProperties(&count, nullptr);
std::vector<VkLayerProperties> layers(count);
vkEnumerateInstanceLayerProperties(&count, layers.data());
return layers;
}
VKAPI_ATTR VkBool32 VKAPI_CALL VulkanTutorial::print_debug_message(
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
VkDebugUtilsMessageTypeFlagsEXT type,
const VkDebugUtilsMessengerCallbackDataEXT* data,
void* user_data
) {
auto stream = [severity]() {
switch(severity) {
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
return logger.debug();
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
return logger.info();
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
return logger.warning();
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
return logger.error();
}
return logger.debug();
}();
switch(type) {
case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
stream << "[vk:general] ";
break;
case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:
stream << "[vk:validation] ";
break;
case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT:
stream << "[vk:performance] ";
break;
}
stream << data->pMessage;
if(severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
abort();
return VK_FALSE;
}
void VulkanTutorial::create_instance() {
if(m_instance)
return;
const char* name = "vk_expe";
const uint32_t version = VK_MAKE_VERSION(0, 1, 0);
VkApplicationInfo app_info {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = name,
.applicationVersion = version,
.pEngineName = name,
.engineVersion = version,
.apiVersion = VK_API_VERSION_1_1,
};
std::vector<const char*> layers;
std::vector<const char*> extensions;
{
unsigned count;
if(!SDL_Vulkan_GetInstanceExtensions(m_window, &count, nullptr))
throw std::runtime_error("failed to get window's vulkan extensions");
extensions.resize(count);
if(!SDL_Vulkan_GetInstanceExtensions(m_window, &count, extensions.data()))
throw std::runtime_error("failed to get window's vulkan extensions");
}
if(m_enableValidation) {
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
auto const available_layers = VulkanTutorial::available_layers();
auto add_layer_if_available = [&](const char* requested_layer) {
if(std::find_if(
available_layers.begin(),
available_layers.end(),
[requested_layer](const VkLayerProperties& layer) {
return std::strcmp(layer.layerName, requested_layer) == 0;
}
) != available_layers.end()) {
layers.push_back(requested_layer);
}
else {
logger.warning() << "requested layer '" << requested_layer
<< "' is not available";
}
};
add_layer_if_available("VK_LAYER_KHRONOS_validation");
}
m_debug_info = VkDebugUtilsMessengerCreateInfoEXT {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
.messageSeverity =
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
.messageType =
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
.pfnUserCallback = &print_debug_message,
.pUserData = nullptr,
};
VkInstanceCreateInfo instance_info {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pNext = &m_debug_info,
.pApplicationInfo = &app_info,
.enabledLayerCount = uint32_t(layers.size()),
.ppEnabledLayerNames = layers.data(),
.enabledExtensionCount = uint32_t(extensions.size()),
.ppEnabledExtensionNames = extensions.data(),
};
logger.debug() << "Requested extensions:";
for(const char* extension: extensions)
logger.debug() << " " << extension;
logger.debug() << "Requested layers:";
for(const char* layer: layers)
logger.debug() << " " << layer;
logger.debug() << "create vulkan instance...";
if(vkCreateInstance(&instance_info, nullptr, &m_instance) != VK_SUCCESS)
throw std::runtime_error("failed to create vulkan instance");
initialize_extension_functions();
if(m_enableValidation) {
vkeCreateDebugUtilsMessengerEXT(
m_instance, &m_debug_info, nullptr, &m_debug_messenger);
}
}
void VulkanTutorial::create_surface() {
if(!SDL_Vulkan_CreateSurface(m_window, m_instance, &m_surface))
throw std::runtime_error("failed to create surface");
}
void VulkanTutorial::find_physical_device() {
auto const physical_devices = get_physical_devices();
for(auto const physical_device: physical_devices) {
VkPhysicalDeviceProperties device_properties;
vkGetPhysicalDeviceProperties(physical_device, &device_properties);
int graphic_family = -1;
int presentation_family = -1;
auto const queue_families = get_queue_families(physical_device);
int index = 0;
for(auto const& queue_family: queue_families) {
if(queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT)
graphic_family = index;
VkBool32 support_surface = false;
vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, index, m_surface, &support_surface);
if(support_surface)
presentation_family = index;
index += 1;
}
if(graphic_family < 0 || presentation_family < 0)
continue;
auto const available_extensions = get_device_extensions(physical_device);
auto has_extension = [&available_extensions](const char* required_extension) {
return std::find_if(
available_extensions.begin(),
available_extensions.end(),
[required_extension](const VkExtensionProperties& extension) {
return strcmp(extension.extensionName, required_extension) == 0;
}
) != available_extensions.end();
};
if(!has_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME))
continue;
auto const surface_formats = get_surface_formats(physical_device);
if(surface_formats.empty())
continue;
auto const present_modes = get_present_modes(physical_device);
if(present_modes.empty())
continue;
logger.info() << "use suitable device: " << device_properties.deviceName;
m_physical_device = physical_device;
m_graphic_queue_family = uint32_t(graphic_family);
m_presentation_queue_family = uint32_t(presentation_family);
auto const surface_format_it = std::find_if(
surface_formats.begin(),
surface_formats.end(),
[](const VkSurfaceFormatKHR& surface_format) {
return surface_format.format == VK_FORMAT_B8G8R8A8_SRGB
&& surface_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
}
);
m_swapchain_format = (surface_format_it != surface_formats.end())?
*surface_format_it:
surface_formats[0];
logger.debug() << "swapchain format: " << m_swapchain_format.format;
m_present_mode = VK_PRESENT_MODE_FIFO_KHR;
logger.debug() << "present mode: " << m_present_mode;
break;
}
if(!m_physical_device)
throw std::runtime_error("failed to find suitable physical device");
}
void VulkanTutorial::create_device() {
std::vector<uint32_t> queue_families {
m_graphic_queue_family,
m_presentation_queue_family,
};
std::sort(queue_families.begin(), queue_families.end());
queue_families.erase(
std::unique(queue_families.begin(), queue_families.end()),
queue_families.end()
);
float queue_priority = 1.0f;
std::vector<VkDeviceQueueCreateInfo> queue_infos;
for(uint32_t queue_family: queue_families) {
queue_infos.emplace_back(VkDeviceQueueCreateInfo {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = queue_family,
.queueCount = 1,
.pQueuePriorities = &queue_priority,
});
}
VkPhysicalDeviceFeatures device_features {
};
std::vector<const char*> extensions {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
VkDeviceCreateInfo device_info {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = uint32_t(queue_infos.size()),
.pQueueCreateInfos = queue_infos.data(),
.enabledExtensionCount = uint32_t(extensions.size()),
.ppEnabledExtensionNames = extensions.data(),
.pEnabledFeatures = &device_features,
};
logger.debug() << "create vulkan device...";
if(vkCreateDevice(m_physical_device, &device_info, nullptr, &m_device) != VK_SUCCESS)
throw std::runtime_error("failed to create logical device");
vkGetDeviceQueue(m_device, m_graphic_queue_family, 0, &m_graphic_queue);
vkGetDeviceQueue(m_device, m_presentation_queue_family, 0, &m_presentation_queue);
}
void VulkanTutorial::create_swapchain() {
VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physical_device, m_surface, &capabilities);
m_swapchain_extent = capabilities.currentExtent;
if(m_swapchain_extent.width == UINT32_MAX) {
int width;
int height;
SDL_Vulkan_GetDrawableSize(m_window, &width, &height);
m_swapchain_extent.width = std::clamp(
uint32_t(width),
capabilities.minImageExtent.width,
capabilities.maxImageExtent.width
);
m_swapchain_extent.height = std::clamp(
uint32_t(height),
capabilities.minImageExtent.height,
capabilities.maxImageExtent.height
);
}
logger.debug() << "swapchain extent: " << m_swapchain_extent.width
<< ", " << m_swapchain_extent.height;
uint32_t image_count = capabilities.minImageCount + 1;
if(capabilities.maxImageCount != 0)
image_count = std::min(image_count, capabilities.maxImageCount);
logger.debug() << "swapchain image count: " << image_count;
bool share_image = (m_graphic_queue_family != m_presentation_queue_family);
uint32_t queue_families[] = {
m_graphic_queue_family,
m_presentation_queue_family,
};
VkSwapchainCreateInfoKHR swapchain_info {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = m_surface,
.minImageCount = image_count,
.imageFormat = m_swapchain_format.format,
.imageColorSpace = m_swapchain_format.colorSpace,
.imageExtent = m_swapchain_extent,
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.imageSharingMode = share_image?
VK_SHARING_MODE_CONCURRENT:
VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = share_image? 2u: 0u,
.pQueueFamilyIndices = share_image? queue_families: nullptr,
.preTransform = capabilities.currentTransform,
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
.presentMode = m_present_mode,
.clipped = VK_TRUE,
.oldSwapchain = VK_NULL_HANDLE,
};
if(vkCreateSwapchainKHR(m_device, &swapchain_info, nullptr, &m_swapchain) != VK_SUCCESS)
throw std::runtime_error("failed to create swapchain");
vkGetSwapchainImagesKHR(m_device, m_swapchain, &image_count, nullptr);
m_swapchain_images.resize(image_count);
vkGetSwapchainImagesKHR(m_device, m_swapchain, &image_count, m_swapchain_images.data());
m_swapchain_image_views.resize(image_count);
for(size_t index = 0; index < image_count; index += 1) {
VkImageViewCreateInfo view_info {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = m_swapchain_images[index],
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = m_swapchain_format.format,
.components = {
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
.g = VK_COMPONENT_SWIZZLE_IDENTITY,
.b = VK_COMPONENT_SWIZZLE_IDENTITY,
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
},
.subresourceRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
if(vkCreateImageView(m_device, &view_info, nullptr, &m_swapchain_image_views[index]) != VK_SUCCESS)
throw std::runtime_error("failed to create swapchain image view");
}
m_invalid_swapchain = false;
}
void VulkanTutorial::create_render_pass() {
VkAttachmentDescription color_attachment {
.format = m_swapchain_format.format,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
};
VkAttachmentReference color_attachment_ref = {
.attachment = 0,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
};
VkSubpassDescription subpass {
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = 1,
.pColorAttachments = &color_attachment_ref,
};
VkSubpassDependency subpass_dep = {
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
};
VkRenderPassCreateInfo render_pass_info {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = &color_attachment,
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = &subpass_dep,
};
if(vkCreateRenderPass(m_device, &render_pass_info, nullptr, &m_render_pass) != VK_SUCCESS)
throw std::runtime_error("failed to create render pass");
}
void VulkanTutorial::create_graphic_pipeline() {
auto const vertex_shader_module = create_shader_module_from_file("shaders/shader.vert.spv");
auto const vertex_shader_guard = make_guard([&]{
vkDestroyShaderModule(m_device, vertex_shader_module, nullptr);
});
auto const fragment_shader_module = create_shader_module_from_file("shaders/shader.frag.spv");
auto const fragment_shader_guard = make_guard([&]{
vkDestroyShaderModule(m_device, fragment_shader_module, nullptr);
});
VkPipelineShaderStageCreateInfo shader_stage_infos[] = {
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = vertex_shader_module,
.pName = "main",
},
{
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = fragment_shader_module,
.pName = "main",
},
};
VkPipelineVertexInputStateCreateInfo vertex_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 0,
.pVertexBindingDescriptions = nullptr,
.vertexAttributeDescriptionCount = 0,
.pVertexAttributeDescriptions = nullptr,
};
VkPipelineInputAssemblyStateCreateInfo assembly_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
.primitiveRestartEnable = VK_FALSE,
};
VkViewport viewport {
.x = 0.0f,
.y = 0.0f,
.width = float(m_swapchain_extent.width),
.height = float(m_swapchain_extent.height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
VkRect2D scissor = {
.offset = { 0, 0 },
.extent = m_swapchain_extent,
};
VkPipelineViewportStateCreateInfo viewport_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.pViewports = &viewport,
.scissorCount = 1,
.pScissors = &scissor,
};
VkPipelineRasterizationStateCreateInfo rasterization_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.depthClampEnable = VK_FALSE,
.rasterizerDiscardEnable = VK_FALSE,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_BACK_BIT,
.frontFace = VK_FRONT_FACE_CLOCKWISE,
.depthBiasEnable = VK_FALSE,
.depthBiasConstantFactor = 0.0f,
.depthBiasClamp = 0.0f,
.depthBiasSlopeFactor = 0.0f,
.lineWidth = 1.0f,
};
VkPipelineMultisampleStateCreateInfo multisample_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
.sampleShadingEnable = VK_FALSE,
.minSampleShading = 1.0f,
.pSampleMask = nullptr,
.alphaToCoverageEnable = VK_FALSE,
.alphaToOneEnable = VK_FALSE,
};
VkPipelineColorBlendAttachmentState color_blend_attachment {
.blendEnable = VK_FALSE,
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
.colorBlendOp = VK_BLEND_OP_ADD,
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
.alphaBlendOp = VK_BLEND_OP_ADD,
.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT,
};
VkPipelineColorBlendStateCreateInfo color_blend_info {
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.logicOpEnable = VK_FALSE,
.logicOp = VK_LOGIC_OP_COPY,
.attachmentCount = 1,
.pAttachments = &color_blend_attachment,
.blendConstants = { 0.0f, 0.0f, 0.0f, 0.0f },
};
VkPipelineLayoutCreateInfo layout_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 0,
.pSetLayouts = nullptr,
.pushConstantRangeCount = 0,
.pPushConstantRanges = nullptr,
};
if(vkCreatePipelineLayout(m_device, &layout_info, nullptr, &m_pipeline_layout) != VK_SUCCESS)
throw std::runtime_error("failed to create pipeline layout");
VkGraphicsPipelineCreateInfo pipeline_info {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = 2,
.pStages = shader_stage_infos,
.pVertexInputState = &vertex_info,
.pInputAssemblyState = &assembly_info,
.pViewportState = &viewport_info,
.pRasterizationState = &rasterization_info,
.pMultisampleState = &multisample_info,
.pDepthStencilState = nullptr,
.pColorBlendState = &color_blend_info,
.pDynamicState = nullptr,
.layout = m_pipeline_layout,
.renderPass = m_render_pass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE,
.basePipelineIndex = -1,
};
if(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &m_pipeline) != VK_SUCCESS)
throw std::runtime_error("failed to create graphic pipeline");
}
void VulkanTutorial::create_framebuffers() {
m_framebuffers.resize(m_swapchain_image_views.size());
for(size_t index = 0; index < m_framebuffers.size(); index += 1) {
VkFramebufferCreateInfo framebuffer_info {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = m_render_pass,
.attachmentCount = 1,
.pAttachments = &m_swapchain_image_views[index],
.width = m_swapchain_extent.width,
.height = m_swapchain_extent.height,
.layers = 1,
};
if(vkCreateFramebuffer(m_device, &framebuffer_info, nullptr, &m_framebuffers[index]) != VK_SUCCESS)
throw std::runtime_error("failed to create framebuffer");
}
}
void VulkanTutorial::create_command_pool() {
VkCommandPoolCreateInfo pool_info {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = 0,
.queueFamilyIndex = m_graphic_queue_family,
};
if(vkCreateCommandPool(m_device, &pool_info, nullptr, &m_command_pool) != VK_SUCCESS)
throw std::runtime_error("failed to create command pool");
}
void VulkanTutorial::create_command_buffers() {
m_command_buffers.resize(m_framebuffers.size());
VkCommandBufferAllocateInfo allocate_info {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_command_pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = uint32_t(m_command_buffers.size()),
};
if(vkAllocateCommandBuffers(m_device, &allocate_info, m_command_buffers.data()) != VK_SUCCESS)
throw std::runtime_error("failed to allocate command buffers");
for(size_t index = 0; index < m_command_buffers.size(); index += 1) {
VkCommandBuffer command_buffer = m_command_buffers[index];
VkCommandBufferBeginInfo begin_info {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = 0,
.pInheritanceInfo = nullptr,
};
logger.debug() << "record command buffer " << index << " (" << command_buffer << ")";
if(vkBeginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS)
throw std::runtime_error("failed to begin command buffer");
VkClearValue clear_color = {
.color = {
.float32 = { 0.0f, 0.0f, 0.0f, 1.0f }
}
};
VkRenderPassBeginInfo pass_info {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = m_render_pass,
.framebuffer = m_framebuffers[index],
.renderArea = {
.offset = { 0, 0 },
.extent = m_swapchain_extent,
},
.clearValueCount = 1,
.pClearValues = &clear_color,
};
vkCmdBeginRenderPass(command_buffer, &pass_info, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline);
vkCmdDraw(command_buffer, 3, 1, 0, 0);
vkCmdEndRenderPass(command_buffer);
if(vkEndCommandBuffer(command_buffer) != VK_SUCCESS)
throw("failed to record command buffer");
}
}
void VulkanTutorial::create_sync_objects() {
m_image_available_semaphores.resize(MAX_FRAMES_IN_FLIGHT);
m_render_finished_semaphores.resize(MAX_FRAMES_IN_FLIGHT);
m_in_flight_fence.resize(MAX_FRAMES_IN_FLIGHT);
m_images_in_flight.resize(m_swapchain_images.size(), VK_NULL_HANDLE);
VkSemaphoreCreateInfo semaphore_info {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
VkFenceCreateInfo fence_info {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
};
for(size_t index = 0; index < MAX_FRAMES_IN_FLIGHT; index += 1) {
if(vkCreateSemaphore(m_device, &semaphore_info, nullptr, &m_image_available_semaphores[index]) != VK_SUCCESS)
throw std::runtime_error("failed to create semaphore");
if(vkCreateSemaphore(m_device, &semaphore_info, nullptr, &m_render_finished_semaphores[index]) != VK_SUCCESS)
throw std::runtime_error("failed to create semaphore");
if(vkCreateFence(m_device, &fence_info, nullptr, &m_in_flight_fence[index]) != VK_SUCCESS)
throw std::runtime_error("failed to create fence");
}
}
void VulkanTutorial::cleanup_swap_chain() {
if (m_command_pool != VK_NULL_HANDLE && m_command_buffers.size() != 0)
vkFreeCommandBuffers(m_device, m_command_pool, uint32_t(m_command_buffers.size()), m_command_buffers.data());
m_command_buffers.clear();
for(VkFramebuffer framebuffer: m_framebuffers)
vkDestroyFramebuffer(m_device, framebuffer, nullptr);
m_framebuffers.clear();
if(m_pipeline != VK_NULL_HANDLE) {
vkDestroyPipeline(m_device, m_pipeline, nullptr);
m_pipeline = VK_NULL_HANDLE;
}
if(m_pipeline_layout != VK_NULL_HANDLE) {
vkDestroyPipelineLayout(m_device, m_pipeline_layout, nullptr);
m_pipeline_layout = VK_NULL_HANDLE;
}
if(m_render_pass != VK_NULL_HANDLE) {
vkDestroyRenderPass(m_device, m_render_pass, nullptr);
m_render_pass = VK_NULL_HANDLE;
}
for(VkImageView view: m_swapchain_image_views)
vkDestroyImageView(m_device, view, nullptr);
m_swapchain_image_views.clear();
if(m_swapchain) {
vkDestroySwapchainKHR(m_device, m_swapchain, nullptr);
m_swapchain_images.clear();
m_swapchain = nullptr;
}
}
void VulkanTutorial::recreate_swap_chain() {
vkDeviceWaitIdle(m_device);
logger.info() << "recreate swapchain";
cleanup_swap_chain();
create_swapchain();
create_render_pass();
create_graphic_pipeline();
create_framebuffers();
create_command_buffers();
}
VkShaderModule VulkanTutorial::create_shader_module(const std::vector<char> bytecode) {
VkShaderModuleCreateInfo shader_info {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = bytecode.size(),
.pCode = reinterpret_cast<const uint32_t*>(bytecode.data()),
};
VkShaderModule shader_module = VK_NULL_HANDLE;
if(vkCreateShaderModule(m_device, &shader_info, nullptr, &shader_module) != VK_SUCCESS)
throw std::runtime_error("failed to create shader module");
return shader_module;
}
VkShaderModule VulkanTutorial::create_shader_module_from_file(const char* path) {
auto const bytecode = read_binary_file(path);
try {
return create_shader_module(bytecode);
}
catch(std::exception err) {
throw std::runtime_error(cat("failed to create shader '", path, "':\n ", err.what()));
}
return VK_NULL_HANDLE;
}
std::vector<VkPhysicalDevice> VulkanTutorial::get_physical_devices() const {
uint32_t devices_count = 0;
vkEnumeratePhysicalDevices(m_instance, &devices_count, nullptr);
std::vector<VkPhysicalDevice> physical_devices(devices_count);
vkEnumeratePhysicalDevices(m_instance, &devices_count, physical_devices.data());
return physical_devices;
}
std::vector<VkQueueFamilyProperties> VulkanTutorial::get_queue_families(VkPhysicalDevice physical_device) const {
uint32_t queue_families_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, nullptr);
std::vector<VkQueueFamilyProperties> queue_families(queue_families_count);
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, queue_families.data());
return queue_families;
}
std::vector<VkExtensionProperties> VulkanTutorial::get_device_extensions(VkPhysicalDevice physical_device) const {
uint32_t extensions_count = 0;
vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensions_count, nullptr);
std::vector<VkExtensionProperties> extensions(extensions_count);
vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensions_count, extensions.data());
return extensions;
}
std::vector<VkSurfaceFormatKHR> VulkanTutorial::get_surface_formats(VkPhysicalDevice physical_device) const {
uint32_t surface_formats_count = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &surface_formats_count, nullptr);
std::vector<VkSurfaceFormatKHR> surface_formats(surface_formats_count);
vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &surface_formats_count, surface_formats.data());
return surface_formats;
}
std::vector<VkPresentModeKHR> VulkanTutorial::get_present_modes(VkPhysicalDevice physical_device) const {
uint32_t present_modes_count = 0;
vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_modes_count, nullptr);
std::vector<VkPresentModeKHR> present_modes(present_modes_count);
vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_modes_count, present_modes.data());
return present_modes;
}
void VulkanTutorial::initialize_extension_functions() {
uint32_t errors_count = 0;
#define GET_PROC_ADDR(func_name) \
vke ## func_name = (PFN_vk ## func_name)vkGetInstanceProcAddr( \
m_instance, "vk" #func_name); \
if(vke ## func_name == nullptr) \
{ \
logger.error() << "failed to load extension function 'vk" #func_name "'"; \
errors_count += 1; \
}
GET_PROC_ADDR(CreateDebugUtilsMessengerEXT)
GET_PROC_ADDR(DestroyDebugUtilsMessengerEXT)
if(errors_count != 0)
throw std::runtime_error("failed to load extensions");
}

106
src/VulkanTutorial.h

@ -0,0 +1,106 @@
#pragma once
#include <SDL2/SDL.h>
#include <vulkan/vulkan.h>
#include <vector>
#if defined(VKEXPE_ENABLE_VALIDATION) || !defined(NDEBUG)
#define VKEXPE_VALIDATION true
#else
#define VKEXPE_VALIDATION false
#endif
extern PFN_vkCreateDebugUtilsMessengerEXT vkeCreateDebugUtilsMessengerEXT;
extern PFN_vkDestroyDebugUtilsMessengerEXT vkeDestroyDebugUtilsMessengerEXT;
class VulkanTutorial {
public:
VulkanTutorial();
VulkanTutorial(const VulkanTutorial&) = delete;
~VulkanTutorial();
VulkanTutorial& operator=(const VulkanTutorial&) = delete;
void initialize(SDL_Window* window);
void shutdown();
void draw_frame();
void invalidate_swapchain();
static std::vector<VkExtensionProperties> available_extensions();
static std::vector<VkLayerProperties> available_layers();
static VKAPI_ATTR VkBool32 VKAPI_CALL print_debug_message(
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
VkDebugUtilsMessageTypeFlagsEXT type,
const VkDebugUtilsMessengerCallbackDataEXT* data,
void* user_data
);
private:
void create_instance();
void create_surface();
void find_physical_device();
void create_device();
void create_swapchain();
void create_render_pass();
void create_graphic_pipeline();
void create_framebuffers();
void create_command_pool();
void create_command_buffers();
void create_sync_objects();
void cleanup_swap_chain();
void recreate_swap_chain();
VkShaderModule create_shader_module(const std::vector<char> bytecode);
VkShaderModule create_shader_module_from_file(const char* path);
std::vector<VkPhysicalDevice> get_physical_devices() const;
std::vector<VkQueueFamilyProperties> get_queue_families(VkPhysicalDevice physical_device) const;
std::vector<VkExtensionProperties> get_device_extensions(VkPhysicalDevice physical_device) const;
std::vector<VkSurfaceFormatKHR> get_surface_formats(VkPhysicalDevice physical_device) const;
std::vector<VkPresentModeKHR> get_present_modes(VkPhysicalDevice physical_device) const;
void initialize_extension_functions();
private:
SDL_Window* m_window = nullptr;
VkInstance m_instance = nullptr;
VkDebugUtilsMessengerEXT m_debug_messenger = VK_NULL_HANDLE;
VkSurfaceKHR m_surface;
VkPhysicalDevice m_physical_device = nullptr;
VkDevice m_device = nullptr;
VkQueue m_graphic_queue = nullptr;
VkQueue m_presentation_queue = nullptr;
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE;
std::vector<VkImage> m_swapchain_images;
std::vector<VkImageView> m_swapchain_image_views;
VkRenderPass m_render_pass = VK_NULL_HANDLE;
VkPipelineLayout m_pipeline_layout = VK_NULL_HANDLE;
VkPipeline m_pipeline = VK_NULL_HANDLE;
std::vector<VkFramebuffer> m_framebuffers;
VkCommandPool m_command_pool = VK_NULL_HANDLE;
std::vector<VkCommandBuffer> m_command_buffers;
std::vector<VkSemaphore> m_image_available_semaphores;
std::vector<VkSemaphore> m_render_finished_semaphores;
std::vector<VkFence> m_in_flight_fence;
std::vector<VkFence> m_images_in_flight;
uint32_t m_graphic_queue_family = -1;
uint32_t m_presentation_queue_family = -1;
VkSurfaceFormatKHR m_swapchain_format;
VkPresentModeKHR m_present_mode;
VkExtent2D m_swapchain_extent;
size_t m_frame_index = 0;
bool m_invalid_swapchain = true;
VkDebugUtilsMessengerCreateInfoEXT m_debug_info;
bool m_enableValidation = VKEXPE_VALIDATION;
static constexpr size_t MAX_FRAMES_IN_FLIGHT = 2;
};

19
src/main.cpp

@ -0,0 +1,19 @@
#include "Logger.h"
#include "VkExpe.h"
#include <iostream>
int main(int argc, char** argv) {
VkExpe vk_expe(argc, argv);
try {
vk_expe.run();
}
catch(std::runtime_error err) {
logger.error() << "Error: " << err.what();
return 1;
}
return 0;
}

18
src/utils.cpp

@ -0,0 +1,18 @@
#include "utils.h"
#include <fstream>
std::vector<char> read_binary_file(const char* path) {
std::ifstream istream(path, std::ios_base::ate | std::ios_base::binary);
if(!istream.is_open())
throw std::runtime_error(cat("failed to open '", path, "'"));
std::vector<char> buffer(size_t(istream.tellg()));
istream.seekg(0);
istream.read(buffer.data(), buffer.size());
istream.close();
return buffer;
}

34
src/utils.h

@ -0,0 +1,34 @@
#pragma once
#include <vector>
#include <string>
#include <sstream>
template<typename T>
struct Guard {
Guard(T&& teardown)
: m_teardown(std::move(teardown))
{}
~Guard() {
m_teardown();
}
private:
T m_teardown;
};
template<typename T>
Guard<T> make_guard(T&& teardown) {
return Guard<T>(std::move(teardown));
}
template<typename... Args>
std::string cat(Args&&... args) {
std::ostringstream ostream;
(ostream << ... << args);
return ostream.str();
}
std::vector<char> read_binary_file(const char* path);
Loading…
Cancel
Save