From a4382c63055935bd18be70a575c28e01725a6ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 16 Dec 2020 19:07:23 +0100 Subject: [PATCH] Vk: more utilites for heavyweight structure chain processing. Gah, these device features are *hell*. --- .../Vk/Implementation/structureHelpers.h | 88 +++++++++++++++++++ src/Magnum/Vk/Test/StructureHelpersTest.cpp | 79 ++++++++++++++++- 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/src/Magnum/Vk/Implementation/structureHelpers.h b/src/Magnum/Vk/Implementation/structureHelpers.h index 62f847fac..fd80693d4 100644 --- a/src/Magnum/Vk/Implementation/structureHelpers.h +++ b/src/Magnum/Vk/Implementation/structureHelpers.h @@ -27,12 +27,18 @@ #include #include +#include #include "Magnum/Magnum.h" #include "Magnum/Vk/Vulkan.h" namespace Magnum { namespace Vk { namespace Implementation { +/* All those helpers are designed in a way that only allows them to work with a + "whitelisted" set of structures to avoid modifying external data by + accident. Thus no "give me the first structure of this type" or "remove any + structure of this type from the chain". */ + /* Meant to be used for connecting a longer chain of structures. Anything that was connected to the `next` pointer before is reconnected to `structure.pNext`; the `next` reference is rebound to the `structure.pNext` @@ -54,6 +60,88 @@ template inline void structureConnect(Containers::Reference&>(next), structure, type); } +CORRADE_HAS_TYPE(IsVulkanStructure, decltype(T::pNext)); + +/* Returns a pointer to the pNext field that has a value of `pointer` or + nullptr if no such structure if found. It can also return the `next` + parameter, if its value is already the pointer. */ +template inline void** structureFind(void*& next, const T& structure) { + static_assert(IsVulkanStructure::value, "passed type is not a Vulkan structure"); + + /* We take a reference in order to make it possible to mutate the found + result, however we need to rebind that reference in the loop, and + assigning to `next` would mean we corrupt the original structure. */ + Containers::Reference nextRebindable = next; + while(nextRebindable) { + auto& found = *reinterpret_cast(*nextRebindable); + if(&found == static_cast(&structure)) return &*nextRebindable; + nextRebindable = reinterpret_cast(found.pNext); + } + + return nullptr; +} + +template inline const void** structureFind(const void*& next, const T& structure) { + /* Ugly. Yes. Same reasoning as in structureConnect(). */ + return const_cast(structureFind(const_cast(next), structure)); +} + +class AnyStructure { + public: + template::value>::type> /*implicit*/ AnyStructure(const T& structure): _structure{reinterpret_cast(&structure)} {} + + /*implicit*/ operator const VkBaseOutStructure&() const { return *_structure; } + + private: + const VkBaseOutStructure* _structure; +}; + +/* Given a chain of structures that are always connected in the same order (but + not necessarily all of them), in which `next` points to the first structure + of the chain, the function disconnects the chain from `next`, replacing the + pointer with the first `pNext` value that points outside of the chain. The + `structures` themselves are not touched in any way. + + Given the following, where 0-9 are the `structures` and 1, 3, 4, 5, 7, 8 + form a chain in which the last `pNext` points to `out`: + + next ___ ___ out + \ / \ / \ / + + + + +--+--+ + +--+ + + 0 1 2 3 4 5 6 7 8 9 + + The result is the following --- the structures are left intact, and `next` + now points directly to `out`. After that, the structures can be cleared and + repurposed or discarded without a risk of losing access to the chain end. + + next ----------------------- out + ___ ___ + / \ / \ / + + + + +--+--+ + +--+ + + 0 1 2 3 4 5 6 7 8 9s + +*/ +inline void structureDisconnectChain(const void*& next, std::initializer_list structures) { + /* We take a reference in order to make it possible to mutate the found + result, however we need to rebind that reference in the loop, and + assigning to `next` would mean we corrupt the original structure. */ + Containers::Reference nextRebindable = next; + + for(const VkBaseOutStructure& structure: structures) { + if(nextRebindable == &structure) + nextRebindable = reinterpret_cast(structure.pNext); + } + + /* For safety we expect `next` actually pointed to something inside the + chain */ + CORRADE_INTERNAL_ASSERT(&*nextRebindable != next); + next = *nextRebindable; +} + +inline void structureDisconnectChain(void*& next, std::initializer_list structures) { + return structureDisconnectChain(const_cast(next), structures); +} + }}} #endif diff --git a/src/Magnum/Vk/Test/StructureHelpersTest.cpp b/src/Magnum/Vk/Test/StructureHelpersTest.cpp index 3f2ad40e9..8357a7fab 100644 --- a/src/Magnum/Vk/Test/StructureHelpersTest.cpp +++ b/src/Magnum/Vk/Test/StructureHelpersTest.cpp @@ -33,12 +33,18 @@ struct StructureHelpersTest: TestSuite::Tester { explicit StructureHelpersTest(); template void connect(); + template void find(); + template void disconnectChain(); }; StructureHelpersTest::StructureHelpersTest() { addTests({ &StructureHelpersTest::connect, - &StructureHelpersTest::connect}); + &StructureHelpersTest::connect, + &StructureHelpersTest::find, + &StructureHelpersTest::find, + &StructureHelpersTest::disconnectChain, + &StructureHelpersTest::disconnectChain}); } template struct Type; @@ -77,6 +83,77 @@ template void StructureHelpersTest::connect() { CORRADE_COMPARE(*next, &variableFeatures); } +template void StructureHelpersTest::find() { + typedef typename std::remove_reference::type NextType; + setTestCaseTemplateName(Type::name()); + + VkPhysicalDeviceVariablePointersFeatures variableFeatures{}; + + VkPhysicalDeviceSamplerYcbcrConversionFeatures ycbcrFeatures{}; + ycbcrFeatures.pNext = &variableFeatures; + + VkPhysicalDeviceMultiviewFeatures multiviewFeatures{}; + multiviewFeatures.pNext = &ycbcrFeatures; + + T info{}; + info.pNext = &multiviewFeatures; + + CORRADE_COMPARE(const_cast(Implementation::structureFind(info.pNext, variableFeatures)), const_cast(&ycbcrFeatures.pNext)); + /* It shouldn't be modifying the input in any way (heh, what the hell did + I manage to do here) */ + CORRADE_COMPARE(info.pNext, &multiviewFeatures); + + CORRADE_COMPARE(const_cast(Implementation::structureFind(info.pNext, ycbcrFeatures)), const_cast(&multiviewFeatures.pNext)); + CORRADE_COMPARE(info.pNext, &multiviewFeatures); + + CORRADE_COMPARE(const_cast(Implementation::structureFind(info.pNext, multiviewFeatures)), const_cast(&info.pNext)); + CORRADE_COMPARE(info.pNext, &multiviewFeatures); + + CORRADE_COMPARE(Implementation::structureFind(info.pNext, info), nullptr); + CORRADE_COMPARE(info.pNext, &multiviewFeatures); +} + +template void StructureHelpersTest::disconnectChain() { + typedef typename std::remove_reference::type NextType; + setTestCaseTemplateName(Type::name()); + + VkPhysicalDeviceFeatures2 a0{}, a1{}, a2{}, a3{}, a4{}, a5{}, a6{}, a7{}, a8{}, a9{}; + + VkDeviceGroupSubmitInfo out{}; + + int error{}; + + T info; + info.pNext = &a1; + a0.pNext = &error; + a1.pNext = &a3; + a2.pNext = &error; + a3.pNext = &a4; + a4.pNext = &a5; + a5.pNext = &a7; + a6.pNext = &error; + a7.pNext = &a8; + a8.pNext = &out; + a9.pNext = &error; + + Implementation::structureDisconnectChain(info.pNext, { + a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 + }); + CORRADE_COMPARE(info.pNext, &out); + + /* Everything else should stay as it was before */ + CORRADE_COMPARE(a0.pNext, &error); + CORRADE_COMPARE(a1.pNext, &a3); + CORRADE_COMPARE(a2.pNext, &error); + CORRADE_COMPARE(a3.pNext, &a4); + CORRADE_COMPARE(a4.pNext, &a5); + CORRADE_COMPARE(a5.pNext, &a7); + CORRADE_COMPARE(a6.pNext, &error); + CORRADE_COMPARE(a7.pNext, &a8); + CORRADE_COMPARE(a8.pNext, &out); + CORRADE_COMPARE(a9.pNext, &error); +} + }}}} CORRADE_TEST_MAIN(Magnum::Vk::Test::StructureHelpersTest)