#include "iwa/device.hpp" #include "iwa/log.hpp" #include "iwa/instance.hpp" namespace iwa { namespace { void buildDefaultDeviceExtensionList(std::vector& outExtensions) noexcept { outExtensions.push_back({.name = VK_KHR_SWAPCHAIN_EXTENSION_NAME, .required = true}); } bool checkQueueFamilies(const PhysicalDeviceInfo& deviceInfo, [[maybe_unused]] int& score) { return deviceInfo.graphicsQueueFamily != std::numeric_limits::max() && deviceInfo.computeQueueFamily != std::numeric_limits::max(); } bool checkDeviceExtensions(const std::vector& extensions, const PhysicalDeviceInfo& deviceInfo, int& score) { auto isExtensionSupported = [&deviceInfo](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; }; for (const ExtensionInfo& extInfo : extensions) { if (isExtensionSupported(extInfo.name)) { score += 10; } else if (extInfo.required) { logVerbose("Vulkan device {} not supported as it is missing required extension {}.", deviceInfo.properties.deviceName, extInfo.name); return false; } } return true; } int scorePhysicalDevice( const PhysicalDeviceCriteria& deviceCriteria, const std::vector& extensions, const PhysicalDeviceInfo& deviceInfo) { int score = 0; switch (deviceInfo.properties.deviceType) { case vk::PhysicalDeviceType::eDiscreteGpu: score = 1000; break; case vk::PhysicalDeviceType::eIntegratedGpu: score = 100; break; default: break; } #define CHECK_FEATURE_STRUCT(infoName, requiredName) \ for (auto feature : deviceCriteria.requiredName) \ { \ if (!(deviceInfo.infoName.*feature)) \ { \ return -1; \ } \ } CHECK_FEATURE_STRUCT(features, requiredFeatures) CHECK_FEATURE_STRUCT(vulkan11Features, requiredVulkan11Features) CHECK_FEATURE_STRUCT(vulkan12Features, requiredVulkan12Features) CHECK_FEATURE_STRUCT(vulkan13Features, requiredVulkan13Features) CHECK_FEATURE_STRUCT(accelerationStructureFeatures, requiredAccelerationStructureFeatures) CHECK_FEATURE_STRUCT(rayTracingPipelineFeatures, requiredRayTracingPipelineFeatures) CHECK_FEATURE_STRUCT(meshShaderFeatures, requredMeshShaderFeatures) #undef CHECK_FEATURE_STRUCT if (!checkQueueFamilies(deviceInfo, score)) { logVerbose("Vulkan device {} not supported as no suitable queue family configuration could be found.", deviceInfo.properties.deviceName); return -1; } if (!checkDeviceExtensions(extensions, deviceInfo, score)) { return -1; } return score; } std::size_t findBestPhysicalDevice( PhysicalDeviceCriteria& deviceCriteria, const std::vector& extensions, const std::vector& deviceInfos) { int bestScore = -1; std::size_t bestIdx = 0; for (std::size_t idx = 0; idx < deviceInfos.size(); ++idx) { const int score = scorePhysicalDevice(deviceCriteria, extensions, deviceInfos[idx]); if (score > bestScore) { bestScore = score; bestIdx = idx; } } if (bestScore < 0) { logAndDie("Could not find a suitable Vulkan device!"); } return bestIdx; } std::vector buildDeviceExtensionNameList(const PhysicalDeviceInfo& deviceInfo, std::vector& extensions) { 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; }; std::vector enabledExtensions; for (ExtensionInfo& extInfo : extensions) { const bool supported = isExtensionSupported(extInfo.name); if (!supported && extInfo.required) { // this shouldn't be possible, we already checked when scoring the device logAndDie("Required Vulkan device extension not supported: {}.", extInfo.name); } if (supported) { enabledExtensions.push_back(extInfo.name); } extInfo.enabled = supported; } return enabledExtensions; } std::vector buildQueueCreateInfoList(const PhysicalDeviceInfo& deviceInfo) { static const float QUEUE_PRIORITY_ONE = 1.0f; std::vector createInfos; createInfos.emplace_back() .setQueueFamilyIndex(deviceInfo.graphicsQueueFamily) .setQueueCount(1) .setPQueuePriorities(&QUEUE_PRIORITY_ONE); return createInfos; } } // namespace Device::Device(ObjectPtr owner, DeviceCreationArgs args) : super_t(std::move(owner)), mExtensions(std::move(args.extensions)) { if (!args.flags.noDefaultExtensions) { buildDefaultDeviceExtensionList(mExtensions); } const std::vector& physicalDevices = getOwner()->getPhysicalDevices(); const std::size_t physicalDeviceIdx = findBestPhysicalDevice(args.physicalDeviceCriteria, mExtensions, physicalDevices); mDeviceInfo = &physicalDevices[physicalDeviceIdx]; const std::vector enabledExtensions = buildDeviceExtensionNameList(*mDeviceInfo, mExtensions); const std::vector queueCreateInfos = buildQueueCreateInfoList(*mDeviceInfo); void* pNext = nullptr; #define APPEND_FEATURE_STRUCT(type, name, requiredArray) \ type name{.pNext = pNext}; \ if (!args.physicalDeviceCriteria.requiredArray.empty()) \ { \ for (auto feature : args.physicalDeviceCriteria.requiredArray) \ { \ (name).*(feature) = VK_TRUE; \ } \ pNext = &(name); \ } APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan11Features, vulkan11Features, requiredVulkan11Features) APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan12Features, vulkan12Features, requiredVulkan12Features) APPEND_FEATURE_STRUCT(vk::PhysicalDeviceVulkan13Features, vulkan13Features, requiredVulkan13Features) APPEND_FEATURE_STRUCT(vk::PhysicalDeviceAccelerationStructureFeaturesKHR, accelerationStructureFeatures, requiredAccelerationStructureFeatures) APPEND_FEATURE_STRUCT(vk::PhysicalDeviceRayTracingPipelineFeaturesKHR, rayTracingPipelineFeature, requiredRayTracingPipelineFeatures) APPEND_FEATURE_STRUCT(vk::PhysicalDeviceMeshShaderFeaturesEXT, meshShaderFeatures, requredMeshShaderFeatures) #undef APPEND_FEATURE_STRUCT vk::PhysicalDeviceFeatures enabledFeatures; // NOLINT(*-const-correctness) false positive for (auto feature : args.physicalDeviceCriteria.requiredFeatures) { enabledFeatures.*feature = VK_TRUE; } const vk::DeviceCreateInfo deviceCreateInfo = { .pNext = pNext, .queueCreateInfoCount = static_cast(queueCreateInfos.size()), .pQueueCreateInfos = queueCreateInfos.data(), .enabledExtensionCount = static_cast(enabledExtensions.size()), .ppEnabledExtensionNames = enabledExtensions.data(), .pEnabledFeatures = &enabledFeatures }; mHandle = mDeviceInfo->device.createDevice(deviceCreateInfo); if (args.flags.singleDevice) { VULKAN_HPP_DEFAULT_DISPATCHER.init(mHandle); } mGraphicsQueue = mHandle.getQueue(mDeviceInfo->graphicsQueueFamily, 0); if (mDeviceInfo->graphicsQueueFamily == mDeviceInfo->computeQueueFamily) { mComputeQueue = mGraphicsQueue; } else { mComputeQueue = mHandle.getQueue(mDeviceInfo->computeQueueFamily, 0); } getOwner()->getMainTaskLoop().addTask(c_updateLoop(), &mUpdateLoopHandle); getOwner()->deviceCreated.emit(*this); } Device::~Device() noexcept { mUpdateLoopHandle.cancel(); mScratchCommandPools.clear(); // hack2: due to the below hack the pools would get destroyed after the device, reset them manually if (mHandle) { mHandle.waitIdle(); mPendingScratchCmdBuffers.clear(); getOwner()->queueDelete([handle=mHandle]() { handle.destroy(); }); } } ScratchCommandPool::ScratchCommandPool(Device& device) { mCommandPool = device.createChild(CommandPoolCreationArgs{ .flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer, .queueFamilyIndex = device.getDeviceInfo().graphicsQueueFamily }); } ObjectPtr ScratchCommandPool::allocateCommandBuffer() { for (Buffer& buffer : mBuffers) { if (buffer.doneFuture->ready()) { buffer.doneFuture = std::make_shared>(); buffer.cmdBuffer->getVkHandle().reset(); return buffer.cmdBuffer; } } // nothing found, allocate a new one return mBuffers.emplace_back(mCommandPool->allocateCommandBuffer(), std::make_shared>()).cmdBuffer; } mijin::FuturePtr ScratchCommandPool::getFuture(const ObjectPtr& cmdBuffer) noexcept { for (const Buffer& buffer : mBuffers) { if (buffer.cmdBuffer == cmdBuffer) { return buffer.doneFuture; } } logAndDie("Someone passed an invalid cmdBuffer to getFuture()!"); } ObjectPtr Device::allocateDeviceMemory(const DeviceMemoryAllocationArgs& args) { return createChild(args); } ObjectPtr Device::beginScratchCommandBuffer() { ObjectPtr cmdBuffer; { const std::shared_lock readLock(mScratchCommandPoolsMutex); auto it = mScratchCommandPools.find(std::this_thread::get_id()); if (it != mScratchCommandPools.end()) { cmdBuffer = it->second.allocateCommandBuffer(); } } if (cmdBuffer == nullptr) { // create the scratch command pool const std::unique_lock writeLock(mScratchCommandPoolsMutex); ScratchCommandPool& pool = mScratchCommandPools.emplace(std::this_thread::get_id(), ScratchCommandPool(*this)).first->second; cmdBuffer = pool.allocateCommandBuffer(); // hack: an object should normally not contain pointers to its own child as that leads to cyclic dependencies // decrease reference count by 1 to migitate that decreaseReferenceCount(); } const vk::CommandBufferBeginInfo beginInfo = { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit }; cmdBuffer->getVkHandle().begin(beginInfo); return cmdBuffer; } mijin::FuturePtr Device::endScratchCommandBuffer(ObjectPtr cmdBuffer) { mijin::FuturePtr future; { const std::shared_lock readLock(mScratchCommandPoolsMutex); future = mScratchCommandPools.at(std::this_thread::get_id()).getFuture(cmdBuffer); } cmdBuffer->getVkHandle().end(); getOwner()->runOnMainThread([cmdBuffer = std::move(cmdBuffer), self = getPointer(), future]() mutable { ObjectPtr doneFence = Fence::create(self); const vk::SubmitInfo submitInfo = { .commandBufferCount = 1, .pCommandBuffers = &cmdBuffer->getVkHandle() }; self->getGraphicsQueue().submit(submitInfo, *doneFence); self->mPendingScratchCmdBuffers.push_back({ .cmdBuffer = std::move(cmdBuffer), .doneFence = std::move(doneFence), .future = std::move(future) }); }); return future; } void Device::queueDelete(std::function deleter) noexcept { getOwner()->queueDelete(std::move(deleter)); } mijin::Task<> Device::c_updateLoop() noexcept { while(!getOwner()->isQuitRequested()) { for (auto it = mPendingScratchCmdBuffers.begin(); it != mPendingScratchCmdBuffers.end();) { if (it->doneFence->isDone()) { it->future->set(); it = mPendingScratchCmdBuffers.erase(it); } else { ++it; } } co_await mijin::c_suspend(); } } }