#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" 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 device_exts = { 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 static bool check_validation_layer_support() { uint32_t layer_count; if (VkResult res = vkEnumerateInstanceLayerProperties(&layer_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 available_layers(layer_count); if (VkResult res = vkEnumerateInstanceLayerProperties(&layer_count, available_layers.data()); res != VK_SUCCESS) { std::cerr << "failed to enumerate instance layer properties, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } for (const char* layer_name : validation_layers) { bool layer_found = false; for (const auto& layer_properties : available_layers) { if (strcmp(layer_name, layer_properties.layerName) == 0) { layer_found = true; break; } } if (!layer_found) { return false; } } return true; } static std::string severity_to_str(VkDebugUtilsMessageSeverityFlagBitsEXT severity) { std::string s; bool first = true; if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { if (!first) s += ", "; else first = false; s += "VERBOSE"; } if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { if (!first) s += ", "; else first = false; s += "INFO"; } if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { if (!first) s += ", "; else first = false; s += "WARNING"; } if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { if (!first) s += ", "; else first = false; s += "ERROR"; } return s; } // TODO: remove [[maybe_unused]] static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback( VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT message_type, const VkDebugUtilsMessengerCallbackDataEXT* callback_data, [[maybe_unused]] void* user_data) { std::cerr << "validation layer: [" << severity_to_str(message_severity) << "] " << callback_data->pMessage << std::endl; return VK_FALSE; } static void populate_msger_create_info(VkDebugUtilsMessengerCreateInfoEXT& msger_create_info) { msger_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; msger_create_info.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; msger_create_info.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_create_info.pfnUserCallback = debug_callback; msger_create_info.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, presentation_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; presentation_queue_family_index = i; break; } else if (is_graphics_queue && !graphics_queue_family_index) { graphics_queue_family_index = i; } else if (is_presentation_queue && !presentation_queue_family_index) { presentation_queue_family_index = i; } } return { graphics_queue_family_index, presentation_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: " << VK_API_VERSION_MAJOR(VK_HEADER_VERSION_COMPLETE) << "." << VK_API_VERSION_MINOR(VK_HEADER_VERSION_COMPLETE) << "." << VK_API_VERSION_PATCH(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); } VkApplicationInfo app_info{}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pApplicationName = "engine - test"; app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0); app_info.pEngineName = "engine"; app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0); app_info.apiVersion = VK_API_VERSION_1_4; std::vector extensions; { uint32_t glfw_extension_count; const char** glfw_extensions; glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count); extensions.insert(extensions.end(), glfw_extensions, glfw_extensions + glfw_extension_count); } if (enable_validation_layers) { extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } uint32_t extension_count; if (VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_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 available_extensions(extension_count); if (VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, available_extensions.data()); res != VK_SUCCESS) { std::cerr << "failed to enumerate instance extension properties, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::cout << "vulkan extensions:\n"; for (const auto& extension : available_extensions) { std::cout << (std::ranges::find_if(extensions, [avail_ext_name=extension.extensionName](const auto& ext_name) { return strcmp(ext_name, avail_ext_name) == 0; }) != extensions.end() ? "*" : " "); std::cout << " " << extension.extensionName << "\n"; } std::cout << "\n"; std::cout << "required extensions:\n"; for (const auto& extension_name : extensions) std::cout << " " << extension_name << "\n"; VkInstanceCreateInfo create_info{}; create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; create_info.pApplicationInfo = &app_info; create_info.enabledExtensionCount = static_cast(extensions.size()); create_info.ppEnabledExtensionNames = extensions.data(); VkDebugUtilsMessengerCreateInfoEXT inst_msger_create_info{}; if (enable_validation_layers) { create_info.enabledLayerCount = static_cast(validation_layers.size()); create_info.ppEnabledLayerNames = validation_layers.data(); populate_msger_create_info(inst_msger_create_info); create_info.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &inst_msger_create_info; } else { create_info.enabledLayerCount = 0; create_info.pNext = nullptr; } VkInstance instance; if (VkResult res = vkCreateInstance(&create_info, nullptr, &instance); res != VK_SUCCESS) { std::cerr << "failed to create instance, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } VkDebugUtilsMessengerEXT debug_messenger; if (enable_validation_layers) { VkDebugUtilsMessengerCreateInfoEXT msger_create_info{}; populate_msger_create_info(msger_create_info); 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_create_info, nullptr, &debug_messenger); } // create window 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); } // select physical device and queues auto [device, graphics_queue_family_index, presentation_queue_family_index, device_features] = [instance, surface]() { std::optional device; uint32_t res_graphics_queue_family_index, res_presentation_queue_family_index; VkPhysicalDeviceFeatures2 res_features; uint32_t physical_devices_count; if (VkResult res = vkEnumeratePhysicalDevices(instance, &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 physical_devices(physical_devices_count); if (VkResult res = vkEnumeratePhysicalDevices(instance, &physical_devices_count, physical_devices.data()); res != VK_SUCCESS) { std::cerr << "failed to enumerate physical devices, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } if (physical_devices.empty()) { std::cerr << "failed to find physical devices with Vulkan support" << std::endl; std::exit(EXIT_FAILURE); } for (const auto& physical_device : physical_devices) { VkPhysicalDeviceProperties2 properties{}; properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; vkGetPhysicalDeviceProperties2(physical_device, &properties); VkPhysicalDeviceFeatures2 features{}; features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; vkGetPhysicalDeviceFeatures2(physical_device, &features); // TODO: found a better name, too confusing with device_exts uint32_t ext_props_count; if (VkResult res = vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &ext_props_count, nullptr); res != VK_SUCCESS) { std::cerr << "failed to enumerate device extension properties, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::vector ext_props(ext_props_count); if (VkResult res = vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &ext_props_count, ext_props.data()); res != VK_SUCCESS) { std::cerr << "failed to enumerate device extension properties, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } uint32_t queue_family_properties_count; vkGetPhysicalDeviceQueueFamilyProperties2(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(physical_device, &queue_family_properties_count, queue_family_properties.data()); std::cout << "structure type: " << string_VkStructureType(properties.sType) << std::endl; std::cout << "pNext: " << properties.pNext << std::endl; std::cout << "apiVersion: (variant " << VK_API_VERSION_VARIANT(properties.properties.apiVersion) << ") " << VK_API_VERSION_MAJOR(properties.properties.apiVersion) << "." << VK_API_VERSION_MINOR(properties.properties.apiVersion) << "." << VK_API_VERSION_PATCH(properties.properties.apiVersion) << std::endl; std::cout << "driverVersion: " << properties.properties.driverVersion << std::endl; std::cout << "vendorID: " << properties.properties.vendorID << std::endl; std::cout << "deviceID: " << properties.properties.deviceID << std::endl; std::cout << "deviceType: " << string_VkPhysicalDeviceType(properties.properties.deviceType) << std::endl; std::cout << "deviceName: " << properties.properties.deviceName << std::endl; // pipelineCacheUUID[VK_UUID_SIZE] // limits std::cout << "sparseProperties:" << std::endl; std::cout << std::boolalpha; std::cout << " residencyStandard2DBlockShape: " << static_cast(properties.properties.sparseProperties.residencyStandard2DBlockShape) << std::endl; std::cout << " residencyStandard2DMultisampleBlockShape: " << static_cast(properties.properties.sparseProperties.residencyStandard2DMultisampleBlockShape) << std::endl; std::cout << " residencyStandard3DBlockShape: " << static_cast(properties.properties.sparseProperties.residencyStandard3DBlockShape) << std::endl; std::cout << " residencyAlignedMipSize: " << static_cast(properties.properties.sparseProperties.residencyAlignedMipSize) << std::endl; std::cout << " residencyNonResidentStrict: " << static_cast(properties.properties.sparseProperties.residencyNonResidentStrict) << std::endl; std::cout << std::noboolalpha; std::cout << "features:" << std::endl; std::cout << std::boolalpha; std::cout << " robustBufferAccess: " << static_cast(features.features.robustBufferAccess) << std::endl; std::cout << " fullDrawIndexUint32: " << static_cast(features.features.fullDrawIndexUint32) << std::endl; std::cout << " imageCubeArray: " << static_cast(features.features.imageCubeArray) << std::endl; std::cout << " independentBlend: " << static_cast(features.features.independentBlend) << std::endl; std::cout << " geometryShader: " << static_cast(features.features.geometryShader) << std::endl; std::cout << " tessellationShader: " << static_cast(features.features.tessellationShader) << std::endl; std::cout << " sampleRateShading: " << static_cast(features.features.sampleRateShading) << std::endl; std::cout << " dualSrcBlend: " << static_cast(features.features.dualSrcBlend) << std::endl; std::cout << " logicOp: " << static_cast(features.features.logicOp) << std::endl; std::cout << " multiDrawIndirect: " << static_cast(features.features.multiDrawIndirect) << std::endl; std::cout << " drawIndirectFirstInstance: " << static_cast(features.features.drawIndirectFirstInstance) << std::endl; std::cout << " depthClamp: " << static_cast(features.features.depthClamp) << std::endl; std::cout << " depthBiasClamp: " << static_cast(features.features.depthBiasClamp) << std::endl; std::cout << " fillModeNonSolid: " << static_cast(features.features.fillModeNonSolid) << std::endl; std::cout << " depthBounds: " << static_cast(features.features.depthBounds) << std::endl; std::cout << " wideLines: " << static_cast(features.features.wideLines) << std::endl; std::cout << " largePoints: " << static_cast(features.features.largePoints) << std::endl; std::cout << " alphaToOne: " << static_cast(features.features.alphaToOne) << std::endl; std::cout << " multiViewport: " << static_cast(features.features.multiViewport) << std::endl; std::cout << " samplerAnisotropy: " << static_cast(features.features.samplerAnisotropy) << std::endl; std::cout << " textureCompressionETC2: " << static_cast(features.features.textureCompressionETC2) << std::endl; std::cout << " textureCompressionASTC_LDR: " << static_cast(features.features.textureCompressionASTC_LDR) << std::endl; std::cout << " textureCompressionBC: " << static_cast(features.features.textureCompressionBC) << std::endl; std::cout << " occlusionQueryPrecise: " << static_cast(features.features.occlusionQueryPrecise) << std::endl; std::cout << " pipelineStatisticsQuery: " << static_cast(features.features.pipelineStatisticsQuery) << std::endl; std::cout << " vertexPipelineStoresAndAtomics: " << static_cast(features.features.vertexPipelineStoresAndAtomics) << std::endl; std::cout << " fragmentStoresAndAtomics: " << static_cast(features.features.fragmentStoresAndAtomics) << std::endl; std::cout << " shaderTessellationAndGeometryPointSize: " << static_cast(features.features.shaderTessellationAndGeometryPointSize) << std::endl; std::cout << " shaderImageGatherExtended: " << static_cast(features.features.shaderImageGatherExtended) << std::endl; std::cout << " shaderStorageImageExtendedFormats: " << static_cast(features.features.shaderStorageImageExtendedFormats) << std::endl; std::cout << " shaderStorageImageMultisample: " << static_cast(features.features.shaderStorageImageMultisample) << std::endl; std::cout << " shaderStorageImageReadWithoutFormat: " << static_cast(features.features.shaderStorageImageReadWithoutFormat) << std::endl; std::cout << " shaderStorageImageWriteWithoutFormat: " << static_cast(features.features.shaderStorageImageWriteWithoutFormat) << std::endl; std::cout << " shaderUniformBufferArrayDynamicIndexing: " << static_cast(features.features.shaderUniformBufferArrayDynamicIndexing) << std::endl; std::cout << " shaderSampledImageArrayDynamicIndexing: " << static_cast(features.features.shaderSampledImageArrayDynamicIndexing) << std::endl; std::cout << " shaderStorageBufferArrayDynamicIndexing: " << static_cast(features.features.shaderStorageBufferArrayDynamicIndexing) << std::endl; std::cout << " shaderStorageImageArrayDynamicIndexing: " << static_cast(features.features.shaderStorageImageArrayDynamicIndexing) << std::endl; std::cout << " shaderClipDistance: " << static_cast(features.features.shaderClipDistance) << std::endl; std::cout << " shaderCullDistance: " << static_cast(features.features.shaderCullDistance) << std::endl; std::cout << " shaderFloat64: " << static_cast(features.features.shaderFloat64) << std::endl; std::cout << " shaderInt64: " << static_cast(features.features.shaderInt64) << std::endl; std::cout << " shaderInt16: " << static_cast(features.features.shaderInt16) << std::endl; std::cout << " shaderResourceResidency: " << static_cast(features.features.shaderResourceResidency) << std::endl; std::cout << " shaderResourceMinLod: " << static_cast(features.features.shaderResourceMinLod) << std::endl; std::cout << " sparseBinding: " << static_cast(features.features.sparseBinding) << std::endl; std::cout << " sparseResidencyBuffer: " << static_cast(features.features.sparseResidencyBuffer) << std::endl; std::cout << " sparseResidencyImage2D: " << static_cast(features.features.sparseResidencyImage2D) << std::endl; std::cout << " sparseResidencyImage3D: " << static_cast(features.features.sparseResidencyImage3D) << std::endl; std::cout << " sparseResidency2Samples: " << static_cast(features.features.sparseResidency2Samples) << std::endl; std::cout << " sparseResidency4Samples: " << static_cast(features.features.sparseResidency4Samples) << std::endl; std::cout << " sparseResidency8Samples: " << static_cast(features.features.sparseResidency8Samples) << std::endl; std::cout << " sparseResidency16Samples: " << static_cast(features.features.sparseResidency16Samples) << std::endl; std::cout << " sparseResidencyAliased: " << static_cast(features.features.sparseResidencyAliased) << std::endl; std::cout << " variableMultisampleRate: " << static_cast(features.features.variableMultisampleRate) << std::endl; std::cout << " inheritedQueries: " << static_cast(features.features.inheritedQueries) << std::endl; std::cout << std::noboolalpha; std::cout << "extensions:" << std::endl; for (const auto& ext : ext_props) { std::cout << (std::ranges::find_if(device_exts, [ext_name=ext.extensionName](const auto& device_ext) { return strcmp(ext_name, device_ext) == 0; }) != device_exts.end() ? "*" : " "); std::cout << " " << ext.extensionName << std::endl; } auto [graphics_queue_family_index, presentation_queue_family_index] = find_queue_family_indices(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 (presentation_queue_family_index) std::cout << *presentation_queue_family_index; else std::cout << "none"; std::cout << std::endl; bool is_suitable = [&properties, &queue_family_properties, &ext_props, &graphics_queue_family_index, &presentation_queue_family_index]() { if (VK_API_VERSION_VARIANT(properties.properties.apiVersion) != 0 || properties.properties.apiVersion < VK_API_VERSION_1_4) return false; if (!graphics_queue_family_index || !presentation_queue_family_index) return false; if (std::ranges::find_if_not(device_exts, [&ext_props](const auto& device_ext) { return std::ranges::find_if(ext_props, [device_ext](const auto& ext_prop) { return strcmp(ext_prop.extensionName, device_ext) == 0; }) != ext_props.end(); }) != device_exts.end()) return false; return true; }(); std::cout << "is_suitable: " << std::boolalpha << is_suitable << std::noboolalpha; if (!device && is_suitable) { std::cout << " (picking this one)"; device = physical_device; res_graphics_queue_family_index = *graphics_queue_family_index; res_presentation_queue_family_index = *presentation_queue_family_index; res_features = features; } std::cout << std::endl; } if (!device) { std::cerr << "no suitable physical device found" << std::endl; std::exit(EXIT_FAILURE); } return std::tuple { *device, res_graphics_queue_family_index, res_presentation_queue_family_index, res_features }; }(); // TODO: We only create the graphics queue, I don't know if we have to also create the // presentation queue. They don't create it in the official Vulkan tutorial, but it might cause // a bug on some rare platforms where there is no queue that's both graphics and presentation. // IDK. float queue_priority = .5f; VkDeviceQueueCreateInfo deviceQueueCreateInfo{}; deviceQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; deviceQueueCreateInfo.queueFamilyIndex = graphics_queue_family_index; deviceQueueCreateInfo.queueCount = 1; deviceQueueCreateInfo.pQueuePriorities = &queue_priority; VkDeviceCreateInfo device_create_info{}; device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; device_create_info.queueCreateInfoCount = 1; device_create_info.pQueueCreateInfos = &deviceQueueCreateInfo; device_create_info.enabledExtensionCount = static_cast(device_exts.size()); device_create_info.ppEnabledExtensionNames = device_exts.data(); // we get device features while selecting the physical device device_create_info.pNext = &device_features; VkPhysicalDeviceVulkan13Features device_vk13_features{}; device_vk13_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; device_vk13_features.synchronization2 = VK_TRUE; device_vk13_features.dynamicRendering = VK_TRUE; device_features.pNext = &device_vk13_features; VkPhysicalDeviceExtendedDynamicStateFeaturesEXT device_eds_features{}; device_eds_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT; device_eds_features.extendedDynamicState = VK_TRUE; device_vk13_features.pNext = &device_eds_features; // TODO: rename to just "device", and rename "device" to "physical_device" VkDevice logical_device; if (VkResult res = vkCreateDevice(device, &device_create_info, nullptr, &logical_device); res != VK_SUCCESS) { std::cerr << "failed to create device: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } VkQueue graphics_queue; vkGetDeviceQueue(logical_device, graphics_queue_family_index, 0, &graphics_queue); VkQueue presentation_queue; vkGetDeviceQueue(logical_device, presentation_queue_family_index, 0, &presentation_queue); // 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 VkSurfaceCapabilitiesKHR surface_capabilities; if (VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(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); } VkExtent2D swapchain_extent = [surface_capabilities, window]() { 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), }; }(); VkSurfaceFormatKHR surface_format = [device, surface]() { uint32_t surface_formats_count; if (VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(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(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); } 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); }(); VkPresentModeKHR present_mode = [device, surface]() { uint32_t present_modes_count; if (VkResult res = vkGetPhysicalDeviceSurfacePresentModesKHR(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(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; }(); // 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_create_info{}; swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapchain_create_info.flags = VkSwapchainCreateFlagsKHR{}; swapchain_create_info.surface = surface; swapchain_create_info.minImageCount = min_image_count; swapchain_create_info.imageFormat = surface_format.format; swapchain_create_info.imageColorSpace = surface_format.colorSpace; swapchain_create_info.imageExtent = swapchain_extent; swapchain_create_info.imageArrayLayers = 1; swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; swapchain_create_info.preTransform = surface_capabilities.currentTransform; swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; swapchain_create_info.presentMode = present_mode; swapchain_create_info.clipped = VK_TRUE; if (graphics_queue_family_index == presentation_queue_family_index) { swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } else { swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; queue_family_indices[0] = graphics_queue_family_index; queue_family_indices[1] = presentation_queue_family_index; swapchain_create_info.queueFamilyIndexCount = 2; swapchain_create_info.pQueueFamilyIndices = queue_family_indices.data(); } VkSwapchainKHR swapchain; if (VkResult res = vkCreateSwapchainKHR(logical_device, &swapchain_create_info, nullptr, &swapchain); res != VK_SUCCESS) { std::cerr << "failed create swapchain, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } uint32_t swapchain_imgs_count; if (VkResult res = vkGetSwapchainImagesKHR(logical_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(logical_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); } std::vector swapchain_img_views(swapchain_imgs.size()); for (uint32_t i = 0; i < swapchain_imgs.size(); i++) { VkImageViewCreateInfo img_view_create_info { .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(logical_device, &img_view_create_info, 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); } } // reading shader file VkShaderModule shader_module; { // shader code has to be 32-bits aligned, which is the case with the default allocator std::vector 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.resize(shader_file.tellg()); shader_file.seekg(0, std::ios::beg); shader_file.read(shader_code.data(), static_cast(shader_code.size())); } VkShaderModuleCreateInfo shader_module_create_info { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .pNext = nullptr, .flags = {}, .codeSize = shader_code.size(), .pCode = reinterpret_cast(shader_code.data()), }; if (VkResult res = vkCreateShaderModule(logical_device, &shader_module_create_info, nullptr, &shader_module); res != VK_SUCCESS) { std::cerr << "failed to create shader module, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } } std::array pl_shader_stage_create_infos { 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 { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; VkPipelineDynamicStateCreateInfo pl_dyn_state_create_info { .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_create_info { .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_create_info { .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_create_info { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pNext = nullptr, .flags = {}, .viewportCount = 1, .pViewports = {}, .scissorCount = 1, .pScissors = {}, }; VkPipelineRasterizationStateCreateInfo pl_raster_state_create_info { .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_create_info { .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_create_info { .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 }, }; VkPipelineLayoutCreateInfo pl_layout_create_info { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pNext = nullptr, .flags = {}, .setLayoutCount = 0, .pSetLayouts = {}, .pushConstantRangeCount = 0, .pPushConstantRanges = {}, }; VkPipelineLayout pl_layout; if (VkResult res = vkCreatePipelineLayout(logical_device, &pl_layout_create_info, nullptr, &pl_layout); res != VK_SUCCESS) { std::cerr << "failed to create pipeline layout, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } VkPipelineRenderingCreateInfo pl_render_create_info { .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, .pNext = nullptr, .viewMask = {}, .colorAttachmentCount = 1, .pColorAttachmentFormats = &surface_format.format, .depthAttachmentFormat = {}, .stencilAttachmentFormat = {}, }; VkGraphicsPipelineCreateInfo graphics_pl_create_info { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = &pl_render_create_info, .flags = {}, .stageCount = 2, .pStages = pl_shader_stage_create_infos.data(), .pVertexInputState = &pl_vert_in_state_create_info, .pInputAssemblyState = &pl_in_asm_state_create_info, .pTessellationState = {}, .pViewportState = &pl_viewport_state_create_info, .pRasterizationState = &pl_raster_state_create_info, .pMultisampleState = &pl_ms_state_create_info, .pDepthStencilState = {}, .pColorBlendState = &pl_col_blend_state_create_info, .pDynamicState = &pl_dyn_state_create_info, .layout = pl_layout, .renderPass = nullptr, .subpass = {}, .basePipelineHandle = {}, .basePipelineIndex = {}, }; VkPipeline graphics_pl; if (VkResult res = vkCreateGraphicsPipelines(logical_device, nullptr, 1, &graphics_pl_create_info, nullptr, &graphics_pl); res != VK_SUCCESS) { std::cerr << "failed to pipeline, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } // destroy when pipeline creation is finished vkDestroyShaderModule(logical_device, shader_module, nullptr); 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(logical_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); } 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(logical_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); } // create sync objects VkSemaphore sem_present_complete, sem_render_finished; VkFence fence_draw; // TODO: I don't know if I have to use 2 different VkSemaphoreCreateInfo, but for now, let's do // this so we're sure it'll work VkSemaphoreCreateInfo sem_present_complete_ci { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = nullptr, .flags = {}, }; if (VkResult res = vkCreateSemaphore(logical_device, &sem_present_complete_ci, nullptr, &sem_present_complete); res != VK_SUCCESS) { std::cerr << "failed to create present complete semaphore, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::cout << "sem_present_complete: " << sem_present_complete << std::endl; VkSemaphoreCreateInfo sem_render_finished_ci { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = nullptr, .flags = {}, }; if (VkResult res = vkCreateSemaphore(logical_device, &sem_render_finished_ci, nullptr, &sem_render_finished); res != VK_SUCCESS) { std::cerr << "failed to create render finished semaphore, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } std::cout << "sem_render_finished: " << sem_render_finished << std::endl; VkFenceCreateInfo fence_draw_ci { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }; if (VkResult res = vkCreateFence(logical_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); } std::cout << "fence_draw: " << fence_draw << std::endl; // main loop while (!glfwWindowShouldClose(window)) { glfwPollEvents(); if (VkResult res = vkQueueWaitIdle(graphics_queue); res != VK_SUCCESS) { std::cerr << "failed to wait idle for graphics queue, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } uint32_t img_idx; if (VkResult res = vkAcquireNextImageKHR(logical_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); } // 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(logical_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); } if (VkResult res = vkWaitForFences(logical_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); } 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(presentation_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(logical_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(logical_device, fence_draw, nullptr); vkDestroySemaphore(logical_device, sem_render_finished, nullptr); vkDestroySemaphore(logical_device, sem_present_complete, nullptr); vkDestroyCommandPool(logical_device, cmd_pool, nullptr); vkDestroyPipeline(logical_device, graphics_pl, nullptr); vkDestroyPipelineLayout(logical_device, pl_layout, nullptr); for (const auto img_view : swapchain_img_views) vkDestroyImageView(logical_device, img_view, nullptr); vkDestroySwapchainKHR(logical_device, swapchain, nullptr); vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroyDevice(logical_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(); } }