summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChad Versace <chadversary@chromium.org>2021-08-09 16:05:26 -0700
committerChad Versace <chadversary@chromium.org>2021-08-17 09:49:04 -0700
commit8ac036aa7425b36380488d8e0a22ffa282e0b88a (patch)
tree51c09d84a76733c1660ab09fd77dbe0c0649c4da
parentd701166befd8cef78889e42e0514ab1bc6653019 (diff)
downloaddrm-tests-bs-vk.zip
drm-tests-bs-vk.tar.xz
WIP: bsdrm/vk: Add Vulkan helpersbs-vk
BUG=b:195986385 TEST=Test the build. emerge-volteer drm-tests TODO: commit message TODO: finish vk_features test TODO: do vk_features in separate commit Change-Id: Id16a00fdd8faf0c85340aa33d6162467f5f3c788
-rw-r--r--Makefile8
-rw-r--r--bsdrm/include/bs_vk.h365
-rw-r--r--bsdrm/src/module.mk5
-rw-r--r--bsdrm/src/vk.c1006
-rw-r--r--vk_features.c491
-rw-r--r--vk_triangle.c36
6 files changed, 1910 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 11c1761..c7ec8a7 100644
--- a/Makefile
+++ b/Makefile
@@ -14,6 +14,7 @@ PC_LIBS := $(shell $(PKG_CONFIG) --libs $(PC_DEPS))
DRM_LIBS = -lGLESv2
CFLAGS += $(PC_CFLAGS) -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
CFLAGS += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
+CFLAGS += -Wno-initializer-overrides
LDLIBS += $(PC_LIBS)
all: \
@@ -36,7 +37,9 @@ all: \
ifeq ($(USE_VULKAN),1)
-all: CC_BINARY(vk_glow)
+all: \
+ CC_BINARY(vk_features) \
+ CC_BINARY(vk_glow)
endif
CC_BINARY(drm_cursor_test): drm_cursor_test.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
@@ -75,6 +78,9 @@ CC_BINARY(udmabuf_create_test): udmabuf_create_test.o \
CC_BINARY(udmabuf_create_test): LDLIBS += -lGLESv2 -lm
ifeq ($(USE_VULKAN),1)
+CC_BINARY(vk_features): vk_features.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
+CC_BINARY(vk_features): LDLIBS += -lm -lvulkan $(DRM_LIBS)
+
CC_BINARY(vk_glow): vk_glow.o CC_STATIC_LIBRARY(libbsdrm.pic.a)
CC_BINARY(vk_glow): LDLIBS += -lm -lvulkan $(DRM_LIBS)
endif
diff --git a/bsdrm/include/bs_vk.h b/bsdrm/include/bs_vk.h
new file mode 100644
index 0000000..2798604
--- /dev/null
+++ b/bsdrm/include/bs_vk.h
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#pragma once
+
+// TODO: clang format
+
+// Vulkan Helpers
+//
+// Annotations
+// The lifetime and ownership of resources in Vulkan can be difficult to reason about. Please
+// annotate the ownership of all struct members that reference resources, regardless of the
+// resource's source API.
+//
+// @owned
+// If a struct member is annotated as @owned, then its lifetime is managed by the
+// containing struct; the struct's destuctor will clean up the resource. If a function
+// parameter is annotated as @owned, then the function acquires ownership of the
+// parameter from the caller.
+//
+// @not-owned
+// If a struct member is annotated as @not-owned, then its lifetime is not managed by the
+// containing struct; the struct's destructor will not clean up the resource. If
+// a function parameter is annotated as @not-owned, then the caller retains ownership
+// of the parameter.
+//
+// Vulkan functions often have many many parameters contained in the input struct's pNext
+// chain. Some bs_vk functions similarly have many parameters, some with default values. For
+// complex functions, please annotate each parameter as required or optional, and its default
+// value if relevant.
+//
+// @required
+// The function parameter is required.
+//
+// @optional
+// The function parameters is optional, with a default value provided.
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <vulkan/vulkan.h>
+
+#include "bs_drm.h"
+
+// Profiles of Vulkan support, derived from the Chrome OS Partner requirements doc.
+//
+// TODO: Rename. I dislike "profile".
+//
+// If a profile's bit is set, and the profile is a superset of a another profile, then the subset
+// profile's bit must be set too.
+//
+// A Vulkan test may alter its behavior according to the profiles that the device is expected to
+// support. Tests should parse the targeted profiles from the command line.
+//
+// The Chrome OS Partner requirements doc specifies Vulkan requirements. The document has evolved
+// over time, and some requirements apply only to a subset of devices. For example, originally the
+// document only required Vulkan 1.1, and on 2021-06-19 began requiring Vulkan 1.2. Another example:
+// devices that support Linux games must support additional features.
+//
+// However, the doc does not enumerate its requirements in terms of "profiles". Instead, each
+// profile is derived from tribal knowledge and the doc's git log.
+enum bs_vk_profile {
+ // Requirements from 2020-10-19 at
+ // <https://chrome-internal.googlesource.com/chromeos/requirements/+/2fd46c6b0e50234f0b79ca6200887c0bc83874f3>,
+ // with fixes from
+ // <https://chrome-internal.googlesource.com/chromeos/requirements/+/d57404053a95e50ab062872535425e3383ef0c42>.
+ BS_VK_PROFILE_1_1 = 1 << 1,
+
+ // Requirements from 2021-08-10 at
+ // <https://chrome-internal.googlesource.com/chromeos/requirements/+/ac42d44b033f12b946586142abfbab32adae9632>.
+ BS_VK_PROFILE_1_2 = 1 << 2,
+
+ // Requirements from 2021-08-10 at
+ // <https://chrome-internal.googlesource.com/chromeos/requirements/+/ac42d44b033f12b946586142abfbab32adae9632>.
+ BS_VK_PROFILE_GAMING = 1 << 3,
+
+ // Support for protected memory. Not yet in the requirements doc.
+ BS_VK_PROFILE_PROTECTED = 1 << 4,
+};
+
+typedef uint32_t bs_vk_profile_flags_t;
+
+// Parse a comma-separated list of profile names. Profile names match the token names in `enum
+// bs_vk_profile` with the prefix stripped; names are case-insensitve; and '_' is equivalent to '.'.
+// Die if given an invalid profile name. If the list is empty or null, then return the base profile
+// BS_VK_PROFILE_1_1.
+//
+// Examples:
+// ""
+// "1.1"
+// "1_1"
+// "1.2,gaming"
+void bs_vk_parse_profiles(const char *s, bs_vk_profile_flags_t *bv_profiles);
+
+// Exit with failure if the expected and actual results differ.
+void bs_vk_check_result_loc(const char *func, const char *file, int line, VkResult expect,
+ VkResult actual);
+
+// Exit with failure if the expected and actual results differ.
+#define bs_vk_check_result(expect, actual) \
+ bs_vk_check_result_loc(__func__, __FILE__, __LINE__, (expect), (actual))
+
+// Exit with failure if 'result' is not VK_SUCCESS.
+#define bs_vk_check(result) \
+ bs_vk_check_result(VK_SUCCESS, (result))
+
+// Version variants must be 0. Version 'a' satisfies version 'b' if a(major) == b(major) and
+// a(minor.patch) >= b(minor.patch).
+bool bs_vk_version_satisfies(uint32_t actual, uint32_t required);
+
+// Version variants must be 0.
+uint32_t bs_vk_version_max(uint32_t a, uint32_t b);
+
+// Print the formatted Vulkan version to 'file'. Version variants are allowed.
+void bs_vk_version_fprint(FILE *file, uint32_t v);
+
+// Add 'child' to the pNext chain of 'parent'. Both must be a VkBaseOutStructure.
+void bs_vk_chain_add(void *parent, void *child);
+
+// Break all links in the pNext chain of 'root' by setting pNext to NULL in each child.
+// The 'root' must be a VkBaseOutStructure.
+void bs_vk_chain_break(void *root);
+
+// Wrapper for VkInstance.
+struct bs_vk_instance {
+ // @owned
+ VkInstance vk_instance;
+
+ // Equal to bs_vk_instance_new_info::bv_profiles.
+ bs_vk_profile_flags_t bv_profiles;
+
+ // Value from vkEnumerateInstanceVersion.
+ uint32_t vk_instance_version;
+
+ // Value of VkInstanceCreateInfo::pApplicationInfo::apiVersion.
+ uint32_t vk_api_version;
+
+ // Enabled instance extensions. This is a superset of
+ // bs_vk_instance_new_info::vk_instance_extensions because the framework may enable
+ // additional extensions.
+ struct bs_strv vk_instance_extensions;
+
+ // @not-owned
+ const VkAllocationCallbacks *vk_alloc;
+};
+
+// Parameters for bs_vk_instance_new().
+struct bs_vk_instance_new_info {
+ // Require at least this profile. Default is BS_VK_PROFILE_1_1.
+ //
+ // @optional
+ bs_vk_profile_flags_t bv_profiles;
+
+ // Require at least this instance version. The given 'bv_profiles' may require a higher
+ // version. Version variant must be 0.
+ //
+ // @optional
+ uint32_t vk_instance_version;
+
+ // Require at least this API version. The given 'bv_profiles' may require a higher version.
+ // Version variant must be 0.
+ //
+ // @optional
+ uint32_t vk_api_version;
+
+ // Required instance extensions. The framework and given 'bv_profiles' may require
+ // additional extensions. Null-terminated.
+ //
+ // @optional
+ // @not-owned
+ const char **vk_instance_extensions;
+
+ // @optional
+ // @not-owned
+ const VkAllocationCallbacks *vk_alloc;
+};
+
+#define bs_vk_instance_new(...) \
+ bs_vk_instance_new_s(&(struct bs_vk_instance_new_info) { \
+ .bv_profiles = BS_VK_PROFILE_1_1, \
+ ##__VA_ARGS__ \
+ })
+
+struct bs_vk_instance * __attribute__((returns_nonnull))
+bs_vk_instance_new_s(const struct bs_vk_instance_new_info *bv_info);
+
+void
+bs_vk_instance_destroy(struct bs_vk_instance **pself);
+
+// Wrapper for VkPhysicalDevice.
+//
+// For all struct members that contain a pNext pointer, the pNext is null.
+//
+// The VkPhysicalDevice satisifies the following:
+// - Its 'apiVersion' satisfies bs_vk_instance::vk_api_version.
+// - Its 'vendorId' and 'deviceId' matches the pci_id of drm_fd.
+// - It supports all extensions in bs_vk_device_new_info::vk_device_extensions; all extensions
+// required by bv_instance::bv_profiles; and possibly additional extensions required by the
+// bs_vk code.
+// - At least one queue supports VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT. If
+// BS_VK_PROFILE_PROTECTED, then at least one queue supports VK_QUEUE_GRAPHICS_BIT
+// | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_PROTECTED_BIT.
+// - For each profile in bs_vk_instance::bv_profiles, it supports the requirements queried
+// through VkPhysicalDeviceProperties2 and VkPhysicalDeviceFeatures2 and their pNext chains.
+// - WARNING. Support is not guaranteed for requirements related to VkFormatProperties2 and
+// VkImageFormatProperties2.
+//
+struct bs_vk_physical_device {
+ VkPhysicalDevice vk_physical_device; // @owned
+ struct bs_vk_instance *bv_instance; // @not-owned
+
+ int drm_fd; // @not-owned
+ struct gbm_device *gbm_device; // @not-owned
+
+ // Available device extensions.
+ struct bs_strv vk_device_extensions_avail;
+
+ VkPhysicalDeviceMemoryProperties vk_memory_properties;
+
+ uint32_t vk_queue_family_count;
+ VkQueueFamilyProperties *vk_queue_family_properties;
+
+ VkPhysicalDeviceProperties vk_properties;
+ VkPhysicalDeviceFeatures vk_features;
+
+ // Provided by VULKAN_1_1
+ VkPhysicalDeviceProtectedMemoryFeatures vk_protected_memory_features;
+ VkPhysicalDeviceShaderDrawParametersFeatures vk_shader_draw_parameters_features;
+
+ // Provided by VULKAN_1_2
+ VkPhysicalDeviceVulkan11Properties vk_vulkan_1_1_properties;
+ VkPhysicalDeviceVulkan11Features vk_vulkan_1_1_features;
+ VkPhysicalDeviceVulkan12Properties vk_vulkan_1_2_properties;
+ VkPhysicalDeviceVulkan12Features vk_vulkan_1_2_features;
+
+ // Provided by extensions
+ VkPhysicalDeviceDepthStencilResolvePropertiesKHR vk_depth_stencil_resolve_properties;
+ VkPhysicalDeviceDriverPropertiesKHR vk_driver_properties;
+ VkPhysicalDeviceCustomBorderColorPropertiesEXT vk_custom_border_color_properties;
+ VkPhysicalDeviceCustomBorderColorFeaturesEXT vk_custom_border_color_features;
+ VkPhysicalDeviceDepthClipEnableFeaturesEXT vk_depth_clip_enable_features;
+ VkPhysicalDeviceHostQueryResetFeaturesEXT vk_host_query_reset_features;
+ VkPhysicalDeviceMemoryPriorityFeaturesEXT vk_memory_priority_features;
+ VkPhysicalDeviceTransformFeedbackPropertiesEXT vk_transform_feedback_properties;
+ VkPhysicalDeviceTransformFeedbackFeaturesEXT vk_transform_feedback_features;
+ VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT vk_vertex_attribute_divisor_properties;
+ VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT vk_vertex_attribute_divisor_features;
+};
+
+// Parameters for bs_vk_physical_device_new().
+struct bs_vk_physical_device_new_info {
+ // @required @not-owned
+ int drm_fd;
+
+ // @required @not-owned
+ struct gbm_device *gbm_device;
+
+ // Required device extensions. Null-terminated. The bs_vk_instance::bv_profiles may require
+ // additional extensions.
+ //
+ // @optional @not-owned
+ const char **vk_device_extensions;
+};
+
+#define bs_vk_physical_device_new(bv_instance, ...) \
+ bs_vk_physical_device_new_s((bv_instance), \
+ &(struct bs_vk_physical_device_new_info) { \
+ .drm_fd = -1, \
+ ##__VA_ARGS__ \
+ })
+
+struct bs_vk_physical_device * __attribute__((returns_nonnull))
+bs_vk_physical_device_new_s(struct bs_vk_instance *bv_instance,
+ const struct bs_vk_physical_device_new_info *bv_info);
+
+void
+bs_vk_physical_device_destroy(struct bs_vk_physical_device **pself);
+
+// TODO
+#if 0
+// Return true if the mdofier is supported.
+bool
+bs_vk_get_drm_format_modifier_properties(struct bs_vk_physical_device *bv_physical_device,
+ uint64_t drm_format_mod,
+ VkDrmFormatModifierPropertiesEXT *props);
+#endif
+
+// Wrapper for VkDevice.
+//
+// For all struct members that contain a pNext pointer, the pNext is null.
+//
+// WARNING: Some features required by the bs_vk_profiles may not be enabled in the VkDevice. See the
+// body of bs_vk_device_new_s().
+//
+struct bs_vk_device {
+ VkDevice vk_device; // @owned
+ struct bs_vk_physical_device *bv_physical_device; // @not-owned
+ struct bs_vk_instance *bv_instance; // @not-owned
+ const VkAllocationCallbacks *vk_alloc; // @not-owned
+
+ // Enabled device extensions. This contains all extensions in
+ // bs_vk_device_new_info::vk_device_extensions; all extensions required by
+ // bv_instance::bv_profiles; possibly additional extensions required by the bs_vk code.
+ struct bs_strv vk_device_extensions;
+
+ // The default queue is unprotected and supports at least VK_QUEUE_GRAPHICS_BIT
+ // | VK_QUEUE_COMPUTE_BIT. Its queue family index may be the same as the protected queue.
+ VkQueue default_queue;
+ uint32_t default_queue_family_index;
+
+ // Unprotected command pool that supports VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT.
+ //
+ // @owned
+ VkCommandPool default_command_pool;
+
+ // The protected queue supports at least VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT
+ // | VK_QUEUE_PROTECTED_BIT. Its queue family index may be the same as the default queue.
+ VkQueue protected_queue;
+ uint32_t protected_queue_family_index;
+
+ // Protected command pool that supports VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT.
+ //
+ // @owned
+ VkCommandPool protected_command_pool;
+
+ // Each function pointer is non-null if and only if its extension is enabled.
+ PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR;
+ PFN_vkGetMemoryFdPropertiesKHR vkGetMemoryFdPropertiesKHR;
+ PFN_vkGetImageDrmFormatModifierPropertiesEXT vkGetImageDrmFormatModifierPropertiesEXT;
+};
+
+// Parameters for bs_vk_device_new().
+//
+// There's not much here, but we still use a struct so as to follow the pattern of other
+// constructors.
+struct bs_vk_device_new_info {
+ // Required device extensions. Framework may require additional extensions.
+ // Null-terminated.
+ //
+ // @optional
+ // @not-owned
+ const char **vk_device_extensions;
+};
+
+#define bs_vk_device_new(bv_physical_device, ...) \
+ bs_vk_device_new_s((bv_physical_device), \
+ &(struct bs_vk_device_new_info) { \
+ ##__VA_ARGS__ \
+ })
+
+struct bs_vk_device * __attribute__((returns_nonnull))
+bs_vk_device_new_s(struct bs_vk_physical_device *bv_physical_device,
+ const struct bs_vk_device_new_info *bv_info);
+
+void
+bs_vk_device_destroy(struct bs_vk_device **pself);
+
+// Sort by canonical extension name sorting: first KHR, then EXT, then vendor extensions.
+static inline VkResult bs_vkGetMemoryFdKHR(struct bs_vk_device *bv_device, const VkMemoryGetFdInfoKHR* pGetFdInfo, int* pFd) { return bv_device->vkGetMemoryFdKHR(bv_device->vk_device, pGetFdInfo, pFd); }
+static inline VkResult bs_vkGetMemoryFdPropertiesKHR(struct bs_vk_device *bv_device, VkExternalMemoryHandleTypeFlagBits handleType, int fd, VkMemoryFdPropertiesKHR* pMemoryFdProperties) { return bv_device->vkGetMemoryFdPropertiesKHR(bv_device->vk_device, handleType, fd, pMemoryFdProperties); }
+static inline VkResult bs_vkGetImageDrmFormatModifierPropertiesEXT(struct bs_vk_device *bv_device, VkImage image, VkImageDrmFormatModifierPropertiesEXT* pProperties) { return bv_device->vkGetImageDrmFormatModifierPropertiesEXT(bv_device->vk_device, image, pProperties); }
diff --git a/bsdrm/src/module.mk b/bsdrm/src/module.mk
index 3a127eb..7286ab9 100644
--- a/bsdrm/src/module.mk
+++ b/bsdrm/src/module.mk
@@ -22,3 +22,8 @@ CC_STATIC_LIBRARY(libbsdrm.pic.a): \
bsdrm/src/open.o \
bsdrm/src/pipe.o \
bsdrm/src/strv.o
+
+ifeq ($(USE_VULKAN),1)
+CC_STATIC_LIBRARY(libbsdrm.pic.a): \
+ bsdrm/src/vk.o
+endif
diff --git a/bsdrm/src/vk.c b/bsdrm/src/vk.c
new file mode 100644
index 0000000..0ec3e8f
--- /dev/null
+++ b/bsdrm/src/vk.c
@@ -0,0 +1,1006 @@
+/*
+ * Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <inttypes.h>
+
+#include "bs_vk.h"
+
+#define BS_VK_VERSION_1_1 VK_MAKE_API_VERSION(0, 1, 1, 0)
+#define BS_VK_VERSION_1_2 VK_MAKE_API_VERSION(0, 1, 2, 0)
+
+// TODO(wip)
+#if 0
+static bool
+str_has_prefix(const char *s, const char *prefix)
+{
+ return strncmp(s, prefix, strlen(prefix) + 1) == 0;
+}
+
+// Return NULL of i is out of range.
+static char *
+argv_pop_i(int *restrict argc, char **argv, int i)
+{
+ // argv must be null-terminated
+ assert(argv[*argc] == NULL);
+
+ if (i < 0 || i >= *argc)
+ return NULL;
+
+ char *a = argv[i];
+
+ for (int j = i; j < *argc; ++j) {
+ argv[j] = argv[j + 1];
+ }
+
+ *argc -= 1;
+
+ return a;
+}
+
+// Return true if option is found.
+static bool
+argv_pop_opt(int *restrict argc, char **argv,
+ const char *opt_short,
+ const char *opt_long,
+ const char *opt_flags,
+ char *opt_value);
+#endif
+
+#define die_version(name, actual, required) \
+ die_version_loc(__func__, __FILE__, __LINE__, (name), (actual), (required))
+
+static void
+die_version_loc(const char *func, const char *file, int line,
+ const char *name, uint32_t actual, uint32_t required)
+{
+ fprintf(stderr, "ERROR: required %s ", name);
+ bs_vk_version_fprint(stderr, required);
+ fprintf(stderr, ", but found ");
+ bs_vk_version_fprint(stderr, actual);
+ fprintf(stderr, "\n");
+ exit(EXIT_FAILURE);
+}
+
+void
+bs_vk_parse_profiles(const char *s, bs_vk_profile_flags_t *bv_profiles)
+{
+ // The base profile must always be supported
+ *bv_profiles = BS_VK_PROFILE_1_1;
+
+ if (!s)
+ return;
+
+ char *t = xstrdup(s);
+ char *u = t;
+ char *state = NULL;
+ const char *value;
+
+ while ((value = strtok_r(u, ",", &state))) {
+ u = NULL;
+
+ if (!strcmp(value, "1_1") || !strcmp(value, "1.1")) {
+ *bv_profiles |= BS_VK_PROFILE_1_1;
+ } else if (!strcmp(value, "1_2") || !strcmp(value, "1.2")) {
+ *bv_profiles |= BS_VK_PROFILE_1_2 |
+ BS_VK_PROFILE_1_1;
+ } else if (!strcasecmp(value, "gaming")) {
+ *bv_profiles |= BS_VK_PROFILE_GAMING |
+ BS_VK_PROFILE_1_2 |
+ BS_VK_PROFILE_1_1;
+ } else {
+ fprintf(stderr, "usage error: invalid cros-vk-profile: %s\n", value);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ free(t);
+}
+
+void
+bs_vk_check_result_loc(const char *func, const char *file, int line, VkResult expect,
+ VkResult actual)
+{
+ if (actual == expect)
+ return;
+
+ // In the common case when we expect success, simplify the error message.
+ if (expect == VK_SUCCESS) {
+ bs_debug_print("ERROR", func, file, line, "VkResult(%d)", actual);
+ } else {
+ bs_debug_print("ERROR", func, file, line, "expected VkResult(%d), got VkResult(%d)",
+ expect, actual);
+ }
+
+ exit(EXIT_FAILURE);
+}
+
+bool
+bs_vk_version_satisfies(uint32_t actual, uint32_t required)
+{
+ assert(VK_API_VERSION_VARIANT(actual) == 0);
+ assert(VK_API_VERSION_VARIANT(required) == 0);
+
+ if (VK_API_VERSION_MAJOR(actual) != VK_API_VERSION_MAJOR(required))
+ return false;
+
+ if (VK_API_VERSION_MINOR(actual) >= VK_API_VERSION_MINOR(required))
+ return true;
+
+ return VK_API_VERSION_PATCH(actual) >= VK_API_VERSION_PATCH(required);
+}
+
+uint32_t
+bs_vk_version_max(uint32_t a, uint32_t b)
+{
+ assert(VK_API_VERSION_VARIANT(a) == 0);
+ assert(VK_API_VERSION_VARIANT(b) == 0);
+
+ if (a >= b)
+ return a;
+ else
+ return b;
+}
+
+void
+bs_vk_version_fprint(FILE *file, uint32_t v)
+{
+ uint32_t variant = VK_API_VERSION_VARIANT(v);
+
+ fprintf(file, "%u.%u.%u",
+ VK_API_VERSION_MAJOR(v),
+ VK_API_VERSION_MINOR(v),
+ VK_API_VERSION_PATCH(v));
+
+ if (variant != 0)
+ fprintf(file, " (variant %u)", variant);
+
+}
+
+void
+bs_vk_chain_add(void *parent, void *child)
+{
+ VkBaseOutStructure *p = parent;
+ VkBaseOutStructure *c = child;
+
+ c->pNext = p->pNext;
+ p->pNext = c;
+}
+
+void
+bs_vk_chain_break(void *root)
+{
+ VkBaseOutStructure *s = root;
+
+ while (s->pNext) {
+ VkBaseOutStructure *next = s->pNext;
+ s->pNext = NULL;
+ s = next;
+ }
+}
+
+static bool
+has_ext_prop(const char *name,
+ uint32_t ext_prop_count,
+ const VkExtensionProperties *ext_props)
+{
+ for (uint32_t i = 0; i < ext_prop_count; ++i) {
+ if (!strcmp(name, ext_props[i].extensionName)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void
+require_ext_props(const struct bs_strv *required,
+ uint32_t ext_prop_count,
+ const VkExtensionProperties *ext_props)
+{
+ for (size_t i = 0; i < required->len; ++i) {
+ if (!has_ext_prop(required->data[i], ext_prop_count, ext_props)) {
+ bs_debug_error("missing extension %s\n", required->data[i]);
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+struct bs_vk_instance *
+bs_vk_instance_new_s(const struct bs_vk_instance_new_info *bv_info)
+{
+ VkResult r;
+
+ struct bs_vk_instance *self = xcalloc(sizeof(*self), 1);
+
+ self->vk_alloc = bv_info->vk_alloc;
+
+ self->bv_profiles = bv_info->bv_profiles;
+ if (self->bv_profiles == 0)
+ self->bv_profiles = BS_VK_PROFILE_1_1;
+
+ uint32_t vk_instance_version_required = 0;
+
+ if (bv_info->bv_profiles & BS_VK_PROFILE_1_2) {
+ vk_instance_version_required = BS_VK_VERSION_1_2;
+ self->vk_api_version = BS_VK_VERSION_1_2;
+ } else if (bv_info->bv_profiles & BS_VK_PROFILE_1_1) {
+ vk_instance_version_required = BS_VK_VERSION_1_1;
+ self->vk_api_version = BS_VK_VERSION_1_1;
+ } else {
+ assert(0);
+ }
+
+ if (bv_info->vk_instance_version) {
+ vk_instance_version_required = bs_vk_version_max(vk_instance_version_required,
+ bv_info->vk_instance_version);
+ }
+
+ if (bv_info->vk_api_version) {
+ self->vk_api_version = bs_vk_version_max(self->vk_api_version,
+ bv_info->vk_api_version);
+ }
+
+ r = vkEnumerateInstanceVersion(&self->vk_instance_version);
+ bs_vk_check(r);
+
+ if (!bs_vk_version_satisfies(self->vk_instance_version, vk_instance_version_required)) {
+ die_version("VkInstance version",
+ self->vk_instance_version, vk_instance_version_required);
+ }
+
+ bs_strv_init(&self->vk_instance_extensions);
+ bs_strv_concat(&self->vk_instance_extensions, bv_info->vk_instance_extensions);
+
+ uint32_t ext_prop_count;
+ r = vkEnumerateInstanceExtensionProperties(NULL, &ext_prop_count, NULL);
+ bs_vk_check(r);
+
+ VkExtensionProperties ext_props[ext_prop_count];
+ r = vkEnumerateInstanceExtensionProperties(NULL, &ext_prop_count, ext_props);
+ bs_vk_check(r);
+
+ require_ext_props(&self->vk_instance_extensions, ext_prop_count, ext_props);
+
+ VkInstanceCreateInfo instance_info = {
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .pApplicationInfo = &(VkApplicationInfo) {
+ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
+ .apiVersion = self->vk_api_version,
+ },
+ .enabledExtensionCount = self->vk_instance_extensions.len,
+ .ppEnabledExtensionNames = bs_strv_const(&self->vk_instance_extensions),
+ };
+
+ r = vkCreateInstance(&instance_info, self->vk_alloc, &self->vk_instance);
+ bs_vk_check(r);
+
+ return self;
+}
+
+void
+bs_vk_instance_destroy(struct bs_vk_instance **pself)
+{
+ struct bs_vk_instance *self = *pself;
+ if (!self)
+ return;
+
+ bs_strv_finish(&self->vk_instance_extensions);
+
+ if (self->vk_instance)
+ vkDestroyInstance(self->vk_instance, self->vk_alloc);
+
+ free(self);
+ *pself = NULL;
+}
+
+static void
+drm_get_pci_id(int drm_fd, uint16_t *vendor_id, uint16_t *device_id)
+{
+ drmDevice *drm_device;
+ if (drmGetDevice2(drm_fd, 0, &drm_device)) {
+ bs_debug_error("drmGetDevice2 failed");
+ exit(EXIT_FAILURE);
+ }
+
+ if (drm_device->bustype != DRM_BUS_PCI) {
+ bs_debug_error("DRM device bus is not PCI");
+ exit(EXIT_FAILURE);
+ }
+
+ *vendor_id = drm_device->deviceinfo.pci->vendor_id;
+ *device_id = drm_device->deviceinfo.pci->device_id;
+
+ drmFreeDevice(&drm_device);
+}
+
+static uint32_t
+find_queue_family_index2(VkQueueFlags flags,
+ uint32_t props_count,
+ const VkQueueFamilyProperties2 *props)
+{
+ for (uint32_t i = 0; i < props_count; ++i) {
+ if ((props[i].queueFamilyProperties.queueFlags & flags) == flags) {
+ return i;
+ }
+ }
+
+ return UINT32_MAX;
+}
+
+static VkPhysicalDevice
+find_physical_device(struct bs_vk_instance *bv_instance,
+ uint16_t pci_vendor_id,
+ uint16_t pci_device_id)
+
+{
+ VkInstance vk_instance = bv_instance->vk_instance;
+ VkPhysicalDevice vk_phys_dev = VK_NULL_HANDLE;
+ VkResult r;
+
+ uint32_t phys_dev_count;
+ r = vkEnumeratePhysicalDevices(vk_instance, &phys_dev_count, NULL);
+ bs_vk_check(r);
+
+ VkPhysicalDevice phys_devs[phys_dev_count];
+ r = vkEnumeratePhysicalDevices(vk_instance, &phys_dev_count, phys_devs);
+ bs_vk_check(r);
+
+ for (uint32_t i = 0; i < phys_dev_count; ++i) {
+ VkPhysicalDeviceProperties props;
+ vkGetPhysicalDeviceProperties(phys_devs[i], &props);
+
+ if ((props.vendorID >> 16) != 0 ||
+ props.vendorID != pci_vendor_id)
+ continue;
+
+ if ((props.deviceID >> 16) != 0 ||
+ props.deviceID != pci_device_id)
+ continue;
+
+ if (vk_phys_dev != VK_NULL_HANDLE) {
+ bs_debug_error("found multiple VkPhysicalDevice that match pci_id "
+ "%04x:%04x", pci_vendor_id, pci_device_id);
+ exit(EXIT_FAILURE);
+ }
+
+ vk_phys_dev = phys_devs[i];
+ }
+
+ return vk_phys_dev;
+}
+
+static void
+add_profile_extensions(struct bs_strv *exts, bs_vk_profile_flags_t bv_profiles)
+{
+ if (bv_profiles & BS_VK_PROFILE_1_1) {
+ bs_strv_concat(exts, (const char *[]) {
+ "VK_KHR_external_semaphore_fd",
+ "VK_KHR_image_format_list",
+ "VK_KHR_sampler_ycbcr_conversion",
+
+ "VK_EXT_external_memory_dma_buf",
+ "VK_KHR_external_memory_fd",
+ "VK_EXT_image_drm_format_modifier",
+ "VK_EXT_queue_family_foreign",
+
+ // The partner doc requires this, but the extension hasn't been published yet.
+ // "VK_GOOGLE_external_memory_foreign_host_access",
+
+ NULL,
+ });
+ }
+
+ if (bv_profiles & BS_VK_PROFILE_GAMING) {
+ bs_strv_concat(exts, (const char *[]) {
+ "VK_KHR_create_renderpass2",
+ "VK_KHR_depth_stencil_resolve",
+ "VK_KHR_driver_properties",
+ "VK_KHR_sampler_mirror_clamp_to_edge",
+
+ "VK_EXT_custom_border_color",
+ "VK_EXT_depth_clip_enable",
+ "VK_EXT_host_query_reset",
+ "VK_EXT_memory_priority",
+ "VK_EXT_shader_stencil_export",
+ "VK_EXT_shader_viewport_index_layer",
+ "VK_EXT_transform_feedback",
+ "VK_EXT_vertex_attribute_divisor",
+
+ NULL,
+ });
+ }
+}
+
+static uint32_t
+find_queue_family_index(VkQueueFlags flags,
+ uint32_t props_count,
+ const VkQueueFamilyProperties *props)
+{
+ for (uint32_t i = 0; i < props_count; ++i) {
+ if ((props[i].queueFlags & flags) == flags) {
+ return i;
+ }
+ }
+
+ return UINT32_MAX;
+}
+
+struct bs_vk_physical_device *
+bs_vk_physical_device_new_s(struct bs_vk_instance *bv_instance,
+ const struct bs_vk_physical_device_new_info *bv_info)
+{
+ bs_vk_profile_flags_t bv_profiles = bv_instance->bv_profiles;
+ VkResult r;
+
+ struct bs_vk_physical_device *self = xcalloc(sizeof(*self), 1);
+ self->bv_instance = bv_instance;
+
+ assert(bv_info->drm_fd >= 0);
+ self->drm_fd = bv_info->drm_fd;
+
+ assert(bv_info->gbm_device != NULL);
+ self->gbm_device = bv_info->gbm_device;
+
+ uint16_t pci_vendor_id;
+ uint16_t pci_device_id;
+ drm_get_pci_id(self->drm_fd, &pci_vendor_id, &pci_device_id);
+
+ self->vk_physical_device = find_physical_device(bv_instance, pci_vendor_id, pci_device_id);
+ if (self->vk_physical_device == VK_NULL_HANDLE) {
+ bs_debug_error("found no VkPhysicalDevice with pci_id %04x:%04x",
+ pci_vendor_id, pci_device_id);
+ exit(EXIT_FAILURE);
+ }
+
+ // Query VkExtensionProperties
+
+ uint32_t ext_prop_count;
+ r = vkEnumerateDeviceExtensionProperties(self->vk_physical_device, NULL, &ext_prop_count,
+ NULL);
+ bs_vk_check(r);
+
+ VkExtensionProperties ext_props[ext_prop_count];
+ r = vkEnumerateDeviceExtensionProperties(self->vk_physical_device, NULL, &ext_prop_count,
+ ext_props);
+ bs_vk_check(r);
+
+ // Copy VkExtensionProperties into bs_vk_physical_device
+
+ bs_strv_init(&self->vk_device_extensions_avail);
+ for (uint32_t i = 0; i < ext_prop_count; ++i) {
+ bs_strv_append(&self->vk_device_extensions_avail, ext_props[i].extensionName);
+ }
+
+ const struct bs_strv *exts_avail = &self->vk_device_extensions_avail;
+
+ // Check extension requirements. We must do this before we chain extension structs into the
+ // other queries.
+ //
+ // We continue to require promoted extensions as a convenience that allows apps and tests to
+ // use the original extension tokens.
+
+ struct bs_strv exts_required = BS_STRV_INIT;
+ bs_strv_concat(&exts_required, bv_info->vk_device_extensions);
+ add_profile_extensions(&exts_required, bv_profiles);
+ require_ext_props(&exts_required, ext_prop_count, ext_props);
+
+ // Query VkPhysicalDeviceProperties2 and VkPhysicalDeviceFeatures2.
+ //
+ // Keep extension structs sorted in the canonical order. First core structs, then KHR, then
+ // EXT, then vendor extension structs, sorted alphabetically within each group.
+
+ assert(bs_vk_version_satisfies(bv_instance->vk_api_version, BS_VK_VERSION_1_1));
+
+ // Provided by VULKAN_1_1
+ VkPhysicalDeviceProperties2 vk_properties2 = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
+ };
+
+ // Provided by VULKAN_1_1
+ VkPhysicalDeviceFeatures2 vk_features2 = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
+ };
+
+ // Provided by VULKAN_1_1
+ self->vk_protected_memory_features = (VkPhysicalDeviceProtectedMemoryFeatures) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
+ };
+
+ bs_vk_chain_add(&vk_features2, &self->vk_protected_memory_features);
+
+ // Provided by VULKAN_1_1
+ self->vk_shader_draw_parameters_features = (VkPhysicalDeviceShaderDrawParametersFeatures) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES,
+ };
+
+ bs_vk_chain_add(&vk_features2, &self->vk_shader_draw_parameters_features);
+
+ // Provided by VULKAN_1_2
+ self->vk_vulkan_1_1_properties = (VkPhysicalDeviceVulkan11Properties) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES,
+ };
+
+ // Provided by VULKAN_1_2
+ self->vk_vulkan_1_1_features = (VkPhysicalDeviceVulkan11Features) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
+ };
+
+ // Provided by VULKAN_1_2
+ self->vk_vulkan_1_2_properties = (VkPhysicalDeviceVulkan12Properties) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES,
+ };
+
+ // Provided by VULKAN_1_2
+ self->vk_vulkan_1_2_features = (VkPhysicalDeviceVulkan12Features) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
+ };
+
+ if (bs_vk_version_satisfies(bv_instance->vk_api_version, BS_VK_VERSION_1_2)) {
+ bs_vk_chain_add(&vk_properties2, &self->vk_vulkan_1_1_properties);
+ bs_vk_chain_add(&vk_features2, &self->vk_vulkan_1_1_features);
+
+ bs_vk_chain_add(&vk_properties2, &self->vk_vulkan_1_2_properties);
+ bs_vk_chain_add(&vk_features2, &self->vk_vulkan_1_2_features);
+ }
+
+ // Provided by VK_KHR_depth_stencil_resolve
+ self->vk_depth_stencil_resolve_properties = (VkPhysicalDeviceDepthStencilResolvePropertiesKHR) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES_KHR,
+ };
+
+ if (bs_strv_find(exts_avail, "VK_KHR_depth_stencil_resolve")) {
+ bs_vk_chain_add(&vk_properties2, &self->vk_depth_stencil_resolve_properties);
+ }
+
+ // Provided by VK_KHR_driver_properties
+ self->vk_driver_properties = (VkPhysicalDeviceDriverPropertiesKHR) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES_KHR,
+ };
+
+ if (bs_strv_find(exts_avail, "VK_KHR_driver_properties")) {
+ bs_vk_chain_add(&vk_properties2, &self->vk_driver_properties);
+ }
+
+ // Provided by VK_EXT_custom_border_color
+ self->vk_custom_border_color_properties = (VkPhysicalDeviceCustomBorderColorPropertiesEXT) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT,
+ };
+
+ // Provided by VK_EXT_custom_border_color
+ self->vk_custom_border_color_features = (VkPhysicalDeviceCustomBorderColorFeaturesEXT) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_FEATURES_EXT,
+ };
+
+ if (bs_strv_find(exts_avail, "VK_EXT_custom_border_color")) {
+ bs_vk_chain_add(&vk_properties2, &self->vk_custom_border_color_properties);
+ bs_vk_chain_add(&vk_features2, &self->vk_custom_border_color_features);
+ }
+
+ // Provided by VK_EXT_depth_clip_enable
+ self->vk_depth_clip_enable_features = (VkPhysicalDeviceDepthClipEnableFeaturesEXT) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_ENABLE_FEATURES_EXT,
+ };
+
+ if (bs_strv_find(exts_avail, "VK_EXT_depth_clip_enable")) {
+ bs_vk_chain_add(&vk_features2, &self->vk_depth_clip_enable_features);
+ }
+
+ // Provided by VK_EXT_host_query_reset
+ self->vk_host_query_reset_features = (VkPhysicalDeviceHostQueryResetFeaturesEXT) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES_EXT,
+ };
+
+ if (bs_strv_find(exts_avail, "VK_EXT_host_query_reset")) {
+ bs_vk_chain_add(&vk_features2, &self->vk_host_query_reset_features);
+ }
+
+ // Provided by VK_EXT_memory_priority
+ self->vk_memory_priority_features = (VkPhysicalDeviceMemoryPriorityFeaturesEXT) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PRIORITY_FEATURES_EXT,
+ };
+
+ if (bs_strv_find(exts_avail, "VK_EXT_memory_priority")) {
+ bs_vk_chain_add(&vk_features2, &self->vk_memory_priority_features);
+ }
+
+ // Provided by VK_EXT_transform_feedback
+ self->vk_transform_feedback_properties = (VkPhysicalDeviceTransformFeedbackPropertiesEXT) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT,
+ };
+
+ // Provided by VK_EXT_transform_feedback
+ self->vk_transform_feedback_features = (VkPhysicalDeviceTransformFeedbackFeaturesEXT) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_FEATURES_EXT,
+ };
+
+ if (bs_strv_find(exts_avail, "VK_EXT_transform_feedback")) {
+ bs_vk_chain_add(&vk_properties2, &self->vk_transform_feedback_properties);
+ bs_vk_chain_add(&vk_features2, &self->vk_transform_feedback_features);
+ }
+
+ // Provided by VK_EXT_vertex_attribute_divisor
+ self->vk_vertex_attribute_divisor_properties = (VkPhysicalDeviceVertexAttributeDivisorPropertiesEXT) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_PROPERTIES_EXT,
+ };
+
+ // Provided by VK_EXT_vertex_attribute_divisor
+ self->vk_vertex_attribute_divisor_features = (VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT) {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_EXT,
+ };
+
+ if (bs_strv_find(exts_avail, "VK_EXT_vertex_attribute_divisor")) {
+ bs_vk_chain_add(&vk_properties2, &self->vk_vertex_attribute_divisor_properties);
+ bs_vk_chain_add(&vk_features2, &self->vk_vertex_attribute_divisor_features);
+ }
+
+ vkGetPhysicalDeviceProperties2(self->vk_physical_device, &vk_properties2);
+ vkGetPhysicalDeviceFeatures2(self->vk_physical_device, &vk_features2);
+
+ self->vk_properties = vk_properties2.properties;
+ self->vk_features = vk_features2.features;
+
+ // Because the doc comment for bs_vk_physical_device says so.
+ bs_vk_chain_break(&vk_properties2);
+ bs_vk_chain_break(&vk_features2);
+
+ // Query VkPhysicalDeviceMemoryProperties2
+
+ VkPhysicalDeviceMemoryProperties2 mem_props2 = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2,
+ };
+
+ vkGetPhysicalDeviceMemoryProperties2(self->vk_physical_device, &mem_props2);
+
+ self->vk_memory_properties = mem_props2.memoryProperties;
+
+ // Query VkQueueFamilyProperties2
+
+ vkGetPhysicalDeviceQueueFamilyProperties2(self->vk_physical_device,
+ &self->vk_queue_family_count, NULL);
+
+ VkQueueFamilyProperties2 queue_family_props2[self->vk_queue_family_count];
+
+ for (uint32_t i = 0; i < self->vk_queue_family_count; ++i) {
+ queue_family_props2[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
+ queue_family_props2[i].pNext = NULL;
+ }
+
+ vkGetPhysicalDeviceQueueFamilyProperties2(self->vk_physical_device,
+ &self->vk_queue_family_count,
+ queue_family_props2);
+
+ // Copy VkQueueFamilyProperties2 into bs_vk_physical_device
+
+ self->vk_queue_family_properties = xcalloc(self->vk_queue_family_count,
+ sizeof(self->vk_queue_family_properties[0]));
+
+ for (uint32_t i = 0; i < self->vk_queue_family_count; ++i) {
+ // Because the doc comment for bs_vk_physical_device says so.
+ bs_vk_chain_break(&queue_family_props2[i]);
+
+ self->vk_queue_family_properties[i] =
+ queue_family_props2[i].queueFamilyProperties;
+ }
+
+ // Start checking requirements against queries
+
+ if (!bs_vk_version_satisfies(self->vk_properties.apiVersion, bv_instance->vk_api_version)) {
+ die_version("VkPhysicalDeviceProperties::apiVersion",
+ self->vk_properties.apiVersion, bv_instance->vk_api_version);
+ }
+
+ #define REQUIRE_FEATURE(_struct, feature) ({ \
+ if (!self->_struct.feature) { \
+ bs_debug_error("missing Vulkan feature '" #feature "'"); \
+ exit(EXIT_FAILURE); \
+ } \
+ })
+
+ if (bv_profiles & BS_VK_PROFILE_GAMING) {
+ // We have already verified that the device supports the required extensions.
+ REQUIRE_FEATURE(vk_features, robustBufferAccess);
+ REQUIRE_FEATURE(vk_features, fullDrawIndexUint32);
+ REQUIRE_FEATURE(vk_features, imageCubeArray);
+ REQUIRE_FEATURE(vk_features, independentBlend);
+ REQUIRE_FEATURE(vk_features, geometryShader);
+ REQUIRE_FEATURE(vk_features, tessellationShader);
+ REQUIRE_FEATURE(vk_features, sampleRateShading);
+ REQUIRE_FEATURE(vk_features, dualSrcBlend);
+ REQUIRE_FEATURE(vk_features, logicOp);
+ REQUIRE_FEATURE(vk_features, multiDrawIndirect);
+ REQUIRE_FEATURE(vk_features, drawIndirectFirstInstance);
+ REQUIRE_FEATURE(vk_features, depthClamp);
+ REQUIRE_FEATURE(vk_features, depthBiasClamp);
+ REQUIRE_FEATURE(vk_features, fillModeNonSolid);
+ REQUIRE_FEATURE(vk_features, multiViewport);
+ REQUIRE_FEATURE(vk_features, samplerAnisotropy);
+ REQUIRE_FEATURE(vk_features, textureCompressionBC);
+ REQUIRE_FEATURE(vk_features, occlusionQueryPrecise);
+ REQUIRE_FEATURE(vk_features, pipelineStatisticsQuery);
+ REQUIRE_FEATURE(vk_features, fragmentStoresAndAtomics);
+ REQUIRE_FEATURE(vk_features, shaderImageGatherExtended);
+ REQUIRE_FEATURE(vk_features, shaderStorageImageExtendedFormats);
+ REQUIRE_FEATURE(vk_features, shaderStorageImageWriteWithoutFormat);
+ REQUIRE_FEATURE(vk_features, shaderClipDistance);
+ REQUIRE_FEATURE(vk_features, shaderCullDistance);
+ REQUIRE_FEATURE(vk_features, variableMultisampleRate);
+
+ REQUIRE_FEATURE(vk_custom_border_color_features, customBorderColorWithoutFormat);
+ REQUIRE_FEATURE(vk_shader_draw_parameters_features, shaderDrawParameters);
+ REQUIRE_FEATURE(vk_transform_feedback_features, transformFeedback);
+ REQUIRE_FEATURE(vk_transform_feedback_features, geometryStreams);
+ REQUIRE_FEATURE(vk_vertex_attribute_divisor_features, vertexAttributeInstanceRateDivisor);
+ }
+
+ if (bv_profiles & BS_VK_PROFILE_PROTECTED) {
+ REQUIRE_FEATURE(vk_protected_memory_features, protectedMemory);
+ }
+
+ #undef REQUIRE_FEATURE
+
+ // Check VkQueueFamilyProperties2
+
+ // We require a graphics queue, but the Vulkan 1.2 spec does not. For example, the
+ // device may expose only compute queues.
+ //
+ // From Vulkan 1.2.184 > VkQueueFlagBits.
+ //
+ // If an implementation exposes any queue family that supports graphics
+ // operations, at least one queue family of at least one physical device exposed
+ // by the implementation must support both graphics and compute operations.
+ //
+ // Furthermore, if the protected memory physical device feature is supported, then
+ // at least one queue family of at least one physical device exposed by the
+ // implementation must support graphics operations, compute operations, and
+ // protected memory operations.
+ //
+ // All commands that are allowed on a queue that supports transfer operations are
+ // also allowed on a queue that supports either graphics or compute operations.
+ // Thus, if the capabilities of a queue family include VK_QUEUE_GRAPHICS_BIT or
+ // VK_QUEUE_COMPUTE_BIT, then reporting the VK_QUEUE_TRANSFER_BIT capability
+ // separately for that queue family is optional.
+ //
+ // Therefore, if there exists at least one graphics queue, then there exists a queue
+ // (possibly a different queue) with everything we want.
+ if (find_queue_family_index2(VK_QUEUE_GRAPHICS_BIT,
+ self->vk_queue_family_count,
+ queue_family_props2) == UINT32_MAX) {
+ bs_debug_error("no VkQueue supports VK_QUEUE_GRAPHICS_BIT");
+ exit(EXIT_FAILURE);
+ }
+
+ if (bv_profiles & BS_VK_PROFILE_PROTECTED) {
+ if (find_queue_family_index2(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT,
+ self->vk_queue_family_count,
+ queue_family_props2) == UINT32_MAX) {
+ bs_debug_error("no VkQueue supports "
+ "VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ return self;
+}
+
+void
+bs_vk_physical_device_destroy(struct bs_vk_physical_device **pself)
+{
+ struct bs_vk_physical_device *self = *pself;
+ if (!self)
+ return;
+
+ bs_strv_finish(&self->vk_device_extensions_avail);
+ free(self->vk_queue_family_properties);
+ free(self);
+
+ *pself = NULL;
+}
+
+// TODO
+#if 0
+bool
+bs_vk_get_drm_format_modifier_properties(struct bs_vk_physical_device *bv_physical_device,
+ uint64_t drm_format_mod,
+ VkDrmFormatModifierPropertiesEXT *out_props)
+{
+ VkPhysicalDevice vk_physical_device = bv_physical_device->vk_physical_device;
+ VkResult r;
+
+ VkFormatProperties2 format_props2 = {
+ .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2,
+ };
+
+ VkDrmFormatModifierPropertiesListEXT mod_props_list = {
+ .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT,
+ };
+ bs_vk_chain_add(&format_props2, &mod_props_list);
+
+ vkGetPhysicalDeviceFormatProperties2(vk_phys_dev, &format_props2);
+
+ if (mod_props.drmFormatModifier == 0)
+ return false;
+
+ mod_props_list.pDrmFormatModifierProperties = alloca(mod_props.drmFormatModifierCount *
+ sizeof(mod_props.pDrmFormatModifierProperties[0]));
+
+ vkGetPhysicalDeviceFormatProperties2(vk_phys_dev, &format_props2);
+
+ for (uint32_t i = 0; i < mod_props_list.drmFormatModifierCount; ++i) {
+ const VkDrmFormatModifierPropertiesEXT *mod_props =
+ &mod_props_list.pDrmFormatModifierProperties[i];
+
+ if (mod_props->drmFormatModifier == drm_format_mod) {
+ *out_props = *mod_props;
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif
+
+
+struct bs_vk_device *
+bs_vk_device_new_s(struct bs_vk_physical_device *bv_physical_device,
+ const struct bs_vk_device_new_info *bv_info)
+{
+ struct bs_vk_instance *bv_instance = bv_physical_device->bv_instance;
+ bs_vk_profile_flags_t bv_profiles = bv_instance->bv_profiles;
+ VkPhysicalDevice vk_physical_device = bv_physical_device->vk_physical_device;
+ const VkAllocationCallbacks *vk_alloc = bv_physical_device->bv_instance->vk_alloc;
+ VkResult r;
+
+ struct bs_vk_device *self = xcalloc(sizeof(*self), 1);
+ self->bv_instance = bv_instance;
+ self->bv_physical_device = bv_physical_device;
+ self->vk_alloc = vk_alloc;
+
+ bs_strv_init(&self->vk_device_extensions);
+ bs_strv_concat(&self->vk_device_extensions, bv_info->vk_device_extensions);
+ add_profile_extensions(&self->vk_device_extensions, bv_profiles);
+
+ // In the future, we may wish to create a VkDevice having a distinct unprotected general
+ // queue and protected general queue. For now, though, for simplicity, let the general queue
+ // and the protected queue be the same queue.
+ VkQueueFlags queue_flags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT;
+ if (bv_profiles & BS_VK_PROFILE_PROTECTED)
+ queue_flags |= VK_QUEUE_PROTECTED_BIT;
+
+ self->default_queue_family_index =
+ find_queue_family_index(queue_flags,
+ bv_physical_device->vk_queue_family_count,
+ bv_physical_device->vk_queue_family_properties);
+ self->protected_queue_family_index = self->default_queue_family_index;
+
+ if (self->default_queue_family_index == UINT32_MAX) {
+ bs_debug_error("unable to find VkQueue with VkQueueFlags(0x%x)", queue_flags);
+ exit(EXIT_FAILURE);
+ }
+
+ VkDeviceQueueCreateInfo device_queue_create_infos[] = {
+ // Default, unprotected queue
+ {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .flags = 0,
+ .queueFamilyIndex = self->default_queue_family_index,
+ .queueCount = 1,
+ .pQueuePriorities = (float[]) { 1.0f },
+ },
+ // Protected queue. Only used if BS_VK_PROFILE_PROTECTED.
+ {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ .flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT,
+ .queueFamilyIndex = self->protected_queue_family_index,
+ .queueCount = 1,
+ .pQueuePriorities = (float[]) { 1.0f },
+ },
+ };
+
+ VkDeviceCreateInfo device_create_info = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ .queueCreateInfoCount = 0, // set later
+ .pQueueCreateInfos = device_queue_create_infos,
+ .enabledExtensionCount = self->vk_device_extensions.len,
+ .ppEnabledExtensionNames = bs_strv_const(&self->vk_device_extensions),
+ };
+
+ if (bv_profiles & BS_VK_PROFILE_PROTECTED) {
+ device_create_info.queueCreateInfoCount = 2;
+ } else {
+ device_create_info.queueCreateInfoCount = 1;
+ }
+
+ VkPhysicalDeviceProtectedMemoryFeatures protected_features = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
+ .protectedMemory = true,
+ };
+
+ if (bv_profiles & BS_VK_PROFILE_PROTECTED) {
+ bs_vk_chain_add(&device_create_info, &protected_features);
+ }
+
+ r = vkCreateDevice(vk_physical_device, &device_create_info,
+ vk_alloc, &self->vk_device);
+ bs_vk_check(r);
+
+ #define GET_DEVICE_FUNC(name) \
+ self->name = (PFN_##name) vkGetDeviceProcAddr(self->vk_device, #name)
+
+ if (bs_strv_find(&self->vk_device_extensions, "VK_EXT_external_memory_fd")) {
+ GET_DEVICE_FUNC(vkGetMemoryFdPropertiesKHR);
+ GET_DEVICE_FUNC(vkGetMemoryFdKHR);
+ }
+
+ if (bs_strv_find(&self->vk_device_extensions, "VK_EXT_image_drm_format_modifier")) {
+ GET_DEVICE_FUNC(vkGetImageDrmFormatModifierPropertiesEXT);
+ }
+
+ #undef GET_DEVICE_FUNC
+
+ VkDeviceQueueInfo2 default_queue_info2 = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2,
+ .flags = device_queue_create_infos[0].flags,
+ .queueFamilyIndex = self->default_queue_family_index,
+ .queueIndex = 0,
+ };
+
+ vkGetDeviceQueue2(self->vk_device, &default_queue_info2, &self->default_queue);
+
+ VkCommandPoolCreateInfo default_command_pool_create_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+ .queueFamilyIndex = self->default_queue_family_index,
+ };
+
+ r = vkCreateCommandPool(self->vk_device, &default_command_pool_create_info, vk_alloc,
+ &self->default_command_pool);
+ bs_vk_check(r);
+
+ if (bv_profiles & BS_VK_PROFILE_PROTECTED) {
+ VkDeviceQueueInfo2 protected_queue_info2 = {
+ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2,
+ .flags = device_queue_create_infos[1].flags,
+ .queueFamilyIndex = self->protected_queue_family_index,
+ .queueIndex = 0,
+ };
+
+ vkGetDeviceQueue2(self->vk_device, &protected_queue_info2, &self->protected_queue);
+
+ VkCommandPoolCreateInfo protected_command_pool_create_info = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .flags = default_command_pool_create_info.flags |
+ VK_COMMAND_POOL_CREATE_PROTECTED_BIT,
+ .queueFamilyIndex = self->protected_queue_family_index,
+ };
+
+ r = vkCreateCommandPool(self->vk_device, &protected_command_pool_create_info,
+ vk_alloc, &self->protected_command_pool);
+ bs_vk_check(r);
+ }
+
+ return self;
+}
+
+void
+bs_vk_device_destroy(struct bs_vk_device **pself)
+{
+ struct bs_vk_device *self = *pself;
+ if (!self)
+ return;
+
+ bs_vk_profile_flags_t bv_profiles = self->bv_physical_device->bv_instance->bv_profiles;
+
+ vkDestroyCommandPool(self->vk_device, self->default_command_pool, self->vk_alloc);
+
+ if (bv_profiles & BS_VK_PROFILE_PROTECTED) {
+ vkDestroyCommandPool(self->vk_device, self->protected_command_pool, self->vk_alloc);
+ }
+
+ bs_strv_finish(&self->vk_device_extensions);
+ free(self);
+
+ *pself = NULL;
+}
diff --git a/vk_features.c b/vk_features.c
new file mode 100644
index 0000000..2787133
--- /dev/null
+++ b/vk_features.c
@@ -0,0 +1,491 @@
+/*
+ * Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// TODO: Describe test
+// TODO: Experiment with abbreviations.
+// TODO: Test that DRM_FORMAT_MOD_LINEAR == VK_IMAGE_TILING_LINEAR. Does dEQP already do it?
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "bs_drm.h"
+#include "bs_vk.h"
+
+// Set this to false when the test should fail. Even when false, the test may continue to test other
+// formats in order to emit comprehensive error messages.
+static bool total_result = true;
+
+struct format_req {
+ VkFormat vk_format;
+ bool require_linear;
+ VkMemoryPropertyFlags vk_memory_props;
+ VkFormatFeatureFlags vk_format_features;
+ VkImageUsageFlags vk_image_usage;
+ VkImageCreateFlags vk_image_create_flags;
+ uint32_t vk_view_format_count;
+ const VkFormat *vk_view_formats;
+};
+
+const VkFormatFeatureFlags single_planar_format_features =
+ VK_FORMAT_FEATURE_BLIT_DST_BIT |
+ VK_FORMAT_FEATURE_BLIT_SRC_BIT |
+ VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT |
+ VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT |
+ VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
+ VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT |
+ VK_FORMAT_FEATURE_TRANSFER_DST_BIT |
+ VK_FORMAT_FEATURE_TRANSFER_SRC_BIT;
+
+const VkImageUsageFlags single_planar_image_usage =
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT |
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+
+const VkFormatFeatureFlags multi_planar_format_features =
+ VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
+ VK_FORMAT_FEATURE_TRANSFER_SRC_BIT |
+ VK_FORMAT_FEATURE_TRANSFER_DST_BIT |
+ VK_FORMAT_FEATURE_MIDPOINT_CHROMA_SAMPLES_BIT |
+ VK_FORMAT_FEATURE_COSITED_CHROMA_SAMPLES_BIT;
+
+const VkImageUsageFlags multi_planar_image_usage =
+ VK_IMAGE_USAGE_SAMPLED_BIT |
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+
+// Maintain the same order as in the requirements docs.
+const struct format_req format_req_table[] = {
+ {
+ .vk_format = VK_FORMAT_R8_UNORM,
+ .vk_format_features = single_planar_format_features,
+ .vk_image_usage = single_planar_image_usage,
+ },
+ {
+ .vk_format = VK_FORMAT_R8G8_UNORM,
+ .vk_format_features = single_planar_format_features,
+ .vk_image_usage = single_planar_image_usage,
+ },
+ {
+ .vk_format = VK_FORMAT_R5G6B5_UNORM_PACK16,
+ .vk_format_features = single_planar_format_features,
+ .vk_image_usage = single_planar_image_usage,
+ },
+ {
+ .vk_format = VK_FORMAT_R8G8B8A8_UNORM,
+ .require_linear = true,
+ .vk_memory_props = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+ .vk_format_features = single_planar_format_features,
+ .vk_image_usage = single_planar_image_usage,
+ },
+ {
+ .vk_format = VK_FORMAT_B8G8R8A8_UNORM,
+ .vk_format_features = single_planar_format_features,
+ .vk_image_usage = single_planar_image_usage,
+ },
+ {
+ .vk_format = VK_FORMAT_A2R10G10B10_UNORM_PACK32,
+ .vk_format_features = single_planar_format_features,
+ .vk_image_usage = single_planar_image_usage,
+ },
+ {
+ .vk_format = VK_FORMAT_R8G8B8A8_SRGB,
+ .require_linear = true,
+ .vk_memory_props = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+ .vk_format_features = single_planar_format_features,
+ .vk_image_usage = single_planar_image_usage,
+ .vk_image_create_flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
+ .vk_view_format_count = 2,
+ .vk_view_formats = (VkFormat[]) {
+ VK_FORMAT_R8G8B8A8_SRGB,
+ VK_FORMAT_R8G8B8A8_UNORM,
+ },
+ },
+ {
+ .vk_format = VK_FORMAT_B8G8R8A8_SRGB,
+ .require_linear = true,
+ .vk_memory_props = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+ .vk_format_features = single_planar_format_features,
+ .vk_image_usage = single_planar_image_usage,
+ .vk_image_create_flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
+ .vk_view_format_count = 2,
+ .vk_view_formats = (VkFormat[]) {
+ VK_FORMAT_B8G8R8A8_SRGB,
+ VK_FORMAT_B8G8R8A8_UNORM,
+ },
+ },
+ {
+ .vk_format = VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16,
+ .require_linear = true,
+ .vk_format_features = multi_planar_format_features,
+ .vk_image_usage = multi_planar_image_usage,
+ .vk_image_create_flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
+ .vk_view_format_count = 3,
+ .vk_view_formats = (VkFormat[]) {
+ VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16,
+ VK_FORMAT_R10X6_UNORM_PACK16,
+ VK_FORMAT_R10X6G10X6_UNORM_2PACK16,
+ },
+ },
+ {
+ .vk_format = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM,
+ .require_linear = true,
+ .vk_memory_props = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
+ .vk_format_features = multi_planar_format_features,
+ .vk_image_usage = multi_planar_image_usage,
+ .vk_image_create_flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
+ .vk_view_format_count = 2,
+ .vk_view_formats = (VkFormat[]) {
+ VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM,
+ VK_FORMAT_R8_UNORM,
+ },
+ },
+};
+
+static bool
+test_memory_props(struct bs_vk_device *bv_dev,
+ const struct format_req *req,
+ VkImage vk_image)
+{
+ if (req->vk_memory_props == 0)
+ return true;
+
+ const VkPhysicalDeviceMemoryProperties *mem_props =
+ &bv_dev->bv_physical_device->vk_memory_properties;
+ const VkMemoryType *mem_types = mem_props->memoryTypes;
+ VkDevice vk_dev = bv_dev->vk_device;
+
+ VkImageMemoryRequirementsInfo2 image_mem_reqs_info2 = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2,
+ .image = vk_image,
+ };
+
+ VkMemoryRequirements2 mem_req2 = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
+ };
+
+ const VkMemoryRequirements *mem_req = &mem_req2.memoryRequirements;
+
+ vkGetImageMemoryRequirements2(vk_dev, &image_mem_reqs_info2, &mem_req2);
+
+ for_each_bit32(b, mem_req->memoryTypeBits) {
+ if (req->vk_memory_props ==
+ (req->vk_memory_props & mem_types[b].propertyFlags)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// If the format and modifier does not support the requirements, then return false.
+//
+// Failure of this function does not necessarily cause a test failure. For example, when testing
+// format_foo, (format_foo, mod1) may fail the requirements, but (format_foo, mod2) supports the
+// requirements; therefore, the subtest for format_foo succeeds thanks to mod2.
+static bool
+test_format_mod(struct bs_vk_device *bv_dev,
+ const struct format_req *req,
+ VkDrmFormatModifierPropertiesEXT vk_mod_props)
+
+{
+ VkDevice vk_dev = bv_dev->vk_device;
+ VkPhysicalDevice vk_phys_dev = bv_dev->bv_physical_device->vk_physical_device;
+ const VkAllocationCallbacks *vk_alloc = bv_dev->vk_alloc;
+ uint64_t mod = vk_mod_props.drmFormatModifier;
+ VkResult r;
+ bool pass = true;
+
+ // The test does not correctly manage disjoint images.
+ assert(!(req->vk_format_features & VK_FORMAT_FEATURE_DISJOINT_BIT));
+ assert(!(req->vk_image_create_flags & VK_IMAGE_CREATE_DISJOINT_BIT));
+
+ if (vk_mod_props.drmFormatModifierPlaneCount == 0) {
+ bs_debug_error("drmFormatModifierPlaneCount is 0 for VkFormat(%d) "
+ "drm_format_mod(0x%"PRIx64")", req->vk_format, mod);
+ total_result = false;
+ return false;
+ }
+
+ if (vk_mod_props.drmFormatModifierTilingFeatures == 0) {
+ bs_debug_error("drmFormatModifierTilingFeatures is 0 for VkFormat(%d) "
+ "drm_format_mod(0x%"PRIx64")", req->vk_format, mod);
+ total_result = false;
+ return false;
+ }
+
+ if (req->require_linear && mod != DRM_FORMAT_MOD_LINEAR)
+ return false;
+
+ if (req->vk_format_features !=
+ (req->vk_format_features & vk_mod_props.drmFormatModifierTilingFeatures))
+ return false;
+
+ VkPhysicalDeviceImageFormatInfo2 image_format_info2 = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
+ .format = req->vk_format,
+ .type = VK_IMAGE_TYPE_2D,
+ .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
+ .usage = req->vk_image_usage,
+ .flags = req->vk_image_create_flags,
+ };
+
+ if (req->vk_view_format_count > 0) {
+ image_format_info2.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+ }
+
+ VkPhysicalDeviceImageDrmFormatModifierInfoEXT image_mod_info = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT,
+ .drmFormatModifier = mod,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ };
+
+ bs_vk_chain_add(&image_format_info2, &image_mod_info);
+
+ VkPhysicalDeviceExternalImageFormatInfo external_image_format_info = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
+ .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
+
+ };
+
+ bs_vk_chain_add(&image_format_info2, &external_image_format_info);
+
+ VkImageFormatListCreateInfo image_format_list_info = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
+ .viewFormatCount = req->vk_view_format_count,
+ .pViewFormats = req->vk_view_formats,
+ };
+
+ if (req->vk_view_format_count > 0)
+ bs_vk_chain_add(&image_format_info2, &image_format_list_info);
+
+ VkImageFormatProperties2 image_format_props2 = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
+ };
+
+ VkImageFormatProperties *image_format_props = &image_format_props2.imageFormatProperties;
+
+ VkExternalImageFormatProperties external_image_format_props = {
+ .sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES,
+ };
+
+ bs_vk_chain_add(&image_format_props2, &external_image_format_props);
+
+ r = vkGetPhysicalDeviceImageFormatProperties2(vk_phys_dev, &image_format_info2,
+ &image_format_props2);
+
+ switch (r) {
+ case VK_SUCCESS:
+ break;
+ case VK_ERROR_FORMAT_NOT_SUPPORTED:
+ return false;
+ default:
+ // Die because some errors prevent further usage of Vulkan without undefined
+ // behavior.
+ bs_debug_error("unexpected VkResult(%d) from "
+ "vkGetPhysicalDeviceFormatProperties2(format=%d, "
+ "drmFormatModifier=0x%"PRIx64")",
+ r, req->vk_format, mod);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!(image_format_props->sampleCounts & VK_SAMPLE_COUNT_1_BIT))
+ return false;
+
+ if (image_format_props->maxExtent.width < 8192 ||
+ image_format_props->maxExtent.height < 8192)
+ return false;
+
+ if (!(external_image_format_props.externalMemoryProperties.externalMemoryFeatures &
+ VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT))
+ return false;
+
+ if (external_image_format_props.externalMemoryProperties.externalMemoryFeatures &
+ VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT) {
+ bs_debug_warning("FINISHME: support VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT");
+ return false;
+ }
+
+ VkImageCreateInfo image_create_info = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .flags = image_format_info2.flags,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = image_format_info2.format,
+ // Choose a small extent to reduce runtime of allocations
+ .extent = (VkExtent3D) { 64, 64, 1 },
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
+ .usage = image_format_info2.usage,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ };
+
+ VkImageDrmFormatModifierListCreateInfoEXT image_mod_create_info = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT,
+ .drmFormatModifierCount = 1,
+ .pDrmFormatModifiers = &mod,
+ };
+
+ bs_vk_chain_add(&image_create_info, &image_mod_create_info);
+
+ VkExternalMemoryImageCreateInfo external_image_create_info = {
+ .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
+ .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT,
+ };
+
+ bs_vk_chain_add(&image_create_info, &external_image_create_info);
+
+ if (req->vk_view_format_count > 0)
+ bs_vk_chain_add(&image_create_info, &image_format_list_info);
+
+ VkImage vk_image;
+ r = vkCreateImage(vk_dev, &image_create_info, vk_alloc, &vk_image);
+
+ if (r != VK_SUCCESS) {
+ // Die because some errors prevent further usage of Vulkan without undefined
+ // behavior.
+ bs_debug_error("unexpected VkResult(%d) from "
+ "vkCreateImage(format=%d, drmFormatModifier=0x%"PRIx64")",
+ r, req->vk_format, mod);
+ exit(EXIT_FAILURE);
+ }
+
+ VkImageDrmFormatModifierPropertiesEXT image_mod_props = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT,
+ };
+
+ r = bs_vkGetImageDrmFormatModifierPropertiesEXT(bv_dev, vk_image, &image_mod_props);
+ if (r != VK_SUCCESS) {
+ // Die because some errors prevent further usage of Vulkan without undefined
+ // behavior.
+ bs_debug_error("unexpected VkResult(%d) from "
+ "vkGetImageDrmFormatModifierPropertiesEXT with "
+ "VkImage(format=%d, drmFormatModifier=0x%"PRIx64")",
+ r, req->vk_format, mod);
+ exit(EXIT_FAILURE);
+ }
+
+ if (image_mod_props.drmFormatModifier != mod) {
+ bs_debug_error("vkGetImageDrmFormatModifierPropertiesEXT return wrong modifier "
+ "with VkImage(format=%d, drmFormatModifier=0x%"PRIx64")",
+ req->vk_format, mod);
+ total_result = false;
+ pass = false;
+ goto cleanup;
+ }
+
+ // We don't test memory properties of disjoint images.
+ assert(!(image_create_info.flags & VK_IMAGE_CREATE_DISJOINT_BIT));
+
+ if (!test_memory_props(bv_dev, req, vk_image)) {
+ pass = false;
+ goto cleanup;
+ }
+
+ cleanup:
+ vkDestroyImage(vk_dev, vk_image, vk_alloc);
+ return pass;
+}
+
+static bool
+test_format(struct bs_vk_device *bv_dev, const struct format_req *req)
+{
+ VkPhysicalDevice vk_phys_dev = bv_dev->bv_physical_device->vk_physical_device;
+
+ VkFormatProperties2 format_props2 = {
+ .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2,
+ };
+
+ VkDrmFormatModifierPropertiesListEXT mod_props_list = {
+ .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT,
+ };
+
+ bs_vk_chain_add(&format_props2, &mod_props_list);
+
+ vkGetPhysicalDeviceFormatProperties2(vk_phys_dev, req->vk_format, &format_props2);
+
+ uint32_t mod_count = mod_props_list.drmFormatModifierCount;
+
+ if (mod_count == 0) {
+ bs_debug_error("VkFormat(%d) supports no modifiers", req->vk_format);
+ total_result = false;
+ return false;
+ }
+
+ mod_props_list.pDrmFormatModifierProperties =
+ alloca(mod_count * sizeof(mod_props_list.pDrmFormatModifierProperties[0]));
+
+ vkGetPhysicalDeviceFormatProperties2(vk_phys_dev, req->vk_format, &format_props2);
+
+ for (uint32_t i = 0; i < mod_count; ++i) {
+ const struct format_req *req = &format_req_table[i];
+ VkDrmFormatModifierPropertiesEXT mod_props =
+ mod_props_list.pDrmFormatModifierProperties[i];
+
+ if (test_format_mod(bv_dev, req, mod_props)) {
+ bs_debug_info("VkFormat(%d) passes requirements with drm_format_mod=0x%"PRIx64,
+ req->vk_format, mod_props.drmFormatModifier);
+ return true;
+ }
+
+ }
+
+ bs_debug_error("VkFormat(%d) failed requirements for all modifiers", req->vk_format);
+ total_result = false;
+ return false;
+}
+
+// TODO: Parse cmdline args
+int main(void) {
+ // TODO: Parse cmdline args
+ bs_vk_profile_flags_t bv_profiles;
+ bs_vk_parse_profiles("", &bv_profiles);
+
+ int drm_fd = bs_drm_open_main_display();
+ if (drm_fd < 0) {
+ bs_debug_error("failed to open display device");
+ exit(EXIT_FAILURE);
+ }
+
+ struct gbm_device *gbm_dev = gbm_create_device(drm_fd);
+ if (!gbm_dev) {
+ bs_debug_error("gbm_create_device failed");
+ exit(EXIT_FAILURE);
+ }
+
+ // The constructor checks the requirements on the VkInstance.
+ // See the comment on bs_vk_instance.
+ struct bs_vk_instance *bv_instance =
+ bs_vk_instance_new(.bv_profiles = bv_profiles);
+
+ // The constructor checks many requirements on the VkPhysicalDevice.
+ // See the comment on bs_vk_physical_device.
+ struct bs_vk_physical_device *bv_phys_dev =
+ bs_vk_physical_device_new(bv_instance,
+ .drm_fd = drm_fd,
+ .gbm_device = gbm_dev);
+
+ struct bs_vk_device *bv_dev = bs_vk_device_new(bv_phys_dev);
+
+ for (uint32_t i = 0; i < BS_ARRAY_LEN(format_req_table); ++i) {
+ test_format(bv_dev, &format_req_table[i]);
+ }
+
+ bs_vk_device_destroy(&bv_dev);
+ bs_vk_physical_device_destroy(&bv_phys_dev);
+ bs_vk_instance_destroy(&bv_instance);
+ gbm_device_destroy(gbm_dev);
+ close(drm_fd);
+
+ if (!total_result)
+ exit(EXIT_FAILURE);
+}
+
diff --git a/vk_triangle.c b/vk_triangle.c
new file mode 100644
index 0000000..e481233
--- /dev/null
+++ b/vk_triangle.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "bs_drm.h"
+#include "bs_vk.h"
+
+// TODO: Parse cmdline args
+
+int main(void) {
+ int drm_fd = bs_drm_open_main_display();
+ if (drm_fd < 0) {
+ bs_debug_error("failed to open display device");
+ exit(EXIT_FAILURE);
+ }
+
+ struct gbm_device *gbm_device = gbm_create_device(drm_fd);
+ if (!gbm) {
+ bs_debug_error("gbm_create_device failed");
+ exit(EXIT_FAILURE);
+ }
+
+ struct bs_vk_instance *bv_instance =
+ bs_vk_instance_new(
+
+ gbm_device_destroy(gbm_device);
+ close(drm_fd);
+}
+