From 67ebe78a6e00eb3e331058bdee5e5437b6659f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 26 Sep 2020 17:32:08 +0200 Subject: [PATCH] Vk: ability to pick a memory type. Goes in-line with APIs for device and queue selection. --- src/Magnum/Vk/DeviceProperties.cpp | 41 +++++++++++++ src/Magnum/Vk/DeviceProperties.h | 43 +++++++++++++ src/Magnum/Vk/Test/DevicePropertiesVkTest.cpp | 60 +++++++++++++++++++ 3 files changed, 144 insertions(+) diff --git a/src/Magnum/Vk/DeviceProperties.cpp b/src/Magnum/Vk/DeviceProperties.cpp index e6e0d3981..7db752ad3 100644 --- a/src/Magnum/Vk/DeviceProperties.cpp +++ b/src/Magnum/Vk/DeviceProperties.cpp @@ -32,9 +32,11 @@ #include #include +#include "Magnum/Math/Functions.h" #include "Magnum/Vk/Instance.h" #include "Magnum/Vk/ExtensionProperties.h" #include "Magnum/Vk/LayerProperties.h" +#include "Magnum/Vk/Memory.h" #include "Magnum/Vk/Result.h" #include "Magnum/Vk/Implementation/Arguments.h" #include "Magnum/Vk/Implementation/InstanceState.h" @@ -236,6 +238,45 @@ UnsignedInt DeviceProperties::memoryHeapIndex(const UnsignedInt memory) { return properties.memoryTypes[memory].heapIndex; } +UnsignedInt DeviceProperties::pickMemory(const MemoryFlags requiredFlags, const MemoryFlags preferredFlags, const UnsignedInt memories) { + Containers::Optional id = tryPickMemory(requiredFlags, preferredFlags, memories); + if(id) return *id; + std::exit(1); /* LCOV_EXCL_LINE */ +} + +Containers::Optional DeviceProperties::tryPickMemory(const MemoryFlags requiredFlags, const MemoryFlags preferredFlags, const UnsignedInt memories) { + const VkPhysicalDeviceMemoryProperties properties = memoryProperties().memoryProperties; + + /* The picking strategy is basically equivalent to vmaFindMemoryTypeIndex() + from AMD's Vulkan Memory Allocator -- choosing the one that has the most + bits set. */ + Int maxPreferredBitCount = -1; + UnsignedInt maxPreferredBitCountMemory = ~UnsignedInt{}; + UnsignedInt bit = 1; + for(UnsignedInt i = 0; i != properties.memoryTypeCount; ++i, bit <<= 1) { + /* Not among considered memory types, skip */ + if(!(memories & bit)) + continue; + + /* Not all required flags present, skip */ + if(!(MemoryFlag(properties.memoryTypes[i].propertyFlags) >= requiredFlags)) + continue; + + /* Check how many of the preferred flags are present and use the one + with highest count */ + const Int preferredBitCount = Math::popcount(properties.memoryTypes[i].propertyFlags & UnsignedInt(preferredFlags)); + if(preferredBitCount > maxPreferredBitCount) { + maxPreferredBitCount = preferredBitCount; + maxPreferredBitCountMemory = i; + } + } + + if(maxPreferredBitCount >= 0) return maxPreferredBitCountMemory; + + Error{} << "Vk::DeviceProperties::tryPickMemory(): no" << requiredFlags << "found among" << Math::popcount(memories & ((1 << properties.memoryTypeCount) - 1)) << "considered memory types"; + return {}; +} + Containers::Array enumerateDevices(Instance& instance) { /* Retrieve total device count */ UnsignedInt count; diff --git a/src/Magnum/Vk/DeviceProperties.h b/src/Magnum/Vk/DeviceProperties.h index 82e3e0078..7e82e348d 100644 --- a/src/Magnum/Vk/DeviceProperties.h +++ b/src/Magnum/Vk/DeviceProperties.h @@ -421,6 +421,49 @@ class MAGNUM_VK_EXPORT DeviceProperties { */ UnsignedInt memoryHeapIndex(UnsignedInt memory); + /** + * @brief Pick a memory type satisfying given flags + * @param requiredFlags Memory flags that should be present in + * picked memory type. Can be an empty set, but picking such + * memory is probably not very useful. + * @param preferredFlags If there's more than one memory type + * matching @p requiredFlags, prefer one that has most of these + * as well. Defaults to an empty set. + * @param memories Bits indicating which memory types should + * be considered (bit @cpp 0 @ce indicates memory type @cpp 0 @ce + * should be considered etc.). Expected to have at least one bit + * of the first @ref memoryCount() bits set, otherwise the + * function will always fail. Defaults to all bits set, meaning + * all memory types are considered. Corresponds to the + * `memoryTypeBits` field of @type_vk{MemoryRequirements}. + * + * Queries memory properties using @ref memoryProperties() and out of + * memory types set in @p memoryBits tries to find one that contains + * all @p requiredFlags and most of @p optionalFlags. If it is not + * found, exits. See @ref tryPickMemory() for an alternative that + * doesn't exit on failure. + * + * @m_class{m-note m-success} + * + * @par + * The @p preferredFlags can be used for example to ask for a + * @ref MemoryFlag::HostVisible bit on a + * @ref MemoryFlag::DeviceLocal memory --- on discrete GPUs this + * combination is usually not possible so you get just a + * device-only memory, but on integrated GPUs it can be used to + * avoid a need for a copy through a temporary staging buffer. + */ + UnsignedInt pickMemory(MemoryFlags requiredFlags, MemoryFlags preferredFlags = {}, UnsignedInt memories = ~UnsignedInt{}); + + /** + * @brief Try to pick a memory type satisfying given flags + * + * Compared to @ref pickMemory() the function returns + * @ref Containers::NullOpt if a desired memory type isn't found + * instead of exiting. + */ + Containers::Optional tryPickMemory(MemoryFlags requiredFlags, MemoryFlags preferredFlags = {}, UnsignedInt memories = ~UnsignedInt{}); + private: friend Implementation::InstanceState; diff --git a/src/Magnum/Vk/Test/DevicePropertiesVkTest.cpp b/src/Magnum/Vk/Test/DevicePropertiesVkTest.cpp index 16aeba0e6..085a09a35 100644 --- a/src/Magnum/Vk/Test/DevicePropertiesVkTest.cpp +++ b/src/Magnum/Vk/Test/DevicePropertiesVkTest.cpp @@ -71,6 +71,9 @@ struct DevicePropertiesVkTest: VulkanTester { void memoryTypes(); void memoryTypeOutOfRange(); + void memoryTypesPick(); + void memoryTypesPickIgnoreSomePreferred(); + void memoryTypesPickFailed(); void pickDevice(); void pickDeviceIndex(); @@ -114,6 +117,9 @@ DevicePropertiesVkTest::DevicePropertiesVkTest(): VulkanTester{NoCreate} { &DevicePropertiesVkTest::memoryTypes, &DevicePropertiesVkTest::memoryTypeOutOfRange, + &DevicePropertiesVkTest::memoryTypesPick, + &DevicePropertiesVkTest::memoryTypesPickIgnoreSomePreferred, + &DevicePropertiesVkTest::memoryTypesPickFailed, &DevicePropertiesVkTest::pickDevice, &DevicePropertiesVkTest::pickDeviceIndex, @@ -427,6 +433,60 @@ void DevicePropertiesVkTest::memoryTypeOutOfRange() { "Vk::DeviceProperties::memoryHeapIndex(): index {0} out of range for {0} memory types\n", count)); } +void DevicePropertiesVkTest::memoryTypesPick() { + Containers::Array devices = enumerateDevices(instance()); + CORRADE_VERIFY(!devices.empty()); + + Containers::Optional id = devices[0].tryPickMemory(MemoryFlag::HostVisible|MemoryFlag::HostCoherent); + CORRADE_VERIFY(id); + CORRADE_COMPARE_AS(*id, devices[0].memoryCount(), TestSuite::Compare::Less); + CORRADE_COMPARE_AS(devices[0].memoryFlags(*id), + MemoryFlag::HostVisible|MemoryFlag::HostCoherent, + TestSuite::Compare::GreaterOrEqual); + + /* Pick should return the same ID, and shouldn't exit */ + CORRADE_COMPARE(devices[0].pickMemory(MemoryFlag::HostVisible|MemoryFlag::HostCoherent), id); + + /* If we put the same into preferred flags and leave the required empty, it + should pick the same (the first one as well) */ + Containers::Optional idPreferred = devices[0].tryPickMemory({}, MemoryFlag::HostVisible|MemoryFlag::HostCoherent); + CORRADE_COMPARE(idPreferred, id); +} + +void DevicePropertiesVkTest::memoryTypesPickIgnoreSomePreferred() { + Containers::Array devices = enumerateDevices(instance()); + CORRADE_VERIFY(!devices.empty()); + + Containers::Optional id = devices[0].tryPickMemory({}, MemoryFlag::HostVisible|MemoryFlag::HostCoherent|MemoryFlag(0xcafe0000u)); + CORRADE_VERIFY(id); + CORRADE_COMPARE_AS(*id, devices[0].memoryCount(), TestSuite::Compare::Less); + /* Should pick all what makes sense and ignore what doesn't */ + CORRADE_COMPARE_AS(devices[0].memoryFlags(*id), + MemoryFlag::HostVisible|MemoryFlag::HostCoherent, + TestSuite::Compare::GreaterOrEqual); + + /* And should be the same as picking the same required or halfway */ + CORRADE_COMPARE(id, devices[0].tryPickMemory(MemoryFlag::HostVisible|MemoryFlag::HostCoherent)); + CORRADE_COMPARE(id, devices[0].tryPickMemory(MemoryFlag::HostVisible, MemoryFlag::HostCoherent)); +} + +void DevicePropertiesVkTest::memoryTypesPickFailed() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Containers::Array devices = enumerateDevices(instance()); + CORRADE_VERIFY(!devices.empty()); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!devices[0].tryPickMemory(MemoryFlag(0xc0ffeee0))); + CORRADE_VERIFY(!devices[0].tryPickMemory({}, {}, 0)); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Vk::DeviceProperties::tryPickMemory(): no Vk::MemoryFlag(0xc0ffeee0) found among {} considered memory types\n" + "Vk::DeviceProperties::tryPickMemory(): no Vk::MemoryFlags{{}} found among 0 considered memory types\n", devices[0].memoryCount())); +} + void DevicePropertiesVkTest::pickDevice() { /* Default behavior */ Containers::Optional device = tryPickDevice(instance());