Search code examples
c++structc++17vulkanstruct-member-alignment

Comparing two structs of the same type from external API


So basically I'm trying to compare two VkPhysicalDeviceFeatures from Vulkan, one from the VkPhysicalDevice I'm looking at, and the other that corresponds to a set of features I actually require. a VkPhysicalDeviceFeatures struct only contains VkBool32 members (which are typedefs of uint32_t), but each minor version of vulkan can add an unknown number of these features. What I would like to do is simply compare the members of each struct against each other, but not for equality, more of a logical comparison. If the corresponding member in the physical device struct is false, but my struct has true for that member, then the comparison should return false.

The only way I can think of doing this is something like what this answer posted:

bool hasRequiredFeatures(VkPhysicalDevice physical_device,
                              VkPhysicalDeviceFeatures required_features) {

    VkPhysicalDeviceFeatures physical_device_features = getSupportedFeatures(physical_device);
    std::size_t struct_length = sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32);

    auto physical_device_features_bool_ptr = reinterpret_cast<VkBool32*>(&physical_device_features);
    auto required_features_bool_ptr = reinterpret_cast<VkBool32*>(&required_features);
    for(std::size_t i = 0; i < struct_length; ++i){
        if(physical_device_features_bool_ptr[i] == VK_FALSE && required_features_bool_ptr[i] == VK_TRUE){
            return false;
        }
    }
    return true;
}

This does what I want (though it would be nice to have a way to see which specific member by name failed the comparison, but that I guess that is impossible with out reflection) but I don't think C++ guarantees strict alignment like this? Is there a cross platform way for me to accomplish this?


Solution

  • There's a big problem with your approach that has less to do with C++ and more to do with Vulkan. Specifically:

    minor version of vulkan can add an unknown number of these structs

    This tells me that you intend to apply such technology to VkPhysicalDeviceFeatures2, as well as any feature struct which can appear in its pNext chain. Hence the "an unknown number of these structs".

    Well, here's the thing: VkPhysicalDeviceFeatures2 is not just a bunch of VkBools. It is an extensible Vulkan struct, which means that it starts with the sType and pNext fields common to such structures. So are all of the post-1.0 Vulkan device feature structs.

    Code that can do what you've stated with post-1.0 feature structs must be able to take an entire pNext chain of feature structs and test them. And in order to do that, you have to know what they are. There's no way to query, from just a pointer to arbitrary data, that this data contains X number of VkBools.

    To make this work, you will need to be able to map an sType value to the size of that structure. And therefore, it cannot be automatically extensible (and no, C++ reflection can't fix that; it cannot know what struct a void *pNext points to); this will require some level of manual upkeep.

    Fortunately, the Vulkan XML specification description files clearly specify which structs can exist in a particular pNext chain. So you can write a tool to load the XML, find VkPhysicalDeviceFeatures2, and process all of the structs appearing in its pNext chain (this part is easier said than done, since the XML format was only built to be processed by Khronos's own tools) to find which structs are available, and generate the C++ information you need. I'm sure you can write such a thing relatively easily in any scripting language.

    But since you've got the struct definitions in (quasi-reasonable) XML, and you've got this tool that's going to be generating some C++ code anyway... you can just generate the actual comparison logic. That is, instead of hand-writing the member-to-member comparisons, just generate the member-to-member comparisons. You can even get the member names that don't match.

    If you're going to process arbitrary pNext-chain features, then you're going to need a generator tool of some kind. And if you're going to need a generator tool anyway, just use it to solve the whole problem.

    Now, it's important to realize that the generation code for a hypothetical hasRequiredFeatures implementation is going to have to be semi-complicated. If you're allowing a full pNext chain of structs, then you need to build your own corresponding chain of equivalent structs to use to query from Vulkan. And that's not exactly trivial.

    You will need to iterate through the pNext chain and examine the sType field of each struct. But of course, pNext is a void*, so you're going to have to lie/cheat C++'s rules a bit to read the sType field. Basically, you'll have to reinterpret_cast the void* to an VkStructureType*, read the value, compare it against all the possibilities you're working with, and proceed from there. You should skip any sType that you don't know about, which will require doing more C++ trickery.

    But you're using a low-level API; breaking C++'s rules is just a thing you'll have to get used to down here.

    For each such struct, you need to allocate a matching struct, fill in its sType appropriately, and then add it to the pNext chain you're building.

    Once you've built all of these, you can make your Vulkan call, do your comparisons, collect the data, and lastly delete the entire chain of structs.


    If your goal really is to stick with just VkPhysicalDeviceFeatures and not the extensible structs, and you just want a C++-portable way to compare such structs, then just memcpy them into a VkBool array and compare the two arrays for mismatches. The two types are trivially copyable, so it's not prima facie illegitmate to do this.

    This code has not been compiled or tested.

    bool hasRequiredFeatures(VkPhysicalDevice physical_device,
                                  VkPhysicalDeviceFeatures required_features)
    {
    
        constexpr auto feature_count = sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32);
        using FeatureArray = std::array<VkBool, feature_count>;
    
        auto physical_device_features = getSupportedFeatures(physical_device);
    
        FeatureArray required;
        memcpy(&required, &required_features, sizeof(FeatureArray));
    
        FeatureArray retrieved;
        memcpy(&retrieved, &physical_device_features, sizeof(FeatureArray));
    
        bool did_mismatch = false;
        for(auto it_pair = std::mismatch(required.begin(), required.end(), retrieved.begin());
            it_pair.first != required.end();
            it_pair = std::mismatch(it_pair.first, required.end(), it_pair.second))
        {
            did_mismatch = true
            auto mismatch_index = it_pair.first - required.begin();
            //Do something with mismatch_index
        }
    
        return did_mismatch;
    }