commit
ed35935f7e
14 changed files with 1437 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||||
|
/.vscode |
||||
|
/build |
||||
@ -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,0 +1,9 @@ |
|||||
|
#version 450 |
||||
|
|
||||
|
layout(location = 0) in vec3 fragColor; |
||||
|
|
||||
|
layout(location = 0) out vec4 outColor; |
||||
|
|
||||
|
void main() { |
||||
|
outColor = vec4(fragColor, 1.0); |
||||
|
} |
||||
@ -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]; |
||||
|
} |
||||
@ -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; |
||||
@ -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; |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
}; |
||||
@ -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"); |
||||
|
} |
||||
@ -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; |
||||
|
}; |
||||
@ -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; |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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…
Reference in new issue