blob: 245914f1d8895d4545a9d64931717f5a5a0669b0 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_GRAPHICS_TESTS_COMMON_VULKAN_CONTEXT_H_
#define SRC_GRAPHICS_TESTS_COMMON_VULKAN_CONTEXT_H_
#include <memory>
#include <gtest/gtest.h>
#include <vulkan/vulkan.hpp>
//
// VulkanContext is a convenience class for handling boilerplate vulkan setup code.
// It creates / encapsulates vulkan:
// - instance
// - physical device
// - queue family
// - device
// - queue
//
// VulkanContext leverages hpp to have smart pointer semantics for simplified vulkan
// resource allocation and free.
//
// There are 2 canonical usage modalities expected for VulkanContext:
//
// (1) the simplest mode is to pair VulkanContext with its nested Builder class
// to selectively modify the required vulkan "CreateInfo" structs during
// construction. E.g. to create an std::unique_ptr<VulkanContext> setting the instance
// CreateInfo and the queue flag bits:
//
// auto ctx = VulkanContext::Builder{}.set_instance_info(info).set_queue_flag_bits(bits).Unique();
//
// (2) the second construction mode is for more sophisticated construction where more fine
// grained control is required during construction. There are three primary piecewise construction
// phases that must be done in order:
//
// - InitInstance()
// - InitQueueFamily()
// - InitDevice()
//
// For example, the device CreateInfo structure may need to be customized
// (e.g. to specify protected memory) before calling InitDevice(), and those modifications require
// access to the physical device chosen in the pair of calls to InitInstance() and
// InitQueueFamily().
//
class VulkanContext {
public:
class Builder;
class ContextWithUserData;
static constexpr int kInvalidQueueFamily = -1;
static constexpr vk::QueueFlags kDefaultQueueFlags = vk::QueueFlagBits::eGraphics;
static vk::DebugUtilsMessengerCreateInfoEXT default_debug_info_s_;
static ContextWithUserData default_debug_callback_user_data_s_;
VulkanContext(const vk::InstanceCreateInfo &instance_info, size_t physical_device_index,
const vk::DeviceCreateInfo &device_info,
const vk::DeviceQueueCreateInfo &queue_info,
const vk::QueueFlags &queue_flags = vk::QueueFlagBits::eGraphics,
const vk::DebugUtilsMessengerCreateInfoEXT &debug_info = default_debug_info_s_,
ContextWithUserData debug_user_data = default_debug_callback_user_data_s_,
vk::Optional<const vk::AllocationCallbacks> allocator = nullptr,
bool validation_layers_enabled = true, bool validation_layers_ignored_ = false);
explicit VulkanContext(size_t physical_device_index,
const vk::QueueFlags &queue_flags = vk::QueueFlagBits::eGraphics,
vk::Optional<const vk::AllocationCallbacks> allocator = nullptr);
bool Init();
bool InitInstance();
bool InitQueueFamily();
bool InitDevice();
bool set_instance_info(const vk::InstanceCreateInfo &v);
bool set_device_info(const vk::DeviceCreateInfo &v);
bool set_queue_info(const vk::DeviceQueueCreateInfo &v);
bool set_queue_flags(const vk::QueueFlags &v);
void set_validation_layers_enabled(bool v) { validation_layers_enabled_ = v; }
// Set to true to ignore validation errors and allow the test to pass even with errors.
void set_validation_errors_ignored(bool v) { validation_errors_ignored_ = v; }
void set_validation_allowed_errors(const std::vector<vk::Result> &v);
void set_debug_utils_messenger(const vk::DebugUtilsMessengerCreateInfoEXT &debug_info,
const ContextWithUserData &user_data);
const vk::InstanceCreateInfo &instance_info() const { return instance_info_; }
const vk::DeviceCreateInfo &device_info() const { return device_info_; }
const vk::DeviceQueueCreateInfo &queue_info() const { return queue_info_; }
const vk::UniqueInstance &instance() const;
const vk::PhysicalDevice &physical_device() const;
const vk::UniqueDevice &device() const;
const vk::Queue &queue() const;
int queue_family_index() const;
const vk::QueueFlags &queue_flag_bits() const { return queue_flags_; }
bool validation_errors_ignored() const { return validation_errors_ignored_; }
// Package up the vulkan context and the user data for the debug callback together.
// |user_data_| declared such that VulkanContext will own the |user_data_| so we don't
// accidentally end up with a dangling (void *).
class ContextWithUserData {
public:
ContextWithUserData() : user_data_(nullptr) {}
explicit ContextWithUserData(std::shared_ptr<void> &user_data)
: user_data_(std::move(user_data)) {}
const VulkanContext *context() const { return context_; }
std::shared_ptr<void> user_data() { return user_data_; }
private:
// VulkanContext (only) should set the |context_| member.
friend class VulkanContext;
VulkanContext *context_ = nullptr;
std::shared_ptr<void> user_data_;
};
private:
FRIEND_TEST(VkContext, Unique);
bool initialized_ = false;
bool instance_initialized_ = false;
bool queue_family_initialized_ = false;
bool device_initialized_ = false;
// These ivars are listed in order of their use in initialization.
vk::UniqueInstance instance_;
vk::InstanceCreateInfo instance_info_;
vk::PhysicalDevice physical_device_;
size_t physical_device_index_;
float queue_priority_ = 0.0f;
int queue_family_index_;
vk::DeviceQueueCreateInfo queue_info_;
vk::DeviceCreateInfo device_info_;
vk::UniqueDevice device_;
ContextWithUserData debug_callback_user_data_;
vk::DebugUtilsMessengerCreateInfoEXT debug_info_;
vk::Queue queue_;
vk::QueueFlags queue_flags_;
vk::Optional<const vk::AllocationCallbacks> allocator_;
// The data in |layers_| and |extensions_| may be referenced by |instance_info_|.
std::vector<const char *> layers_;
std::vector<const char *> extensions_;
// By default validation layers should be enabled. A test may want to disable them if it's testing
// completely invalid behavior that could cause the layers to crash, or if it's a benchmark.
bool validation_layers_enabled_ = true;
// By default validation errors should fail the test.
bool validation_errors_ignored_ = false;
vk::DispatchLoaderDynamic loader_;
vk::UniqueHandle<vk::DebugUtilsMessengerEXT, vk::DispatchLoaderDynamic> messenger_;
};
class VulkanContext::Builder {
public:
Builder();
Builder(const Builder &) = delete;
std::unique_ptr<VulkanContext> Unique() const;
// Convenience methods for retrieving default device and queue info
// structs for read/modify/write amendments to these info structs during
// construction. The device info struct |device_info_| captures the address
// of the queue info ivar |queue_info_| as part of its definition.
vk::DeviceCreateInfo DeviceInfo() const;
vk::DeviceQueueCreateInfo QueueInfo() const;
Builder &set_allocator(vk::Optional<const vk::AllocationCallbacks> allocator);
//
// The mutators below shallow-copy the *CreateInfo structs because of the
// chaining nature of these structs (i.e. the pNext member).
//
// The caller of these methods must preserve memory backing the *info
// members through any calls to Unique() or Shared() which rely upon
// this information for instantiation.
//
// Typical construction example:
// auto ctx = VulkanContext::Builder{}.(optional set* calls).Unique();
//
Builder &set_instance_info(const vk::InstanceCreateInfo &v);
Builder &set_physical_device_index(size_t v);
Builder &set_queue_info(const vk::DeviceQueueCreateInfo &v);
Builder &set_device_info(const vk::DeviceCreateInfo &v);
Builder &set_queue_flags(const vk::QueueFlags &v);
Builder &set_validation_layers_enabled(bool v);
Builder &set_validation_errors_ignored(bool v);
Builder &set_debug_utils_messenger(const vk::DebugUtilsMessengerCreateInfoEXT &v0,
const ContextWithUserData &v1);
private:
vk::InstanceCreateInfo instance_info_;
size_t physical_device_index_;
float queue_priority_;
vk::DeviceQueueCreateInfo queue_info_;
vk::DeviceCreateInfo device_info_;
vk::QueueFlags queue_flags_;
bool validation_layers_enabled_ = true;
bool validation_errors_ignored_ = false;
vk::Optional<const vk::AllocationCallbacks> allocator_;
vk::DebugUtilsMessengerCreateInfoEXT debug_info_;
ContextWithUserData debug_callback_user_data_;
};
#endif // SRC_GRAPHICS_TESTS_COMMON_VULKAN_CONTEXT_H_