diff --git a/src/include/libplacebo/vulkan.h b/src/include/libplacebo/vulkan.h index 62255c5d938a9dc929e49e73f65acc7dcd410de7..c5ef9b154af82e8d80925b0e2d4824e4ccc00b26 100644 --- a/src/include/libplacebo/vulkan.h +++ b/src/include/libplacebo/vulkan.h @@ -125,9 +125,6 @@ struct pl_vulkan { int num_extensions; // The device features that were enabled at device creation time. - // - // Note: The pNext chain of this only includes features known to - // libplacebo, which as the time of writing only includes the base set. const VkPhysicalDeviceFeatures2KHR *features; // The explicit queue families we are using to provide a given capability, @@ -235,7 +232,9 @@ struct pl_vulkan_params { // Optional extra features to enable at device creation time. These are // opportunistically enabled if supported by the physical device, but // otherwise kept disabled. Users may include extra extension-specific - // features in the pNext chain. + // features in the pNext chain, however these *must* all be + // extension-specific structs, i.e. the use of "meta-structs" like + // VkPhysicalDeviceVulkan11Features is not allowed. const VkPhysicalDeviceFeatures2KHR *features; // --- Misc/debugging options @@ -456,9 +455,9 @@ const struct pl_tex *pl_vulkan_wrap(const struct pl_gpu *gpu, // optional, but provide a hint to the API user as to what might be worth // enabling at device creation time. // -// Note: Currently, libplacebo does not include any extra extension-specific -// device features in the `pNext` chain of pl_vulkan_recommended_features, but -// this may change in the future. +// Note: This also includes physical device features provided by extensions. +// They are all provided using extension-specific features structs, rather +// than the more general purpose VkPhysicalDeviceVulkan11Features etc. extern const char * const pl_vulkan_recommended_extensions[]; extern const int pl_vulkan_num_recommended_extensions; extern const VkPhysicalDeviceFeatures2KHR pl_vulkan_recommended_features; diff --git a/src/vulkan/context.c b/src/vulkan/context.c index 0445b07f046898244c653896d5d85720495b80b0..89bef52118d482b37fe1c1c8ef145e117c9016c9 100644 --- a/src/vulkan/context.c +++ b/src/vulkan/context.c @@ -1087,21 +1087,50 @@ static bool device_init(struct vk_ctx *vk, const struct pl_vulkan_params *params } } - // Enable all features that we might need, by iterating through the entire - // VkPhysicalDeviceFeatures struct as a VkBool32 array and checking each - // supported feature against the whitelist of features we want - vk->GetPhysicalDeviceFeatures2KHR(vk->physd, &vk->features); - for (int i = 0; i < sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32); i++) { - VkBool32 wanted = ((VkBool32 *) &pl_vulkan_recommended_features.features)[i]; - if (params->features) - wanted |= ((VkBool32 *) ¶ms->features->features)[i]; + // Query all supported device features by constructing a pNext chain + // starting with the features we care about and ending with whatever + // features were requested by the user + vk->features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR; + for (const VkBaseInStructure *in = pl_vulkan_recommended_features.pNext; + in; in = in->pNext) + vk_link_struct(&vk->features, vk_struct_memdup(vk->ta, in)); + + for (const VkBaseInStructure *in = (const VkBaseInStructure *) params->features; + in; in = in->pNext) + { + if (vk_find_struct(&vk->features, in->sType)) + continue; // skip structs already present + + void *copy = vk_struct_memdup(vk->ta, in); + if (!copy) { + PL_ERR(vk, "Unknown struct type %"PRIu64"?", (uint64_t) in->sType); + continue; + } - ((VkBool32 *) &vk->features.features)[i] &= wanted; + vk_link_struct(&vk->features, copy); } - // Temporarily link the pNext chain of the extra user features into this - if (params->features) - vk->features.pNext = params->features->pNext; + vk->GetPhysicalDeviceFeatures2KHR(vk->physd, &vk->features); + + // Go through the features chain a second time and mask every option + // that wasn't whitelisted by either libplacebo or the user + for (VkBaseOutStructure *chain = (VkBaseOutStructure *) &vk->features; + chain; chain = chain->pNext) + { + const VkBaseInStructure *in_a, *in_b; + in_a = vk_find_struct(&pl_vulkan_recommended_features, chain->sType); + in_b = vk_find_struct(params->features, chain->sType); + in_a = PL_DEF(in_a, in_b); + in_b = PL_DEF(in_b, in_a); + pl_assert(in_a && in_b); + + VkBool32 *req = (VkBool32 *) &chain[1]; + const VkBool32 *wl_a = (const VkBool32 *) &in_a[1]; + const VkBool32 *wl_b = (const VkBool32 *) &in_b[1]; + size_t size = vk_struct_size(chain->sType) - sizeof(chain[0]); + for (int i = 0; i < size / sizeof(VkBool32); i++) + req[i] &= wl_a[i] || wl_b[i]; + } VkDeviceCreateInfo dinfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, @@ -1117,7 +1146,6 @@ static bool device_init(struct vk_ctx *vk, const struct pl_vulkan_params *params PL_INFO(vk, " %s", (*exts)[i]); VK(vk->CreateDevice(vk->physd, &dinfo, VK_ALLOC, &vk->dev)); - vk->features.pNext = NULL; // Load all mandatory device-level functions for (int i = 0; i < PL_ARRAY_SIZE(vk_dev_funs); i++) { @@ -1186,7 +1214,6 @@ const struct pl_vulkan *pl_vulkan_create(struct pl_context *ctx, .ctx = ctx, .inst = params->instance, .GetInstanceProcAddr = get_proc_addr_fallback(ctx, params->get_proc_addr), - .features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR, }; if (!vk->GetInstanceProcAddr) @@ -1337,7 +1364,6 @@ const struct pl_vulkan *pl_vulkan_import(struct pl_context *ctx, .physd = params->phys_device, .dev = params->device, .GetInstanceProcAddr = get_proc_addr_fallback(ctx, params->get_proc_addr), - .features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR, }; if (!vk->GetInstanceProcAddr) @@ -1377,11 +1403,10 @@ const struct pl_vulkan *pl_vulkan_import(struct pl_context *ctx, PRINTF_VER(params->max_api_version), PRINTF_VER(vk->api_ver)); } - if (params->features) { - // Explicitly drop the pNext chain of this, since we don't currently - // "officially" know about any extra features provided by extensions - vk->features.features = params->features->features; - } + VkPhysicalDeviceFeatures2KHR *features; + features = vk_chain_memdup(vk->ta, params->features); + if (features) + vk->features = *features; // Load all mandatory device-level functions for (int i = 0; i < PL_ARRAY_SIZE(vk_dev_funs); i++) { diff --git a/src/vulkan/utils.c b/src/vulkan/utils.c index 0d1ae2c26ff6896940f9d8d0bf7c69694117057d..2a84d906d1ff0e178244b9d5df53306f4946c932 100644 --- a/src/vulkan/utils.c +++ b/src/vulkan/utils.c @@ -103,3 +103,54 @@ const enum pl_handle_type vk_sync_handle_list[] = { #endif 0 }; + +const void *vk_find_struct(const void *chain, VkStructureType stype) +{ + const VkBaseInStructure *in = chain; + while (in) { + if (in->sType == stype) + return in; + + in = in->pNext; + } + + return NULL; +} + +void vk_link_struct(void *chain, void *in) +{ + if (!in) + return; + + VkBaseOutStructure *out = chain; + while (out->pNext) + out = out->pNext; + + out->pNext = in; +} + +void *vk_struct_memdup(void *tactx, const void *pin) +{ + if (!pin) + return NULL; + + const VkBaseInStructure *in = pin; + size_t size = vk_struct_size(in->sType); + if (!size) + return NULL; + + VkBaseOutStructure *out = talloc_memdup(tactx, in, size); + out->pNext = NULL; + return out; +} + +void *vk_chain_memdup(void *tactx, const void *pin) +{ + const VkBaseInStructure *in = pin; + VkBaseOutStructure *out = vk_struct_memdup(tactx, in); + if (!out) + return NULL; + + out->pNext = vk_chain_memdup(tactx, in->pNext); + return out; +} diff --git a/src/vulkan/utils.h b/src/vulkan/utils.h index a017cd434f99d39cd3ace06043805058e3545aff..56261f99591bc76e6770fb40bc15729ddf682a45 100644 --- a/src/vulkan/utils.h +++ b/src/vulkan/utils.h @@ -23,6 +23,9 @@ const char *vk_res_str(VkResult res); const char *vk_obj_str(VkObjectType obj); +// Return the size of an arbitrary vulkan struct. Returns 0 for unknown structs +size_t vk_struct_size(VkStructureType stype); + // Enum translation boilerplate VkExternalMemoryHandleTypeFlagBitsKHR vk_mem_handle_type(enum pl_handle_type); VkExternalSemaphoreHandleTypeFlagBitsKHR vk_sync_handle_type(enum pl_handle_type); @@ -36,6 +39,18 @@ bool vk_external_mem_check(const VkExternalMemoryPropertiesKHR *props, extern const enum pl_handle_type vk_mem_handle_list[]; extern const enum pl_handle_type vk_sync_handle_list[]; +// Find a structure in a pNext chain, or NULL +const void *vk_find_struct(const void *chain, VkStructureType stype); + +// Link a structure into a pNext chain +void vk_link_struct(void *chain, void *in); + +// Make a copy of a structure, not including the pNext chain +void *vk_struct_memdup(void *tactx, const void *in); + +// Make a deep copy of an entire pNext chain +void *vk_chain_memdup(void *tactx, const void *in); + // Convenience macros to simplify a lot of common boilerplate #define VK_ASSERT(res, str) \ do { \ diff --git a/src/vulkan/utils_gen.py b/src/vulkan/utils_gen.py index cf3b3fb8d9b0137d187678bdb4af51603727afcb..07d3a5f8eb1234cf191bef3fd2011d0c0d11287e 100644 --- a/src/vulkan/utils_gen.py +++ b/src/vulkan/utils_gen.py @@ -43,6 +43,17 @@ const char *vk_obj_str(VkObjectType obj) default: return "unknown object"; } } + +size_t vk_struct_size(VkStructureType stype) +{ + switch (stype) { +%for struct in vkstructs: + case ${struct.stype}: return sizeof(${struct.name}); +%endfor + + default: return 0; + } +} """) class Obj(object): @@ -59,6 +70,22 @@ def get_vkobjects(registry): yield Obj(enum = e.attrib['name'], name = e.attrib['comment']) +def get_vkstructs(registry): + for e in registry.findall('types/type[@category="struct"]'): + # Strings for platform-specific crap we want to blacklist as they will + # most likely cause build failures + blacklist_strs = [ + 'ANDROID', 'Surface', 'Win32', 'D3D12', 'GGP' + ] + + if any([ str in e.attrib['name'] for str in blacklist_strs ]): + continue + + stype = e.find('member/name[.="sType"]/..') + if stype and 'values' in stype.attrib: + yield Obj(stype = stype.attrib['values'], + name = e.attrib['name']) + if __name__ == '__main__': assert len(sys.argv) == 3 xmlfile = sys.argv[1] @@ -69,4 +96,5 @@ if __name__ == '__main__': f.write(TEMPLATE.render( vkresults = get_vkresults(registry), vkobjects = get_vkobjects(registry), + vkstructs = get_vkstructs(registry), ))