Initial commit.
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/.vscode
|
||||
/build
|
||||
57
CMakeLists.txt
Normal file
57
CMakeLists.txt
Normal file
@@ -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)
|
||||
9
shaders/shader.frag
Normal file
9
shaders/shader.frag
Normal file
@@ -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
Normal file
20
shaders/shader.vert
Normal file
@@ -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
Normal file
41
src/Logger.cpp
Normal file
@@ -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
Normal file
39
src/Logger.h
Normal file
@@ -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
Normal file
87
src/VkExpe.cpp
Normal file
@@ -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
Normal file
33
src/VkExpe.h
Normal file
@@ -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
Normal file
972
src/VulkanTutorial.cpp
Normal file
@@ -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
Normal file
106
src/VulkanTutorial.h
Normal file
@@ -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
Normal file
19
src/main.cpp
Normal file
@@ -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
Normal file
18
src/utils.cpp
Normal file
@@ -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
Normal file
34
src/utils.h
Normal file
@@ -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);
|
||||
Reference in New Issue
Block a user