Device and PhysicalDevice refactored.

This commit is contained in:
Anish Bhobe 2024-06-15 15:45:30 +02:00
parent 7f8e18ff97
commit 31efa504a3
5 changed files with 111 additions and 348 deletions

View File

@ -1,13 +1,36 @@
#include "constants.h"
#include "glfw_context.h"
#include "physical_device.h"
#include "window.h"
#include <iostream>
bool suitable_device(const PhysicalDevice *physical_device) {
constexpr auto required_support = QueueSupportFlags{} | QueueSupportFlagBits::eGraphics | QueueSupportFlagBits::ePresent | QueueSupportFlagBits::eTransfer;
return physical_device->properties.deviceType != vk::PhysicalDeviceType::eCpu &&
!physical_device->surface_formats.empty() &&
!physical_device->present_modes.empty() &&
cast<bool>(physical_device->queue_support & required_support);
}
PhysicalDevice find_suitable_device(PhysicalDevices &&_physical_devices) {
for (auto physdev : _physical_devices) {
VERBOSE(fmt::format("Checking device: {}", physdev.properties.deviceName.data()));
if (suitable_device(&physdev)) {
VERBOSE(fmt::format("Found suitable device {}.", physdev.properties.deviceName.data()));
return physdev;
}
}
throw std::runtime_error("No suitable physical device found.");
}
int main(int, char **) {
GlfwContext glfw = {};
Context context = { "Aster", VERSION };
Window window = { "Aster1", &context, { 640, 480 } };
PhysicalDevice physical_device = find_suitable_device({ &window, &context });
INFO(fmt::format("Using Device {}", physical_device.properties.deviceName.data()));
while (window.poll()) {
}

View File

@ -15,7 +15,6 @@
Device::Device(Device &&_other) noexcept :
physical_device{ std::move(_other.physical_device) },
device{ std::exchange(_other.device, nullptr) },
queues{ _other.queues },
allocator{ std::exchange(_other.allocator, nullptr) },
name{ std::move(_other.name) } {}
@ -24,31 +23,23 @@ Device &Device::operator=(Device &&_other) noexcept {
return *this;
physical_device = std::move(_other.physical_device);
device = std::exchange(_other.device, nullptr);
queues = _other.queues;
allocator = std::exchange(_other.allocator, nullptr);
name = std::move(_other.name);
return *this;
}
Device::Device(const std::string_view &_name, Context *_context, const PhysicalDevice &_physical_device_info, const vk::PhysicalDeviceFeatures &_enabled_features) :
Device::Device(const std::string_view &_name, Context *_context, const PhysicalDevice &_physical_device_info, const vk::PhysicalDeviceFeatures &_enabled_features, std::set<QueueAllocation> &&_enabled_queues) :
physical_device{ _physical_device_info },
name{ _name } {
const auto &physical_device = _physical_device_info.device;
const auto &queue_families = _physical_device_info.queue_families;
// Logical Device
std::map<u32, u16> unique_queue_families;
unique_queue_families[queue_families.graphics_idx]++;
unique_queue_families[queue_families.present_idx]++;
unique_queue_families[queue_families.transfer_idx]++;
unique_queue_families[queue_families.compute_idx]++;
std::array<f32, 4> queue_priority = { 1.0f, 1.0f, 1.0f, 1.0f };
std::vector queue_priority(_enabled_queues.size(), 1.0f);
std::vector<vk::DeviceQueueCreateInfo> queue_create_infos;
queue_create_infos.reserve(unique_queue_families.size());
for (auto &[index_, count_] : unique_queue_families) {
queue_create_infos.reserve(_enabled_queues.size());
for (auto &[family_, count_] : _enabled_queues) {
queue_create_infos.push_back({
.queueFamilyIndex = index_,
.queueFamilyIndex = family_,
.queueCount = count_,
.pQueuePriorities = queue_priority.data(),
});
@ -69,7 +60,7 @@ Device::Device(const std::string_view &_name, Context *_context, const PhysicalD
}
INFO("Logical Device Created!");
VmaAllocatorCreateInfo allocator_create_info = {
const VmaAllocatorCreateInfo allocator_create_info = {
.physicalDevice = *physical_device,
.device = *device,
.instance = *_context->instance,
@ -85,54 +76,7 @@ Device::Device(const std::string_view &_name, Context *_context, const PhysicalD
INFO(fmt::format("Created Device '{}' Successfully", _name));
// Setup queues
{
u32 compute_idx = --unique_queue_families[queue_families.compute_idx];
u32 transfer_idx = --unique_queue_families[queue_families.transfer_idx];
u32 present_idx = --unique_queue_families[queue_families.present_idx];
u32 graphics_idx = --unique_queue_families[queue_families.graphics_idx];
queues.graphics = device.getQueue(queue_families.graphics_idx, graphics_idx);
queues.present = device.getQueue(queue_families.present_idx, present_idx);
queues.transfer = device.getQueue(queue_families.transfer_idx, transfer_idx);
queues.compute = device.getQueue(queue_families.graphics_idx, compute_idx);
INFO(fmt::format("Graphics Queue Index: ({}, {})", queue_families.graphics_idx, graphics_idx));
INFO(fmt::format("Present Queue Index: ({}, {})", queue_families.present_idx, present_idx));
INFO(fmt::format("Transfer Queue Index: ({}, {})", queue_families.transfer_idx, transfer_idx));
INFO(fmt::format("Compute Queue Index: ({}, {})", queue_families.compute_idx, compute_idx));
}
// transfer_cmd_pool = device.createCommandPool({
// .flags = vk::CommandPoolCreateFlagBits::eTransient,
// .queueFamilyIndex = queue_families.transfer_idx,
// });
// if (failed(result)) {
// allocator.destroy();
// device.destroy();
// return Err::make(fmt::format("Transfer command pool creation failed with {}" CODE_LOC, to_cstr(result)), result);
// }
// VERBOSE("Transfer Command Pool Created");
//
// vk::CommandPool graphics_cmd_pool;
// tie(result, graphics_cmd_pool) = device.createCommandPool({
// .flags = vk::CommandPoolCreateFlagBits::eTransient | vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
// .queueFamilyIndex = queue_families.graphics_idx,
// });
// if (failed(result)) {
// device.destroyCommandPool(transfer_cmd_pool);
// allocator.destroy();
// device.destroy();
// return Err::make(fmt::format("Graphics command pool creation failed with {}" CODE_LOC, to_cstr(result)), result);
// }
// VERBOSE("Graphics Command Pool Created");
// transfer_cmd_pool,
// graphics_cmd_pool
// };
set_name(_name);
// final_device.set_object_name(transfer_cmd_pool, "Async transfer command pool");
// final_device.set_object_name(graphics_cmd_pool, "Single use Graphics command pool");
}
Device::~Device() {
@ -141,85 +85,6 @@ Device::~Device() {
}
INFO("Device '" + name + "' Destroyed");
}
//
// Res<vk::CommandBuffer> Device::alloc_temp_command_buffer(vk::CommandPool _pool) const {
// vk::CommandBuffer cmd;
// vk::CommandBufferAllocateInfo cmd_buf_alloc_info = {
// .commandPool = _pool,
// .level = vk::CommandBufferLevel::ePrimary,
// .commandBufferCount = 1,
// };
// if (const auto result = device.allocateCommandBuffers(&cmd_buf_alloc_info, &cmd); failed(result)) {
// return Err::make(fmt::format("Temp Command buffer allocation failed with {}" CODE_LOC, to_cstr(result)), result);
// }
// return cmd;
// }
//
// Res<SubmitTask<Buffer>> Device::upload_data(const Borrowed<Buffer> &_host_buffer, Buffer &&_staging_buffer) {
// ERROR_IF(!(_host_buffer->usage & vk::BufferUsageFlagBits::eTransferDst), fmt::format("Buffer {} is not a transfer dst. Use vk::BufferUsageFlagBits::eTransferDst during creation", _host_buffer->name.data()))
// ELSE_IF_ERROR(!(_staging_buffer.usage & vk::BufferUsageFlagBits::eTransferSrc), fmt::format("Buffer {} is not a transfer src. Use vk::BufferUsageFlagBits::eTransferSrc during creation", _staging_buffer.name.data()))
// ELSE_IF_WARN(_host_buffer->memory_usage != vma::MemoryUsage::eGpuOnly, fmt::format("Memory {} is not GPU only. Upload not required", _host_buffer->name.data()))
// ELSE_IF_WARN(_host_buffer->memory_usage != vma::MemoryUsage::eCpuOnly, fmt::format("Memory {} is not CPU only. Staging should ideally be a CPU only buffer", _staging_buffer.name.data()));
//
// vk::CommandBuffer cmd;
// vk::CommandBufferAllocateInfo allocate_info = {
// .commandPool = transfer_cmd_pool,
// .level = vk::CommandBufferLevel::ePrimary,
// .commandBufferCount = 1,
// };
//
// auto result = device.allocateCommandBuffers(&allocate_info, &cmd);
// if (failed(result)) {
// return Err::make(fmt::format("Transfer command pool allocation failed with {}" CODE_LOC, to_cstr(result)), result);
// }
// set_object_name(cmd, fmt::format("{} transfer command", _host_buffer->name.data()));
//
// result = cmd.begin({
// .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
// });
// if (failed(result)) {
// return Err::make(fmt::format("Command buffer begin failed with {}" CODE_LOC, to_cstr(result)), result);
// }
//
// cmd.copyBuffer(_staging_buffer.buffer, _host_buffer->buffer, { { .srcOffset = 0, .dstOffset = 0, .size = cast<u32>(_staging_buffer.size) } });
// result = cmd.end();
// if (failed(result)) {
// return Err::make(fmt::format("Command buffer end failed with {}" CODE_LOC, to_cstr(result)), result);
// }
//
// return SubmitTask<Buffer>::create(borrow(this), std::move(_staging_buffer), queues.transfer, transfer_cmd_pool, { cmd });
// }
//
// Res<SubmitTask<Buffer>> Device::upload_data(const Borrowed<Buffer> &_host_buffer, const std::span<u8> &_data) {
// ERROR_IF(!(_host_buffer->usage & vk::BufferUsageFlagBits::eTransferDst), fmt::format("Buffer {} is not a transfer dst. Use vk::BufferUsageFlagBits::eTransferDst during creation", _host_buffer->name.data()))
// ELSE_IF_WARN(_host_buffer->memory_usage != vma::MemoryUsage::eGpuOnly, fmt::format("Memory {} is not GPU only. Upload not required", _host_buffer->name.data()));
//
// if (auto res = Buffer::create("_stage " + _host_buffer->name, borrow(this), _data.size(), vk::BufferUsageFlagBits::eTransferSrc, vma::MemoryUsage::eCpuOnly)) {
// return Err::make("Staging buffer creation failed", std::move(res.error()));
// } else {
// if (auto result = update_data(borrow(res.value()), _data)) {
// return upload_data(_host_buffer, std::move(res.value()));
// } else {
// return Err::make(std::move(result.error()));
// }
// }
// }
//
// Res<> Device::update_data(const Borrowed<Buffer> &_host_buffer, const std::span<u8> &_data) const {
// if (_host_buffer->memory_usage != vma::MemoryUsage::eCpuToGpu &&
// _host_buffer->memory_usage != vma::MemoryUsage::eCpuOnly) {
// return Err::make("Memory is not on CPU so mapping can't be done. Use upload_data" CODE_LOC);
// }
//
// auto [result, mapped_memory] = allocator.mapMemory(_host_buffer->allocation);
// if (failed(result)) {
// return Err::make(fmt::format("Memory mapping failed with {}" CODE_LOC, to_cstr(result)), result);
// }
// memcpy(mapped_memory, _data.data(), _data.size());
// allocator.unmapMemory(_host_buffer->allocation);
//
// return {};
// }
void Device::set_name(const std::string_view &_name) {
VERBOSE(fmt::format("Device {} -> {}", name.data(), _name.data()));

View File

@ -11,20 +11,16 @@
#include "window.h"
#include <iterator>
#include <set>
#include <span>
#include <string_view>
#include <utility>
struct Queues {
vk::raii::Queue graphics{ nullptr };
vk::raii::Queue present{ nullptr };
vk::raii::Queue transfer{ nullptr };
Option<vk::raii::Queue> compute;
struct QueueAllocation {
u32 family;
u32 count;
};
template <typename T>
struct SubmitTask;
class Device {
public:
Device(const Device &_other) = delete;
@ -32,7 +28,7 @@ public:
Device &operator=(const Device &_other) = delete;
Device &operator=(Device &&_other) noexcept;
Device(const std::string_view &_name, Context *_context, const PhysicalDevice &_physical_device_info, const vk::PhysicalDeviceFeatures &_enabled_features);
Device(const std::string_view &_name, Context *_context, const PhysicalDevice &_physical_device_info, const vk::PhysicalDeviceFeatures &_enabled_features, std::set<QueueAllocation> &&_enabled_queues);
~Device();
@ -49,153 +45,13 @@ public:
}
}
//
// [[nodiscard]]
// Res<vk::CommandBuffer> alloc_temp_command_buffer(vk::CommandPool _pool) const;
//
// [[nodiscard]] Res<SubmitTask<Buffer>> upload_data(const Borrowed<Buffer>& _host_buffer, Buffer&& _staging_buffer);
// [[nodiscard]] Res<SubmitTask<Buffer>> upload_data(const Borrowed<Buffer>& _host_buffer, const std::span<u8>& _data);
// Res<> update_data(const Borrowed<Buffer>& _host_buffer, const std::span<u8>& _data) const;
// fields
PhysicalDevice physical_device;
vk::raii::Device device{ nullptr };
Queues queues;
VmaAllocator allocator;
// vk::CommandPool transfer_cmd_pool;
// vk::CommandPool graphics_cmd_pool;
std::string name;
private:
void set_name(const std::string_view &_name);
};
//
// template <typename T = void>
// struct SubmitTask {
// vk::Fence fence;
// Borrowed<Device> device{};
// T payload;
// std::vector<vk::CommandBuffer> cmd;
// vk::CommandPool pool;
//
// [[nodiscard]]
// static Res<SubmitTask<T>> create(const Borrowed<Device>& _device, T&& _payload, vk::Queue _queue, vk::CommandPool _pool, const std::vector<vk::CommandBuffer>& _cmd, const std::vector<vk::Semaphore>& _wait_on = {}, const std::vector<vk::Semaphore>& _signal_to = {}) {
//
// SubmitTask<T> task;
// if (auto res = task.submit(_device, std::forward<T>(_payload), _queue, _pool, _cmd, _wait_on, _signal_to)) {
// return std::move(task);
// } else {
// return Err::make(std::move(res.error()));
// }
// }
//
// [[nodiscard]]
// Res<> submit(const Borrowed<Device>& _device, T&& _payload, vk::Queue _queue, vk::CommandPool _pool, std::vector<vk::CommandBuffer> _cmd, std::vector<vk::Semaphore> _wait_on = {}, std::vector<vk::Semaphore> _signal_to = {}) {
// device = _device;
// auto [result, _fence] = device->device.createFence({});
// if (failed(result)) {
// return Err::make(std::fmt("Fence creation failed with %s" CODE_LOC, to_cstr(result)), result);
// }
// fence = _fence;
// payload = std::move(_payload);
// cmd = _cmd;
// pool = _pool;
//
// result = _queue.submit({
// {
// .waitSemaphoreCount = cast<u32>(_wait_on.size()),
// .pWaitSemaphores = _wait_on.data(),
// .commandBufferCount = cast<u32>(_cmd.size()),
// .pCommandBuffers = _cmd.data(),
// .signalSemaphoreCount = cast<u32>(_signal_to.size()),
// .pSignalSemaphores = _signal_to.data(),
// }
// },
// _fence);
// if (failed(result)) {
// return Err::make(std::fmt("Submit failed with %s" CODE_LOC, to_cstr(result)), result);
// }
//
// return {};
// }
//
// [[nodiscard]]
// Res<> wait_and_destroy() {
// return this->destroy();
// }
//
// [[nodiscard]]
// Res<> destroy() {
// const auto result = device->device.waitForFences({ fence }, true, max_value<u64>);
// if (failed(result)) return Err::make(std::fmt("Fence wait failed with %s" CODE_LOC, to_cstr(result)), result);
// device->device.destroyFence(fence);
// device->device.freeCommandBuffers(pool, cast<u32>(cmd.size()), cmd.data());
// return {};
// }
// };
//
// template <>
// struct SubmitTask<void> {
// vk::Fence fence;
// Borrowed<Device> device;
// std::vector<vk::CommandBuffer> cmd;
// vk::CommandPool pool;
//
// [[nodiscard]]
// static Res<SubmitTask<>> create(const Borrowed<Device>& _device, vk::Queue _queue, vk::CommandPool _pool, const std::vector<vk::CommandBuffer>& _cmd, const std::vector<vk::Semaphore>& _wait_on = {}, const vk::PipelineStageFlags& _wait_stage = vk::PipelineStageFlagBits::eBottomOfPipe, const std::vector<vk::Semaphore>& _signal_to = {}) {
//
// SubmitTask<> task;
// if (auto res = task.submit(_device, _queue, _pool, _cmd, _wait_on, _wait_stage, _signal_to)) {
// return std::move(task);
// } else {
// return Err::make(std::move(res.error()));
// }
// }
//
// [[nodiscard]]
// Res<> wait_and_destroy() {
// return this->destroy();
// }
//
// [[nodiscard]]
// Res<> destroy() {
// const auto result = device->device.waitForFences({ fence }, true, max_value<u64>);
// if (failed(result)) return Err::make(std::fmt("Fence wait failed with %s", to_cstr(result)), result);
// device->device.destroyFence(fence);
// device->device.freeCommandBuffers(pool, cast<u32>(cmd.size()), cmd.data());
// return {};
// }
//
// private:
// [[nodiscard]]
// Res<> submit(const Borrowed<Device>& _device, vk::Queue _queue, vk::CommandPool _pool, std::vector<vk::CommandBuffer> _cmd, std::vector<vk::Semaphore> _wait_on = {},
// const vk::PipelineStageFlags& _wait_stage = vk::PipelineStageFlagBits::eBottomOfPipe, std::vector<vk::Semaphore> _signal_to = {}) {
// device = _device;
// auto [result, _fence] = device->device.createFence({});
// if (failed(result)) {
// return Err::make(std::fmt("Fence creation failed with %s" CODE_LOC, to_cstr(result)), result);
// }
// fence = _fence;
// cmd = _cmd;
// pool = _pool;
// result = _queue.submit({
// vk::SubmitInfo{
// .waitSemaphoreCount = cast<u32>(_wait_on.size()),
// .pWaitSemaphores = _wait_on.data(),
// .pWaitDstStageMask = &_wait_stage,
// .commandBufferCount = cast<u32>(_cmd.size()),
// .pCommandBuffers = _cmd.data(),
// .signalSemaphoreCount = cast<u32>(_signal_to.size()),
// .pSignalSemaphores = _signal_to.data(),
// } },
// _fence);
// if (failed(result)) {
// return Err::make(std::fmt("Submit failed with %s" CODE_LOC, to_cstr(result)), result);
// }
//
// return {};
// }
// };
//

View File

@ -5,59 +5,45 @@
#include "physical_device.h"
QueueFamilyIndices PhysicalDevice::get_queue_families(const Window *_window, const vk::raii::PhysicalDevice *_device) {
QueueFamilyIndices indices;
std::vector<QueueFamilyInfo> PhysicalDevice::get_queue_families(const Window *_window) const {
auto queue_families_ = device.getQueueFamilyProperties();
auto queue_families_ = _device->getQueueFamilyProperties();
std::vector<QueueFamilyInfo> queue_family_infos;
queue_family_infos.reserve(queue_families_.size());
u32 family_index = 0;
for (const auto &queue_family : queue_families_) {
u32 this_family_count = 0;
VERBOSE(fmt::format("Queue({}): {}", family_index, to_string(queue_family.queueFlags).data()));
QueueSupportFlags support;
if (!indices.has_graphics() && (queue_family.queueFlags & vk::QueueFlagBits::eGraphics)) {
if (queue_family.queueCount > this_family_count) {
indices.graphics_idx = family_index;
++this_family_count;
} else {
continue;
if (queue_family.queueFlags & vk::QueueFlagBits::eGraphics) {
support |= QueueSupportFlagBits::eGraphics;
}
if (queue_family.queueFlags & vk::QueueFlagBits::eCompute) {
support |= QueueSupportFlagBits::eCompute;
}
if (!indices.has_compute() && (queue_family.queueFlags & vk::QueueFlagBits::eCompute)) {
if (queue_family.queueCount > this_family_count) {
indices.compute_idx = family_index;
++this_family_count;
} else {
continue;
}
}
if (!indices.has_transfer() && (queue_family.queueFlags & vk::QueueFlagBits::eTransfer)) {
if (queue_family.queueCount > this_family_count) {
indices.transfer_idx = family_index;
++this_family_count;
} else {
continue;
}
if (queue_family.queueFlags & vk::QueueFlagBits::eTransfer) {
support |= QueueSupportFlagBits::eTransfer;
}
try {
if (!indices.has_present() && _device->getSurfaceSupportKHR(family_index, *_window->surface)) {
if (queue_family.queueCount > this_family_count) {
indices.present_idx = family_index;
++this_family_count;
} else {
continue;
}
if (device.getSurfaceSupportKHR(family_index, *_window->surface)) {
support |= QueueSupportFlagBits::ePresent;
}
} catch (const std::exception &err) {
ERROR("Failure in finding surface support, all possibilities fatal. Failed with "s + err.what());
throw;
}
VERBOSE(fmt::format("Queue({}): {}", family_index, to_string(support)));
queue_family_infos.push_back({
.index = family_index,
.count = queue_family.queueCount,
.queue_support = support,
});
++family_index;
}
return indices;
return queue_family_infos;
}

View File

@ -8,28 +8,43 @@
#include "global.h"
#include "window.h"
struct QueueFamilyIndices {
static constexpr u32 invalid_value = 0xFFFFFFFFu;
enum class QueueSupportFlagBits {
eGraphics = 0b0000'0001,
eTransfer = 0b0000'0010,
eCompute = 0b0000'0100,
ePresent = 0b0000'1000,
};
u32 graphics_idx{ invalid_value };
u32 present_idx{ invalid_value };
u32 compute_idx{ invalid_value };
u32 transfer_idx{ invalid_value };
using QueueSupportFlags = vk::Flags<QueueSupportFlagBits>;
[[nodiscard]] b8 has_graphics() const {
return graphics_idx != invalid_value;
inline std::string to_string(QueueSupportFlags queue_support) {
if (! queue_support)
return {};
std::string result = "";
if (queue_support & QueueSupportFlagBits::eGraphics) {
result += "Graphics | ";
}
if (queue_support & QueueSupportFlagBits::eTransfer) {
result += "Transfer | ";
}
if (queue_support & QueueSupportFlagBits::eCompute) {
result += "Compute | ";
}
if (queue_support & QueueSupportFlagBits::ePresent) {
result += "Present | ";
}
[[nodiscard]] b8 has_present() const {
return present_idx != invalid_value;
return "{ " + result.substr( 0, result.size() - 3 ) + " }";
}
[[nodiscard]] b8 has_compute() const {
return compute_idx != invalid_value;
}
struct QueueFamilyInfo {
u32 index;
u32 count;
QueueSupportFlags queue_support;
[[nodiscard]] b8 has_transfer() const {
return transfer_idx != invalid_value;
[[nodiscard]] bool has_support(const QueueSupportFlags &support) const {
return cast<bool>(queue_support & support);
}
};
@ -37,15 +52,33 @@ struct PhysicalDevice {
vk::raii::PhysicalDevice device;
vk::PhysicalDeviceProperties properties;
vk::PhysicalDeviceFeatures features;
QueueFamilyIndices queue_families;
std::vector<vk::SurfaceFormatKHR> surface_formats;
std::vector<vk::PresentModeKHR> present_modes;
std::vector<QueueFamilyInfo> queue_family_infos;
QueueSupportFlags queue_support;
PhysicalDevice(const Window *const _window, vk::raii::PhysicalDevice _device) :
PhysicalDevice(const Window *const _window, vk::raii::PhysicalDevice &&_device) :
device{ std::move(_device) } {
properties = device.getProperties();
features = device.getFeatures();
queue_families = get_queue_families(_window, &device);
surface_formats = device.getSurfaceFormatsKHR(*_window->surface);
present_modes = device.getSurfacePresentModesKHR(*_window->surface);
queue_family_infos = get_queue_families(_window);
for (auto [family, count, support] : queue_family_infos) {
queue_support |= support;
}
}
private:
[[nodiscard]] static QueueFamilyIndices get_queue_families(const Window *_window, const vk::raii::PhysicalDevice *_device);
std::vector<QueueFamilyInfo> get_queue_families(const Window *_window) const;
};
struct PhysicalDevices : std::vector<PhysicalDevice> {
PhysicalDevices(const Window *const _window, const Context *_context) {
auto phys_devs = vk::raii::PhysicalDevices(_context->instance);
reserve(phys_devs.size());
for (auto physdev : phys_devs) {
emplace_back(_window, std::move(physdev));
}
}
};