#include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GLFW_INCLUDE_VULKAN #include #include #ifdef HAVE_NCURSES #include #endif #include "fb/chfb.hpp" #include "fb/pixfb.hpp" #include "o3d/scene.hpp" #include "o3d/mesh.hpp" #include "o3d/obj3d.hpp" #include "o3d/vertex_data.hpp" #include "o3d/tri.hpp" #include "o3d/camera.hpp" #include "math/vector.hpp" #include "math/mat4.hpp" #include "math/quat.hpp" #include "math/tform.hpp" #include "ctrl/keyboard.hpp" #include "ctrl/mouse.hpp" #include "renderer.hpp" #include "obj_parser.hpp" #include "vulkan_utils.hpp" using engine::Renderer, engine::fb::CharacterFrameBuffer, engine::fb::PixelFrameBuffer, engine::o3d::Scene, engine::o3d::Mesh, engine::o3d::Triangle, engine::o3d::Camera, engine::o3d::VertexData, engine::math::Vector2, engine::math::Vector3, engine::math::Vector4, engine::math::Matrix4, engine::math::Quaternion, engine::controllers::Keyboard, engine::controllers::KeyboardKey, engine::controllers::Mouse; #define FPS 60 #define PI 3.1415926535f #define MODE_HELP 0 #define MODE_TERM 1 #define MODE_GRAPHICAL 2 #define GAME_PLANE 0 #define GAME_SUZANNE 1 #define GAME_PHYSICS 2 #define GAME GAME_PHYSICS static void print_usage(std::ostream& output_stream) { output_stream << "Usage: ./engine [-htg] [--help] [--term] [--graphical]\n" << " -h, --help show usage (this)\n" << " -t, --term terminal mode\n" << " -g, --graphical graphical mode (default)\n" << std::flush; } [[noreturn]] static void usage_error_exit() { print_usage(std::cerr); std::exit(EXIT_FAILURE); } extern Camera* camera; Camera* camera; template static void scene_main(Renderer& renderer, const Matrix4& final_transform_mat, UpdateFrameFn update_frame) { bool cont = true; Scene scene{ {90.f * PI / 180.f, {{0.f, 1.8f, 7.f}, Quaternion::one(), {1.f, 1.f, 1.f}}}, { #if GAME == GAME_PLANE { Mesh::plane(2.f, 2.f), { Vector3(0.f, 0.f, 0.f), Quaternion::one(), Vector3(1.f, 1.f, 1.f), } }, #elif GAME == GAME_SUZANNE { engine::parse_object(DATADIR "/assets/suzanne.obj"), { Vector3(0.f, 0.f, 0.f), Quaternion::one(), Vector3(1.f, 1.f, 1.f), } }, #elif GAME == GAME_PHYSICS { Mesh::plane(10.f, 10.f), { Vector3(0.f, 0.f, 0.f), Quaternion::one(), Vector3(1.f, 1.f, 1.f), } }, { engine::parse_object(DATADIR "/assets/suzanne.obj"), { Vector3(0.f, 1.f, 0.f), Quaternion::one(), Vector3(1.f, 1.f, 1.f), } }, #endif } }; float rx = 0.f, ry = 0.f; Keyboard kb{[&](KeyboardKey key) { (void) key; }, [&](KeyboardKey key) { (void) key; }}; Mouse mouse{[&](Vector2 rel) { rx += -rel.y; ry += -rel.x; if (rx < -PI / 2.f) rx = -PI / 2.f; if (rx > PI / 2.f) rx = PI / 2.f; scene.camera.transform.rot = Quaternion::euler_zxy(rx, ry, 0.f); }}; camera = &scene.camera; while (cont) { renderer.clear(); auto pre_final_mat = final_transform_mat * scene.camera.to_mat4(static_cast(renderer.height()) / static_cast(renderer.width()), .5f, 12.f); for (const auto& obj : scene.objs) { auto obj_mat = obj.transform.to_mat4(); auto final_mat = pre_final_mat * obj_mat; const auto& mesh = obj.mesh; std::vector vertices; std::vector vertices_data; for (const auto& vertex : mesh.vertices) { vertices.push_back(final_mat * vertex); vertices_data.push_back(VertexData((obj_mat * vertex).xyz())); } for (const auto& triangle_indices : mesh.indices) { [&](std::integer_sequence) { renderer.draw_triangle({{vertices[triangle_indices[j][0]], mesh.normals[triangle_indices[j][1]], vertices_data[triangle_indices[j][0]]}...}); }(std::make_integer_sequence()); } } cont = update_frame(scene, kb, mouse); Vector3 movement(0.f, 0.f, 0.f); if (kb.is_down(KeyboardKey::fw)) movement.z += -1.f; if (kb.is_down(KeyboardKey::key_left)) movement.x += -1.f; if (kb.is_down(KeyboardKey::bw)) movement.z += +1.f; if (kb.is_down(KeyboardKey::key_right)) movement.x += +1.f; if (kb.is_down(KeyboardKey::fw) || kb.is_down(KeyboardKey::key_left) || kb.is_down(KeyboardKey::bw) || kb.is_down(KeyboardKey::key_right)) movement.normalize(); scene.camera.transform.loc += movement.rot(Quaternion::rot_y(ry)) * .05f; scene.camera.fov = (kb.is_down(KeyboardKey::zoom) ? 40.f : 80.f) * PI / 180.f; } } #ifdef HAVE_NCURSES #define MKEY_ESC 27 static int main_term() { // init std::setlocale(LC_ALL, ""); initscr(); cbreak(); noecho(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE); set_escdelay(0); curs_set(0); int w, h; getmaxyx(stdscr, h, w); Renderer renderer{CharacterFrameBuffer{static_cast(w), static_cast(h)}}; scene_main(renderer, Matrix4::scale(Vector3(2.f, 1.f, 1.f)), [&](Scene& scene, auto& kb, auto& mouse) { (void) scene; mvaddnstr(0, 0, renderer.fb.chars(), renderer.width() * renderer.height()); bool cont = true; std::optional key; std::optional rel; //timeout(1000 / FPS); timeout(10); int c = getch(); switch (c) { case 'z': key = KeyboardKey::fw; break; case 'q': key = KeyboardKey::key_left; break; case 's': key = KeyboardKey::bw; break; case 'd': key = KeyboardKey::key_right; break; case 'p': key = KeyboardKey::zoom; break; case KEY_UP: rel = Vector2(0.f, -.1f); break; case KEY_LEFT: rel = Vector2(-.1f, 0.f); break; case KEY_DOWN: rel = Vector2(0.f, +.1f); break; case KEY_RIGHT: rel = Vector2(+.1f, 0.f); break; case MKEY_ESC: return false; } if (key && *key == KeyboardKey::fw) { if (!kb.is_down(KeyboardKey::fw)) kb.key_down_event(KeyboardKey::fw); } else { if (kb.is_down(KeyboardKey::fw)) kb.key_up_event(KeyboardKey::fw); } if (key && *key == KeyboardKey::key_left) { if (!kb.is_down(KeyboardKey::key_left)) kb.key_down_event(KeyboardKey::key_left); } else { if (kb.is_down(KeyboardKey::key_left)) kb.key_up_event(KeyboardKey::key_left); } if (key && *key == KeyboardKey::bw) { if (!kb.is_down(KeyboardKey::bw)) kb.key_down_event(KeyboardKey::bw); } else { if (kb.is_down(KeyboardKey::bw)) kb.key_up_event(KeyboardKey::bw); } if (key && *key == KeyboardKey::key_right) { if (!kb.is_down(KeyboardKey::key_right)) kb.key_down_event(KeyboardKey::key_right); } else { if (kb.is_down(KeyboardKey::key_right)) kb.key_up_event(KeyboardKey::key_right); } if (key && *key == KeyboardKey::zoom) { if (kb.is_down(KeyboardKey::zoom)) kb.key_up_event(KeyboardKey::zoom); else kb.key_down_event(KeyboardKey::zoom); } if (rel) mouse.mouse_motion_event(*rel); getmaxyx(stdscr, h, w); renderer.resize(static_cast(w), static_cast(h)); return cont; } ); // terminate endwin(); return EXIT_SUCCESS; } #endif #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 static const std::vector validation_layers = { "VK_LAYER_KHRONOS_validation", }; static const std::vector physical_device_ext_names = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_SPIRV_1_4_EXTENSION_NAME, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME, }; #ifdef NDEBUG const bool enable_validation_layers = false; #else const bool enable_validation_layers = true; #endif struct PhysicalDeviceEntry { uint32_t idx; VkPhysicalDevice physical_device; const char* name; uint32_t graphics_queue_family_index, present_queue_family_index; VkPhysicalDeviceFeatures2 features; }; static bool check_validation_layer_support() { auto avail_layers = [&]() { uint32_t avail_layers_count; if (VkResult res = vkEnumerateInstanceLayerProperties(&avail_layers_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to enumerate instance layer properties, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::vector avail_layers(avail_layers_count); if (VkResult res = vkEnumerateInstanceLayerProperties(&avail_layers_count, avail_layers.data()); res != VK_SUCCESS) { std::cerr << "failed to enumerate instance layer properties, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return avail_layers; }(); for (const char* layer_name : validation_layers) { if (std::ranges::find_if(avail_layers, [&](const auto& props) { return strcmp(layer_name, props.layerName) == 0; }) == avail_layers.end()) return false; } return true; } static std::string severity_to_str(VkDebugUtilsMessageSeverityFlagBitsEXT severity) { switch (severity) { case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: return "VERBOSE"; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: return "INFO"; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: return "WARNING"; case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: return "ERROR"; default: std::unreachable(); } } static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback( VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT /* message_type */, const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* /* user_data */) { std::cerr << "validation layer: [" << severity_to_str(message_severity) << "] " << callback_data->pMessage << std::endl; return VK_FALSE; } static void populate_msger_ci(VkDebugUtilsMessengerCreateInfoEXT& msger_ci) { msger_ci.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; msger_ci.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; msger_ci.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; msger_ci.pfnUserCallback = debug_callback; msger_ci.pUserData = nullptr; } static std::tuple, std::optional> find_queue_family_indices( VkPhysicalDevice physical_device, const std::vector& queue_family_properties, VkSurfaceKHR surface) { std::optional graphics_queue_family_index, present_queue_family_index; for (uint32_t i = 0; i < queue_family_properties.size(); i++) { const auto& prop = queue_family_properties[i]; bool is_graphics_queue = (prop.queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) != static_cast(0); VkBool32 is_presentation_queue; if (VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, i, surface, &is_presentation_queue); res != VK_SUCCESS) { std::cerr << "failed to check if queue family supports surface, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } if (is_graphics_queue && is_presentation_queue) { graphics_queue_family_index = i; present_queue_family_index = i; break; } else if (is_graphics_queue && !graphics_queue_family_index) { graphics_queue_family_index = i; } else if (is_presentation_queue && !present_queue_family_index) { present_queue_family_index = i; } } return { graphics_queue_family_index, present_queue_family_index }; } static void transition_image_layout(VkCommandBuffer cmd_buf, VkImage img, VkImageLayout old_layout, VkImageLayout new_layout, VkAccessFlags2 src_access_mask, VkAccessFlags2 dst_access_mask, VkPipelineStageFlags2 src_stage_mask, VkPipelineStageFlags2 dst_stage_mask) { VkImageMemoryBarrier2 img_mem_barrier { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, .pNext = nullptr, .srcStageMask = src_stage_mask, .srcAccessMask = src_access_mask, .dstStageMask = dst_stage_mask, .dstAccessMask = dst_access_mask, .oldLayout = old_layout, .newLayout = new_layout, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = img, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; VkDependencyInfo dependency_info { .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .pNext = nullptr, .dependencyFlags = {}, .memoryBarrierCount = 0, .pMemoryBarriers = {}, .bufferMemoryBarrierCount = 0, .pBufferMemoryBarriers = {}, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &img_mem_barrier, }; vkCmdPipelineBarrier2(cmd_buf, &dependency_info); } static int main_graphical() { // init window glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); GLFWwindow* window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Engine", nullptr, nullptr); // init Vulkan std::cout << "Vulkan loader version: " << engine::myvk::api { VK_HEADER_VERSION_COMPLETE } << std::endl; // init Vulkan - create instance if (enable_validation_layers && !check_validation_layer_support()) { std::cerr << "validation layers requested, but not available!" << std::endl; std::exit(EXIT_FAILURE); } auto instance = [&]() { VkApplicationInfo app_info { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = nullptr, .pApplicationName = "engine - test", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_API_VERSION_1_4, }; auto instance_exts = [&]() { std::vector instance_exts; { uint32_t glfw_extension_count; const char** glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count); instance_exts.insert(instance_exts.end(), glfw_extensions, glfw_extensions + glfw_extension_count); } if (enable_validation_layers) instance_exts.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); return instance_exts; }(); auto avail_instance_exts = [&]() { uint32_t avail_instance_exts_count; if (VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &avail_instance_exts_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to enumerate instance extension properties, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::vector avail_instance_exts(avail_instance_exts_count); if (VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &avail_instance_exts_count, avail_instance_exts.data()); res != VK_SUCCESS) { std::cerr << "failed to enumerate instance extension properties, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return avail_instance_exts; }(); std::cout << "required instance extensions:\n"; for (const auto& extension_name : instance_exts) { std::cout << (std::ranges::find_if(avail_instance_exts, [&](const auto& avail_ext) { return strcmp(avail_ext.extensionName, extension_name) == 0; }) == avail_instance_exts.end() ? "!" : " "); std::cout << " " << extension_name << "\n"; } VkInstanceCreateInfo instance_ci { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = nullptr, .flags = {}, .pApplicationInfo = &app_info, .enabledLayerCount = {}, .ppEnabledLayerNames = {}, .enabledExtensionCount = static_cast(instance_exts.size()), .ppEnabledExtensionNames = instance_exts.data(), }; VkDebugUtilsMessengerCreateInfoEXT inst_msger_ci{}; if (enable_validation_layers) { populate_msger_ci(inst_msger_ci); instance_ci.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &inst_msger_ci; instance_ci.enabledLayerCount = static_cast(validation_layers.size()); instance_ci.ppEnabledLayerNames = validation_layers.data(); } VkInstance instance; if (VkResult res = vkCreateInstance(&instance_ci, nullptr, &instance); res != VK_SUCCESS) { std::cerr << "failed to create instance, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return instance; }(); VkDebugUtilsMessengerEXT debug_messenger; if (enable_validation_layers) { VkDebugUtilsMessengerCreateInfoEXT msger_ci{}; populate_msger_ci(msger_ci); auto create_debug_messenger = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (!create_debug_messenger) { std::cerr << "failed to set up debug messenger!" << std::endl; std::exit(EXIT_FAILURE); } create_debug_messenger(instance, &msger_ci, nullptr, &debug_messenger); } // create window surface auto surface = [&]() { VkSurfaceKHR surface; if (VkResult res = glfwCreateWindowSurface(instance, window, nullptr, &surface); res != VK_SUCCESS) { std::cerr << "failed to create window surface, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return surface; }(); // select physical device and queues auto [physical_device, graphics_queue_family_index, present_queue_family_index, device_features] = [&]() { std::multimap physical_devices; auto avail_physical_devices = [&]() { uint32_t avail_physical_devices_count; if (VkResult res = vkEnumeratePhysicalDevices(instance, &avail_physical_devices_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to enumerate physical devices, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::vector avail_physical_devices(avail_physical_devices_count); if (VkResult res = vkEnumeratePhysicalDevices(instance, &avail_physical_devices_count, avail_physical_devices.data()); res != VK_SUCCESS) { std::cerr << "failed to enumerate physical devices, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return avail_physical_devices; }(); if (avail_physical_devices.empty()) { std::cerr << "failed to find physical devices with Vulkan support" << std::endl; std::exit(EXIT_FAILURE); } std::cout << "devices:" << std::endl; for (uint32_t i = 0; i < avail_physical_devices.size(); i++) { const auto& avail_physical_device = avail_physical_devices[i]; auto physical_device_props = [&]() { VkPhysicalDeviceProperties2 physical_device_props{}; physical_device_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; vkGetPhysicalDeviceProperties2(avail_physical_device, &physical_device_props); return physical_device_props; }(); std::cout << " " << (i + 1) << ". " << physical_device_props.properties.deviceName << ":" << std::endl; std::cout << " apiVersion: " << engine::myvk::api { physical_device_props.properties.apiVersion } << std::endl; auto physical_device_features = [&]() { VkPhysicalDeviceFeatures2 physical_device_features{}; physical_device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; vkGetPhysicalDeviceFeatures2(avail_physical_device, &physical_device_features); return physical_device_features; }(); auto physical_device_ext_props = [&]() { uint32_t physical_device_ext_props_count; if (VkResult res = vkEnumerateDeviceExtensionProperties(avail_physical_device, nullptr, &physical_device_ext_props_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to enumerate physical device extension properties, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::vector physical_device_ext_props(physical_device_ext_props_count); if (VkResult res = vkEnumerateDeviceExtensionProperties(avail_physical_device, nullptr, &physical_device_ext_props_count, physical_device_ext_props.data()); res != VK_SUCCESS) { std::cerr << "failed to enumerate physical device extension properties, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return physical_device_ext_props; }(); std::cout << " required physical device extensions:" << std::endl; for (const auto& ext : physical_device_ext_names) { std::cout << " " << (std::ranges::find_if(physical_device_ext_props, [&](const auto& avail_ext) { return strcmp(avail_ext.extensionName, ext) == 0; }) == physical_device_ext_props.end() ? "!" : " "); std::cout << " " << ext << std::endl; } auto queue_family_properties = [&]() { uint32_t queue_family_properties_count; vkGetPhysicalDeviceQueueFamilyProperties2(avail_physical_device, &queue_family_properties_count, nullptr); std::vector queue_family_properties(queue_family_properties_count); for (auto& elt : queue_family_properties) elt.sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2; vkGetPhysicalDeviceQueueFamilyProperties2(avail_physical_device, &queue_family_properties_count, queue_family_properties.data()); return queue_family_properties; }(); auto [graphics_queue_family_index, present_queue_family_index] = find_queue_family_indices(avail_physical_device, queue_family_properties, surface); std::cout << " graphics queue family index: "; if (graphics_queue_family_index) std::cout << *graphics_queue_family_index; else std::cout << "none"; std::cout << std::endl; std::cout << " presentation queue family index: "; if (present_queue_family_index) std::cout << *present_queue_family_index; else std::cout << "none"; std::cout << std::endl; auto score = [&]() { if (VK_API_VERSION_VARIANT(physical_device_props.properties.apiVersion) != 0 || physical_device_props.properties.apiVersion < VK_API_VERSION_1_4) return std::optional {}; if (!graphics_queue_family_index || !present_queue_family_index) return std::optional {}; if (std::ranges::find_if_not(physical_device_ext_names, [&](const auto& device_ext) { return std::ranges::find_if(physical_device_ext_props, [&](const auto& ext_prop) { return strcmp(ext_prop.extensionName, device_ext) == 0; }) != physical_device_ext_props.end(); }) != physical_device_ext_names.end()) return std::optional {}; unsigned score = 0; if (*graphics_queue_family_index == *present_queue_family_index) score += 5; score += [&](VkPhysicalDeviceType device_type) { switch (device_type) { case VK_PHYSICAL_DEVICE_TYPE_OTHER: return 0; case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: return 2; case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: return 10; case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: return 5; case VK_PHYSICAL_DEVICE_TYPE_CPU: return 1; default: { std::cerr << "device type not supported: " << string_VkPhysicalDeviceType(device_type) << std::endl; std::exit(EXIT_FAILURE); } } }(physical_device_props.properties.deviceType); return std::optional { score }; }(); std::cout << " is suitable: " << std::boolalpha << static_cast(score) << std::noboolalpha << std::endl; if (score) physical_devices.insert({ *score, { .idx = i, .physical_device = avail_physical_device, .name = physical_device_props.properties.deviceName, .graphics_queue_family_index = *graphics_queue_family_index, .present_queue_family_index = *present_queue_family_index, .features = physical_device_features, } }); } std::cout << std::endl; if (physical_devices.empty()) { std::cerr << "no suitable physical device found" << std::endl; std::exit(EXIT_FAILURE); } auto best = physical_devices.crbegin(); std::cout << "picking: " << (best->second.idx + 1) << ". " << best->second.name << " (score: " << best->first << ")" << std::endl; return std::tuple { best->second.physical_device, best->second.graphics_queue_family_index, best->second.present_queue_family_index, best->second.features }; }(); auto device = [&]() { // TODO: really weird way of making a single structure if // graphics_queue_family_index == present_queue_family_index // here, in this cas, we create both *CreateInfo, and then tell VkDeviceCreateInfo that // there is only a single element std::array queue_priorities { .5f, .5f }; std::array device_queue_cis { VkDeviceQueueCreateInfo { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .pNext = nullptr, .flags = {}, .queueFamilyIndex = graphics_queue_family_index, .queueCount = 1, .pQueuePriorities = &queue_priorities[0], }, VkDeviceQueueCreateInfo { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .pNext = nullptr, .flags = {}, .queueFamilyIndex = present_queue_family_index, .queueCount = 1, .pQueuePriorities = &queue_priorities[1], }, }; VkPhysicalDeviceExtendedDynamicStateFeaturesEXT device_eds_features { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT, .pNext = {}, .extendedDynamicState = VK_TRUE, }; VkPhysicalDeviceVulkan13Features device_vk13_features { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, .pNext = &device_eds_features, .robustImageAccess = {}, .inlineUniformBlock = {}, .descriptorBindingInlineUniformBlockUpdateAfterBind = {}, .pipelineCreationCacheControl = {}, .privateData = {}, .shaderDemoteToHelperInvocation = {}, .shaderTerminateInvocation = {}, .subgroupSizeControl = {}, .computeFullSubgroups = {}, .synchronization2 = VK_TRUE, .textureCompressionASTC_HDR = {}, .shaderZeroInitializeWorkgroupMemory = {}, .dynamicRendering = VK_TRUE, .shaderIntegerDotProduct = {}, .maintenance4 = {}, }; device_features.pNext = &device_vk13_features; VkDeviceCreateInfo device_ci { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &device_features, .flags = {}, .queueCreateInfoCount = (graphics_queue_family_index == present_queue_family_index ? static_cast(1) : static_cast(2)), .pQueueCreateInfos = device_queue_cis.data(), .enabledLayerCount = {}, .ppEnabledLayerNames = {}, .enabledExtensionCount = static_cast(physical_device_ext_names.size()), .ppEnabledExtensionNames = physical_device_ext_names.data(), .pEnabledFeatures = {}, }; VkDevice device; if (VkResult res = vkCreateDevice(physical_device, &device_ci, nullptr, &device); res != VK_SUCCESS) { std::cerr << "failed to create device: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return device; }(); auto [graphics_queue, present_queue] = [&]() { const auto map_family_to_queue = [&](uint32_t queue_family_index) { VkQueue queue; vkGetDeviceQueue(device, queue_family_index, 0, &queue); return queue; }; return std::tuple { map_family_to_queue(graphics_queue_family_index), map_family_to_queue(present_queue_family_index), }; }(); // create swap chain // TODO: should probably use version 2 of theses functions, but glfwCreateWindowSurface return // version 1, so for now we will use version 1 auto surface_capabilities = [&]() { VkSurfaceCapabilitiesKHR surface_capabilities; if (VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surface_capabilities); res != VK_SUCCESS) { std::cerr << "failed to get physical device surface capabilities, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return surface_capabilities; }(); auto swapchain_extent = [&]() { if (surface_capabilities.currentExtent.width != std::numeric_limits::max()) return surface_capabilities.currentExtent; int width, height; glfwGetFramebufferSize(window, &width, &height); return VkExtent2D{ .width = std::clamp(static_cast(width), surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width), .height = std::clamp(static_cast(height), surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height), }; }(); auto surface_format = [&]() { auto surface_formats = [&]() { uint32_t surface_formats_count; if (VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &surface_formats_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to get physical device surface formats, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::vector surface_formats(surface_formats_count); if (VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &surface_formats_count, surface_formats.data()); res != VK_SUCCESS) { std::cerr << "failed to get physical device surface formats, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return surface_formats; }(); for (const auto& surface_format : surface_formats) { if (surface_format.format == VK_FORMAT_B8G8R8A8_SRGB && surface_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) return surface_format; } // not found std::cerr << "surface format not found (VK_FORMAT_B8G8R8_SRGB and VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)" << std::endl; std::exit(EXIT_FAILURE); }(); auto present_mode = [&]() { uint32_t present_modes_count; if (VkResult res = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_modes_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to get physical device present modes, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::vector present_modes(present_modes_count); if (VkResult res = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_modes_count, present_modes.data()); res != VK_SUCCESS) { std::cerr << "failed to get physical device present modes, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } for (const auto& present_mode : present_modes) if (present_mode == VK_PRESENT_MODE_MAILBOX_KHR) return present_mode; return VK_PRESENT_MODE_FIFO_KHR; }(); auto swapchain = [&]() { // TODO: remove unnecessary static_cast, but at this moment I'm not sure where they // are necessary uint32_t min_image_count = std::max(static_cast(3), surface_capabilities.minImageCount + static_cast(1)); if (surface_capabilities.maxImageCount > 0 && surface_capabilities.maxImageCount < min_image_count) min_image_count = surface_capabilities.maxImageCount; // might not be used, but if we do, we have to keep it in memory until the call to vkCreateSwapchainKHR() std::array queue_family_indices; VkSwapchainCreateInfoKHR swapchain_ci { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .pNext = nullptr, .flags = {}, .surface = surface, .minImageCount = min_image_count, .imageFormat = surface_format.format, .imageColorSpace = surface_format.colorSpace, .imageExtent = swapchain_extent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .imageSharingMode = {}, .queueFamilyIndexCount = {}, .pQueueFamilyIndices = {}, .preTransform = surface_capabilities.currentTransform, .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, .presentMode = present_mode, .clipped = VK_TRUE, .oldSwapchain = {}, }; if (graphics_queue_family_index == present_queue_family_index) { swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } else { swapchain_ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT; queue_family_indices[0] = graphics_queue_family_index; queue_family_indices[1] = present_queue_family_index; swapchain_ci.queueFamilyIndexCount = 2; swapchain_ci.pQueueFamilyIndices = queue_family_indices.data(); } VkSwapchainKHR swapchain; if (VkResult res = vkCreateSwapchainKHR(device, &swapchain_ci, nullptr, &swapchain); res != VK_SUCCESS) { std::cerr << "failed create swapchain, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return swapchain; }(); auto swapchain_imgs = [&]() { uint32_t swapchain_imgs_count; if (VkResult res = vkGetSwapchainImagesKHR(device, swapchain, &swapchain_imgs_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to get swapchain images, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::vector swapchain_imgs(swapchain_imgs_count); if (VkResult res = vkGetSwapchainImagesKHR(device, swapchain, &swapchain_imgs_count, swapchain_imgs.data()); res != VK_SUCCESS) { std::cerr << "failed to get swapchain images, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return swapchain_imgs; }(); auto swapchain_img_views = [&]() { std::vector swapchain_img_views(swapchain_imgs.size()); for (uint32_t i = 0; i < swapchain_imgs.size(); i++) { VkImageViewCreateInfo img_view_ci { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .pNext = {}, .flags = {}, .image = swapchain_imgs[i], .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = surface_format.format, .components = {}, .subresourceRange = { // VkImageSubresourceRange .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; if (VkResult res = vkCreateImageView(device, &img_view_ci, nullptr, &swapchain_img_views[i]); res != VK_SUCCESS) { std::cerr << "failed to create image view, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } } return swapchain_img_views; }(); auto [pl_layout, graphics_pl] = [&]() { // reading shader file auto shader_module = [&]() { auto shader_code = [&]() { const char* shader_file_name = SHADERSDIR "/shader.spv"; std::ifstream shader_file(shader_file_name, std::ios::ate | std::ios::binary); if (!shader_file.is_open()) { std::cerr << "file `" << shader_file_name << "' not found" << std::endl; // TODO: improve std::exit(EXIT_SUCCESS); } // shader code has to be 32-bits aligned, which is the case with the default allocator std::vector shader_code(shader_file.tellg()); shader_file.seekg(0, std::ios::beg); shader_file.read(shader_code.data(), static_cast(shader_code.size())); return shader_code; }(); VkShaderModuleCreateInfo shader_module_ci { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .pNext = nullptr, .flags = {}, .codeSize = shader_code.size(), .pCode = reinterpret_cast(shader_code.data()), }; VkShaderModule shader_module; if (VkResult res = vkCreateShaderModule(device, &shader_module_ci, nullptr, &shader_module); res != VK_SUCCESS) { std::cerr << "failed to create shader module, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return shader_module; }(); auto [pl_layout, graphics_pl] = [&]() { std::array pl_shader_stage_cis { VkPipelineShaderStageCreateInfo { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .pNext = nullptr, .flags = {}, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = shader_module, .pName = "vert_main", .pSpecializationInfo = nullptr, }, VkPipelineShaderStageCreateInfo { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .pNext = nullptr, .flags = {}, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = shader_module, .pName = "frag_main", .pSpecializationInfo = nullptr, }, }; std::array dynamic_states { VkDynamicState { VK_DYNAMIC_STATE_VIEWPORT }, VkDynamicState { VK_DYNAMIC_STATE_SCISSOR }, }; VkPipelineDynamicStateCreateInfo pl_dyn_state_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .pNext = nullptr, .flags = {}, .dynamicStateCount = static_cast(dynamic_states.size()), .pDynamicStates = dynamic_states.data(), }; VkPipelineVertexInputStateCreateInfo pl_vert_in_state_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .pNext = nullptr, .flags = {}, .vertexBindingDescriptionCount = 0, .pVertexBindingDescriptions = nullptr, .vertexAttributeDescriptionCount = 0, .pVertexAttributeDescriptions = nullptr, }; VkPipelineInputAssemblyStateCreateInfo pl_in_asm_state_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .pNext = nullptr, .flags = {}, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .primitiveRestartEnable = VK_FALSE, }; VkPipelineViewportStateCreateInfo pl_viewport_state_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pNext = nullptr, .flags = {}, .viewportCount = 1, .pViewports = {}, .scissorCount = 1, .pScissors = {}, }; VkPipelineRasterizationStateCreateInfo pl_raster_state_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pNext = nullptr, .flags = {}, .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 = {}, .depthBiasClamp = {}, .depthBiasSlopeFactor = {}, .lineWidth = 1.f, }; VkPipelineMultisampleStateCreateInfo pl_ms_state_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .pNext = nullptr, .flags = {}, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, .sampleShadingEnable = VK_FALSE, .minSampleShading = {}, .pSampleMask = {}, .alphaToCoverageEnable = VK_FALSE, .alphaToOneEnable = VK_FALSE, }; VkPipelineColorBlendAttachmentState pl_col_blend_attachment_state { .blendEnable = VK_FALSE, .srcColorBlendFactor = {}, .dstColorBlendFactor = {}, .colorBlendOp = {}, .srcAlphaBlendFactor = {}, .dstAlphaBlendFactor = {}, .alphaBlendOp = {}, .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, }; VkPipelineColorBlendStateCreateInfo pl_col_blend_state_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .pNext = nullptr, .flags = {}, .logicOpEnable = VK_FALSE, .logicOp = {}, .attachmentCount = 1, .pAttachments = &pl_col_blend_attachment_state, .blendConstants = { 0.f, 0.f, 0.f, 0.f }, }; auto pl_layout = [&]() { VkPipelineLayoutCreateInfo pl_layout_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pNext = nullptr, .flags = {}, .setLayoutCount = 0, .pSetLayouts = {}, .pushConstantRangeCount = 0, .pPushConstantRanges = {}, }; VkPipelineLayout pl_layout; if (VkResult res = vkCreatePipelineLayout(device, &pl_layout_ci, nullptr, &pl_layout); res != VK_SUCCESS) { std::cerr << "failed to create pipeline layout, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return pl_layout; }(); VkPipelineRenderingCreateInfo pl_render_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, .pNext = nullptr, .viewMask = {}, .colorAttachmentCount = 1, .pColorAttachmentFormats = &surface_format.format, .depthAttachmentFormat = {}, .stencilAttachmentFormat = {}, }; VkGraphicsPipelineCreateInfo graphics_pl_ci { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = &pl_render_ci, .flags = {}, .stageCount = static_cast(pl_shader_stage_cis.size()), .pStages = pl_shader_stage_cis.data(), .pVertexInputState = &pl_vert_in_state_ci, .pInputAssemblyState = &pl_in_asm_state_ci, .pTessellationState = {}, .pViewportState = &pl_viewport_state_ci, .pRasterizationState = &pl_raster_state_ci, .pMultisampleState = &pl_ms_state_ci, .pDepthStencilState = {}, .pColorBlendState = &pl_col_blend_state_ci, .pDynamicState = &pl_dyn_state_ci, .layout = pl_layout, .renderPass = nullptr, .subpass = {}, .basePipelineHandle = {}, .basePipelineIndex = {}, }; VkPipeline graphics_pl; if (VkResult res = vkCreateGraphicsPipelines(device, nullptr, 1, &graphics_pl_ci, nullptr, &graphics_pl); res != VK_SUCCESS) { std::cerr << "failed to pipeline, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return std::tuple { pl_layout, graphics_pl }; }(); vkDestroyShaderModule(device, shader_module, nullptr); return std::tuple { pl_layout, graphics_pl }; }(); auto cmd_pool = [&]() { VkCommandPoolCreateInfo cmd_pool_ci { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .pNext = nullptr, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = graphics_queue_family_index, }; VkCommandPool cmd_pool; if (VkResult res = vkCreateCommandPool(device, &cmd_pool_ci, nullptr, &cmd_pool); res != VK_SUCCESS) { std::cerr << "failed to create command pool, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return cmd_pool; }(); auto cmd_buf = [&]() { VkCommandBufferAllocateInfo cmd_buf_ai { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .pNext = nullptr, .commandPool = cmd_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; VkCommandBuffer cmd_buf; if (VkResult res = vkAllocateCommandBuffers(device, &cmd_buf_ai, &cmd_buf); res != VK_SUCCESS) { std::cerr << "failed to allocate command buffer, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return cmd_buf; }(); // create sync objects auto [sem_present_complete, sem_render_finished] = [&]() { const auto create_semaphore = [&](const char* name) { VkSemaphoreCreateInfo sem_ci { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = nullptr, .flags = {}, }; VkSemaphore sem; if (VkResult res = vkCreateSemaphore(device, &sem_ci, nullptr, &sem); res != VK_SUCCESS) { std::cerr << "failed to create " << name << " semaphore, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return sem; }; return std::tuple { create_semaphore("present complete"), create_semaphore("render finished"), }; }(); std::cout << "sem_present_complete: " << sem_present_complete << std::endl; std::cout << "sem_render_finished: " << sem_render_finished << std::endl; auto fence_draw = [&]() { VkFenceCreateInfo fence_draw_ci { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }; VkFence fence_draw; if (VkResult res = vkCreateFence(device, &fence_draw_ci, nullptr, &fence_draw); res != VK_SUCCESS) { std::cerr << "failed to create draw semaphore, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return fence_draw; }(); std::cout << "fence_draw: " << fence_draw << std::endl; // main loop while (!glfwWindowShouldClose(window)) { glfwPollEvents(); if (VkResult res = vkWaitForFences(device, 1, &fence_draw, VK_TRUE, std::numeric_limits::max()); res != VK_SUCCESS) { std::cerr << "failed to wait for draw fence, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } if (VkResult res = vkQueueWaitIdle(present_queue); res != VK_SUCCESS) { std::cerr << "failed to wait idle for graphics queue, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } auto img_idx = [&]() { uint32_t img_idx; if (VkResult res = vkAcquireNextImageKHR(device, swapchain, std::numeric_limits::max(), sem_present_complete, nullptr, &img_idx); res != VK_SUCCESS) { std::cerr << "failed to acquire next image, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } return img_idx; }(); // record command buffer { VkCommandBufferBeginInfo cmd_buf_bi { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .pNext = nullptr, .flags = {}, .pInheritanceInfo = {}, }; if (VkResult res = vkBeginCommandBuffer(cmd_buf, &cmd_buf_bi); res != VK_SUCCESS) { std::cerr << "failed to begin command buffer, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } } transition_image_layout(cmd_buf, swapchain_imgs[img_idx], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, {}, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT); { VkClearValue clear_val { .color = { .float32 = { 0.f, 0.f, 0.f, 1.f } }, }; VkRenderingAttachmentInfo attachment_info { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .pNext = nullptr, .imageView = swapchain_img_views[img_idx], .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .resolveMode = {}, .resolveImageView = {}, .resolveImageLayout = {}, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .clearValue = clear_val, }; VkRenderingInfo render_info { .sType = VK_STRUCTURE_TYPE_RENDERING_INFO, .pNext = nullptr, .flags = {}, .renderArea = { .offset { 0, 0 }, .extent = swapchain_extent }, .layerCount = 1, .viewMask = {}, .colorAttachmentCount = 1, .pColorAttachments = &attachment_info, .pDepthAttachment = {}, .pStencilAttachment = {}, }; vkCmdBeginRendering(cmd_buf, &render_info); } vkCmdBindPipeline(cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pl); { VkViewport viewport { .x = 0.f, .y = 0.f, .width = static_cast(swapchain_extent.width), .height = static_cast(swapchain_extent.height), .minDepth = 0.f, .maxDepth = 1.f, }; vkCmdSetViewport(cmd_buf, 0, 1, &viewport); } { VkRect2D scissor { .offset = { 0, 0 }, .extent = swapchain_extent, }; vkCmdSetScissor(cmd_buf, 0, 1, &scissor); } vkCmdDraw(cmd_buf, 3, 1, 0, 0); vkCmdEndRendering(cmd_buf); transition_image_layout(cmd_buf, swapchain_imgs[img_idx], VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, {}, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT); if (VkResult res = vkEndCommandBuffer(cmd_buf); res != VK_SUCCESS) { std::cerr << "failed to end command buffer, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } if (VkResult res = vkResetFences(device, 1, &fence_draw); res != VK_SUCCESS) { std::cerr << "failed to reset draw fence, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } { VkPipelineStageFlags pl_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkSubmitInfo submit_info { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .pNext = nullptr, .waitSemaphoreCount = 1, .pWaitSemaphores = &sem_present_complete, .pWaitDstStageMask = &pl_stage_flags, .commandBufferCount = 1, .pCommandBuffers = &cmd_buf, .signalSemaphoreCount = 1, .pSignalSemaphores = &sem_render_finished, }; if (VkResult res = vkQueueSubmit(graphics_queue, 1, &submit_info, fence_draw); res != VK_SUCCESS) { std::cerr << "failed to submit queue, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } } { VkPresentInfoKHR present_info { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .pNext = nullptr, .waitSemaphoreCount = 1, .pWaitSemaphores = &sem_render_finished, .swapchainCount = 1, .pSwapchains = &swapchain, .pImageIndices = &img_idx, .pResults = nullptr, }; if (VkResult res = vkQueuePresentKHR(present_queue, &present_info); res != VK_SUCCESS) { std::cerr << "failed to present, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } } } if (VkResult res = vkDeviceWaitIdle(device); res != VK_SUCCESS) { std::cerr << "failed to wait idle for device, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } // cleanup vkDestroyFence(device, fence_draw, nullptr); vkDestroySemaphore(device, sem_render_finished, nullptr); vkDestroySemaphore(device, sem_present_complete, nullptr); vkDestroyCommandPool(device, cmd_pool, nullptr); vkDestroyPipeline(device, graphics_pl, nullptr); vkDestroyPipelineLayout(device, pl_layout, nullptr); for (auto it_img_view = swapchain_img_views.rbegin(); it_img_view != swapchain_img_views.rend(); ++it_img_view) vkDestroyImageView(device, *it_img_view, nullptr); vkDestroySwapchainKHR(device, swapchain, nullptr); vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroyDevice(device, nullptr); if (enable_validation_layers) { auto destroy_debug_messenger = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (!destroy_debug_messenger) { std::cerr << "failed to destroy debug messenger!" << std::endl; std::exit(EXIT_FAILURE); } destroy_debug_messenger(instance, debug_messenger, nullptr); } vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); glfwTerminate(); return EXIT_SUCCESS; } static std::vector convert_args(int argc, char *argv[]) { std::vector args(argc); for (int i = 0; i < argc; i++) args[i] = argv[i]; return args; } static void parse_args(const std::vector& args, int& mode) { for (auto args_iter = std::next(args.begin()); args_iter != args.end(); args_iter++) { const auto& arg = *args_iter; if (arg.size() >= 1 && arg[0] == '-') { if (arg.size() >= 2 && arg[1] == '-') { auto long_opt = arg.substr(2); if (long_opt == "help") { mode = MODE_HELP; } else if (long_opt == "term") { mode = MODE_TERM; } else if (long_opt == "graphical") { mode = MODE_GRAPHICAL; } else { std::cerr << "Error: Unexpected option `--" << long_opt << "'." << std::endl; usage_error_exit(); } } else { std::size_t arg_len = arg.size(); if (arg_len == 1) { std::cerr << "Error: Unexpected argument `-'." << std::endl; usage_error_exit(); } for (auto arg_iter = std::next(arg.begin()); arg_iter != arg.end(); arg_iter++) { const auto& opt = *arg_iter; switch (opt) { case 'h': mode = MODE_HELP; break; case 't': mode = MODE_TERM; break; case 'g': mode = MODE_GRAPHICAL; break; default: std::cerr << "Error: Unexpected option `-" << opt << "'." << std::endl; usage_error_exit(); } } } } else { std::cerr << "Error: Unexpected argument `" << arg << "'." << std::endl; usage_error_exit(); } } } int main(int argc, char *argv[]) { int mode = MODE_GRAPHICAL; parse_args(convert_args(argc, argv), mode); switch (mode) { case MODE_HELP: print_usage(std::cout); return EXIT_SUCCESS; case MODE_TERM: #ifdef HAVE_NCURSES return main_term(); #else std::cerr << "Error: ncurses was not enabled during compilation." << std::endl; return EXIT_FAILURE; #endif case MODE_GRAPHICAL: return main_graphical(); default: std::unreachable(); } }