#include "config.h" #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; vkEnumerateInstanceLayerProperties(&layer_count, nullptr); std::vector available_layers(layer_count); vkEnumerateInstanceLayerProperties(&layer_count, available_layers.data()); 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 uint32_t find_queue_family_index(const std::vector& queue_family_properties) { auto it = std::ranges::find_if(queue_family_properties, [](const auto& prop) { return (prop.queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) != static_cast(0); }); return static_cast(std::distance(queue_family_properties.begin(), it)); } 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; vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr); std::vector available_extensions(extension_count); vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, available_extensions.data()); 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); } // select physical device uint32_t physical_devices_count; vkEnumeratePhysicalDevices(instance, &physical_devices_count, nullptr); std::vector physical_devices(physical_devices_count); vkEnumeratePhysicalDevices(instance, &physical_devices_count, physical_devices.data()); if (physical_devices.empty()) { std::cerr << "failed to find physical devices with Vulkan support" << std::endl; std::exit(EXIT_FAILURE); } VkPhysicalDevice device; bool found = false; 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; vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &ext_props_count, nullptr); std::vector ext_props(ext_props_count); vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &ext_props_count, ext_props.data()); 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; } bool is_suitable = [&properties, &queue_family_properties, &ext_props]() { if (VK_API_VERSION_VARIANT(properties.properties.apiVersion) != 0 || properties.properties.apiVersion < VK_API_VERSION_1_4) return false; if (find_queue_family_index(queue_family_properties) == queue_family_properties.size()) 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 (!found && is_suitable) { found = true; std::cout << " (picking this one)"; device = physical_device; } std::cout << std::endl; } if (!found) { std::cerr << "no suitable physical device found" << std::endl; std::exit(EXIT_FAILURE); } // TODO: shouldn't fetch twice (already fetched while selecting physical device) uint32_t queue_family_index = [device]() { uint32_t queue_family_properties_count; vkGetPhysicalDeviceQueueFamilyProperties2(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(device, &queue_family_properties_count, queue_family_properties.data()); // don't need to check if it's found, we already checked that in device seleciton return find_queue_family_index(queue_family_properties); }(); float queue_priority = .5f; VkDeviceQueueCreateInfo deviceQueueCreateInfo{}; deviceQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; deviceQueueCreateInfo.queueFamilyIndex = 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(); // set everything to false VkPhysicalDeviceFeatures2 device_features{}; device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; 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.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, queue_family_index, 0, &graphics_queue); // main loop while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } // cleanup 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(); } }