#include "iwa/instance.hpp" #include #include #include "iwa/addon.hpp" #include "iwa/log.hpp" #include "iwa/window.hpp" #include VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE namespace iwa { IWA_REGISTER_CLASS(Instance) namespace { void buildDefaultInstanceExtensionList(std::vector& outExtensions) noexcept { #if !defined(KAZAN_RELEASE) outExtensions.push_back({.name = VK_EXT_DEBUG_UTILS_EXTENSION_NAME, .required = false}); #endif // if (!getEngineOptions().headless) { outExtensions.push_back({.name = VK_KHR_SURFACE_EXTENSION_NAME, .required = true}); #if MIJIN_TARGET_OS == MIJIN_OS_LINUX // There are two possible APIs for X11 surfaces (SDL supports both) // just try to enable both and hope SDL will be happy. // Both are optional, in case one is not supported, but in the end // one of them will be needed. outExtensions.push_back({.name = "VK_KHR_xcb_surface", .required = false}); outExtensions.push_back({.name = "VK_KHR_xlib_surface", .required = false}); #elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS outExtensions.push_back({.name = "VK_KHR_win32_surface", .required = true}); #endif } } void buildDefaultInstanceLayerList(std::vector& outLayers) noexcept { #if !defined(KAZAN_RELEASE) outLayers.push_back({.name = "VK_LAYER_KHRONOS_validation", .required = false}); #else (void) outLayers; #endif } std::vector checkInstanceExtensions(std::vector& extensions) { std::vector properties = vk::enumerateInstanceExtensionProperties(nullptr); auto isExtensionSupported = [&properties](const char* extension) { for (const vk::ExtensionProperties& props : properties) { if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0) { return true; } } return false; }; std::vector enabledExtensions; for (ExtensionInfo& extInfo : extensions) { const bool supported = isExtensionSupported(extInfo.name); if (!supported && extInfo.required) { logAndDie("Required Vulkan instance extension not supported: {}.", extInfo.name); } if (supported) { enabledExtensions.push_back(extInfo.name); } extInfo.enabled = supported; } return enabledExtensions; } std::vector checkInstanceLayers(std::vector& layers) { std::vector enabledLayers; #if !defined(KAZAN_RELEASE) std::vector properties = vk::enumerateInstanceLayerProperties(); auto isLayerSupported = [&properties](const char* extension) { for (const vk::LayerProperties& props : properties) { if (std::strncmp(props.layerName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0) { return true; } } return false; }; for (LayerInfo& layerInfo : layers) { const bool supported = isLayerSupported(layerInfo.name); if (!supported && layerInfo.required) { logAndDie("Required Vulkan instance layer not supported: {}.", layerInfo.name); } if (supported) { enabledLayers.push_back(layerInfo.name); } layerInfo.enabled = supported; } #endif return enabledLayers; } vk::Bool32 VKAPI_PTR vulkanMessengerCallback( VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT /* types */, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* /* pUserData */ ) { switch (pCallbackData->messageIdNumber) { case 1303270965: // We use general layout to copy texture to mips. I know it's not optimal, but it's allowed and // works for me ¯\_(ツ)_/¯ case 148949623: // The spec says that a) you can't create descriptor sets from layouts with the push-descriptor bit // set and b) you have to bind a descriptor set when using said set layout in vkCmdPushDescriptorSetKHR. // So you have to use a set, but can't create it... Something is off. // Apart from that c) it also says that dstSet is ignored when using vkCmdPushDescriptorSetKHR, so // maybe the validation layers are just wrong. // a) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDescriptorSetAllocateInfo.html#VUID-VkDescriptorSetAllocateInfo-pSetLayouts-00308 // b) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkWriteDescriptorSet.html#VUID-VkWriteDescriptorSet-dstSet-00320 // c) https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkCmdPushDescriptorSetKHR.html return VK_FALSE; } logMsg("VK> {}", pCallbackData->pMessage); if (/* g_breakOnVulkanError && */ severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { if (const mijin::Result trace = mijin::captureStacktrace(0); trace.isSuccess()) { logMsg("{}", *trace); } MIJIN_TRAP(); } return VK_FALSE; } std::pair detectQueueFamilies(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR dummySurface) { std::vector queueFamilyProperties = physicalDevice.getQueueFamilyProperties(); for (std::uint32_t idx = 0; idx < queueFamilyProperties.size(); ++idx) { const vk::QueueFamilyProperties& properties = queueFamilyProperties[idx]; const vk::Bool32 surfaceSupport = dummySurface ? physicalDevice.getSurfaceSupportKHR(idx, dummySurface) : VK_TRUE; // no dummy surface -> no surface support needed if (surfaceSupport && (properties.queueFlags & vk::QueueFlagBits::eGraphics) && (properties.queueFlags & vk::QueueFlagBits::eCompute)) { return std::make_pair(idx, idx); } } return std::make_pair(std::numeric_limits::max(), std::numeric_limits::max()); } PhysicalDeviceInfo getPhysicalDeviceInfo(vk::PhysicalDevice physicalDevice, vk::SurfaceKHR dummySurface) { PhysicalDeviceInfo deviceInfo; deviceInfo.device = physicalDevice; deviceInfo.memoryProperties = physicalDevice.getMemoryProperties(); deviceInfo.extensions = physicalDevice.enumerateDeviceExtensionProperties(); auto isExtensionSupported = [&](const char* extension) { for (const vk::ExtensionProperties& props : deviceInfo.extensions) { if (std::strncmp(props.extensionName, extension, VK_MAX_EXTENSION_NAME_SIZE) == 0) { return true; } } return false; }; const bool rayTracingSupported = isExtensionSupported(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME) && isExtensionSupported(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME) && isExtensionSupported(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); const bool meshShadersSupported = isExtensionSupported(VK_EXT_MESH_SHADER_EXTENSION_NAME); vk::PhysicalDeviceProperties2 deviceProperties = { .pNext = &deviceInfo.rayTracingProperties }; physicalDevice.getProperties2(&deviceProperties); deviceInfo.properties = deviceProperties.properties; #define ADD_TO_NEXT_CHAIN(struct_name) \ *ppNext = &(struct_name); ppNext = &(struct_name).pNext vk::PhysicalDeviceFeatures2 deviceFeatures; void** ppNext = &deviceFeatures.pNext; ADD_TO_NEXT_CHAIN(deviceInfo.vulkan11Features); ADD_TO_NEXT_CHAIN(deviceInfo.vulkan12Features); ADD_TO_NEXT_CHAIN(deviceInfo.vulkan13Features); if (rayTracingSupported) { ADD_TO_NEXT_CHAIN(deviceInfo.accelerationStructureFeatures); ADD_TO_NEXT_CHAIN(deviceInfo.rayTracingPipelineFeatures); } if (meshShadersSupported) { ADD_TO_NEXT_CHAIN(deviceInfo.meshShaderFeatures); } #undef ADD_TO_NEXT_CHAIN physicalDevice.getFeatures2(&deviceFeatures); deviceInfo.features = deviceFeatures.features; // fill the renderer features deviceInfo.availableFeatures.rayTracing = rayTracingSupported; deviceInfo.availableFeatures.meshShaders = meshShadersSupported && deviceInfo.meshShaderFeatures.meshShader; deviceInfo.surfaceCapabilities = physicalDevice.getSurfaceCapabilitiesKHR(dummySurface); std::tie(deviceInfo.graphicsQueueFamily, deviceInfo.computeQueueFamily) = detectQueueFamilies(physicalDevice, dummySurface); return deviceInfo; } std::vector getPhysicalDeviceInfos(Instance& instance) { const std::vector devices = instance.getVkHandle().enumeratePhysicalDevices(); std::vector deviceInfos; deviceInfos.reserve(devices.size()); const ObjectPtr dummyWindow = instance.createWindow({.flags={.hidden=true}}); const vk::SurfaceKHR dummySurface = dummyWindow->getVkSurface(); for (const vk::PhysicalDevice device : devices) { deviceInfos.push_back(getPhysicalDeviceInfo(device, dummySurface)); } return deviceInfos; } } // namespace Instance::Instance(InstanceCreationArgs args) : super_t(nullptr), mExtensions(std::move(args.extensions)), mLayers(std::move(args.layers)) { setMainThread(); const AddonInitArgs addonInitArgs = { .instance = getPointer(), .instanceCreationArgs = args }; for (Addon* addon : getAddons()) { addon->init(addonInitArgs); } VULKAN_HPP_DEFAULT_DISPATCHER.init(); if (!args.flags.noDefaultExtensions) { buildDefaultInstanceExtensionList(mExtensions); } if (!args.flags.noDefaultLayers) { buildDefaultInstanceLayerList(mLayers); } const std::vector enabledLayerNames = checkInstanceLayers(mLayers); const std::vector enabledExtensionNames = checkInstanceExtensions(mExtensions); const vk::InstanceCreateInfo createInfo = { .pApplicationInfo = &args.applicationInfo, .enabledLayerCount = static_cast(enabledLayerNames.size()), .ppEnabledLayerNames = enabledLayerNames.data(), .enabledExtensionCount = static_cast(enabledExtensionNames.size()), .ppEnabledExtensionNames = enabledExtensionNames.data() }; mHandle = vk::createInstance(createInfo); VULKAN_HPP_DEFAULT_DISPATCHER.init(mHandle); // dump info about enabled extensions and layers logMsg("Enabled Vulkan instance extensions: {}", enabledExtensionNames); logMsg("Enabled Vulkan instance layers: {}", enabledLayerNames); // also dump the actual instance version const std::uint32_t apiVersion = vk::enumerateInstanceVersion(); logMsg("Vulkan instance version: {}.{}.{}.\n", VK_API_VERSION_MAJOR(apiVersion), VK_API_VERSION_MINOR(apiVersion), VK_API_VERSION_PATCH(apiVersion) ); if (isExtensionEnabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { // create debug messenger const vk::DebugUtilsMessengerCreateInfoEXT dumCreateInfo = { .messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError, .messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation, .pfnUserCallback = &vulkanMessengerCallback }; mDebugMessenger = mHandle.createDebugUtilsMessengerEXT(dumCreateInfo); } mPhysicalDevices = getPhysicalDeviceInfos(*this); mWorkerTaskLoop.start(10); } Instance::~Instance() noexcept { for (Addon* addon : getAddons()) { addon->cleanup(); } while (!mDeleteQueue.empty()) { runDeleters(true); } if (mDebugMessenger) { mHandle.destroyDebugUtilsMessengerEXT(mDebugMessenger); } mHandle.destroy(); } bool Instance::isExtensionEnabled(const char* name) const noexcept { for (const ExtensionInfo& extInfo : mExtensions) { if (std::strcmp(extInfo.name, name) == 0) { return extInfo.enabled; } } return false; } bool Instance::isLayerEnabled(const char* name) const noexcept { for (const LayerInfo& layerInfo : mLayers) { if (std::strcmp(layerInfo.name, name) == 0) { return layerInfo.enabled; } } return false; } ObjectPtr Instance::createWindow(const WindowCreationArgs& args) { return createChild(args); } ObjectPtr Instance::createDevice(DeviceCreationArgs args) { return createChild(std::move(args)); } void Instance::queueDelete(deleter_t deleter) noexcept { MIJIN_ASSERT(deleter, "Don't pass an empty function into queueDelete()!"); if (!deleter) { return; } runOnMainThread([deleter = std::move(deleter), this]() mutable { static const int DELETE_DELAY_FRAMES = 5; // TODO: ? mDeleteQueue.push_back({ .deleter = std::move(deleter), .remainingFrames = DELETE_DELAY_FRAMES }); }); } void Instance::tickDeleteQueue() { for (DeleteQueueEntry& entry : mDeleteQueue) { --entry.remainingFrames; } runDeleters(); } void Instance::setMainThread() { mMainThread = std::this_thread::get_id(); } void Instance::requestQuit() noexcept { if (mQuitRequested) { return; } mQuitRequested = true; quitRequested.emit(); mMainTaskLoop.addTask([](Instance* instance) -> mijin::Task<> { for (int tick = 0; tick < 100; ++tick) { if (instance->getMainTaskLoop().getNumTasks() == 1) { // just us left, bye bye! co_return; } co_await mijin::c_suspend(); } const std::size_t activeTasks = instance->getMainTaskLoop().getActiveTasks() - 1; // substract yourself if (activeTasks > 0) { logMsg("{} tasks did not finish when shutting down!", activeTasks - 1); #if MIJIN_COROUTINE_ENABLE_DEBUG_INFO const std::vector tasks = instance->getMainTaskLoop().getAllTasks(); for (auto [index, handle] : mijin::enumerate(tasks)) { if (handle == mijin::getCurrentTask()) { continue; } const mijin::Optional stack = handle.getCreationStack(); if (!stack.empty()) { logMsg("Task {} creation stack:\n{}", index, *stack); } } #endif // MIJIN_COROUTINE_ENABLE_DEBUG_INFO MIJIN_TRAP(); } else { const std::size_t totalTasks = instance->getMainTaskLoop().getNumTasks() - 1; logMsg("{} tasks still waiting on program exit.", totalTasks); } instance->getMainTaskLoop().cancelAllTasks(); co_return; }(this)); } void Instance::runDeleters(bool runAll) { for (auto it = mDeleteQueue.begin(); it != mDeleteQueue.end();) { if (runAll || it->remainingFrames <= 0) { it->deleter(); it = mDeleteQueue.erase(it); } else { ++it; } } } }