diff --git a/aster_core/CMakeLists.txt b/aster_core/CMakeLists.txt index 2b4d21c..0674097 100644 --- a/aster_core/CMakeLists.txt +++ b/aster_core/CMakeLists.txt @@ -9,8 +9,8 @@ find_package( Vulkan REQUIRED ) # find_package( VulkanHeaders CONFIG REQUIRED ) find_package( VulkanMemoryAllocator CONFIG REQUIRED ) -set( HEADER_FILES constants.h config.h logger.h global.h context.h window.h ) -set( SOURCE_FILES logger.cpp global.cpp context.cpp window.cpp ) +set( HEADER_FILES constants.h config.h logger.h global.h context.h window.h device.h ) +set( SOURCE_FILES logger.cpp global.cpp context.cpp window.cpp device.cpp ) add_library( aster_core ${SOURCE_FILES} ${HEADER_FILES} ) set_property( TARGET aster_core PROPERTY CXX_STANDARD 23 ) diff --git a/aster_core/device.cpp b/aster_core/device.cpp new file mode 100644 index 0000000..51bad77 --- /dev/null +++ b/aster_core/device.cpp @@ -0,0 +1,281 @@ +// ============================================= +// Aster: device.cpp +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#include "device.h" + +#include "context.h" +#include "window.h" + +#include +#include +#include + +Device::Device(Device &&_other) noexcept : + physical_device{ _other.physical_device }, device{ std::exchange(_other.device, nullptr) }, queues{ _other.queues }, allocator{ std::exchange(_other.allocator, nullptr) }, name{ std::move(_other.name) } {} + +Device &Device::operator=(Device &&_other) noexcept { + if (this == &_other) + return *this; + physical_device = _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 PhysicalDeviceInfo &_physical_device_info, const vk::PhysicalDeviceFeatures &_enabled_features) : + 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 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 queue_priority = { 1.0f, 1.0f, 1.0f, 1.0f }; + std::vector queue_create_infos; + for (auto &[index_, count_] : unique_queue_families) { + queue_create_infos.push_back({ + .queueFamilyIndex = index_, + .queueCount = count_, + .pQueuePriorities = queue_priority.data(), + }); + } + try { + device = physical_device.createDevice({ + .queueCreateInfoCount = cast(queue_create_infos.size()), + .pQueueCreateInfos = queue_create_infos.data(), + .enabledLayerCount = _context->enable_validation_layers ? cast(_context->validation_layers.size()) : 0, + .ppEnabledLayerNames = _context->enable_validation_layers ? _context->validation_layers.data() : nullptr, + .enabledExtensionCount = cast(_context->device_extensions.size()), + .ppEnabledExtensionNames = _context->device_extensions.data(), + .pEnabledFeatures = &_enabled_features, + }); + } catch (const std::exception &err) { + ERROR("Failed to create a logical device with "s + err.what()); + throw err; + } + INFO("Logical Device Created!"); + + VmaAllocatorCreateInfo allocator_create_info = { + .physicalDevice = *physical_device, + .device = *device, + .instance = *_context->instance, + }; + + auto result = cast(vmaCreateAllocator(&allocator_create_info, &allocator)); + if (failed(result)) { + ERROR("Memory allocator creation failed with "s + vk::to_string(result)); + throw std::runtime_error("Memory allocator creation failed with "s + vk::to_string(result)); + } + VERBOSE("Memory Allocator Created"); + + INFO(std::fmt("Created Device '%s' Successfully", _name.data())); + + // 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(std::fmt("Graphics Queue Index: (%i, %i)", queue_families.graphics_idx, graphics_idx)); + INFO(std::fmt("Present Queue Index: (%i, %i)", queue_families.present_idx, present_idx)); + INFO(std::fmt("Transfer Queue Index: (%i, %i)", queue_families.transfer_idx, transfer_idx)); + INFO(std::fmt("Compute Queue Index: (%i, %i)", 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(std::fmt("Transfer command pool creation failed with %s" 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(std::fmt("Graphics command pool creation failed with %s" 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() { + if (allocator) { + vmaDestroyAllocator(allocator); + } + INFO("Device '" + name + "' Destroyed"); +} +// +// Res 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(std::fmt("Temp Command buffer allocation failed with %s" CODE_LOC, to_cstr(result)), result); +// } +// return cmd; +// } +// +// Res> Device::upload_data(const Borrowed &_host_buffer, Buffer &&_staging_buffer) { +// ERROR_IF(!(_host_buffer->usage & vk::BufferUsageFlagBits::eTransferDst), std::fmt("Buffer %s is not a transfer dst. Use vk::BufferUsageFlagBits::eTransferDst during creation", _host_buffer->name.data())) +// ELSE_IF_ERROR(!(_staging_buffer.usage & vk::BufferUsageFlagBits::eTransferSrc), std::fmt("Buffer %s 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, std::fmt("Memory %s is not GPU only. Upload not required", _host_buffer->name.data())) +// ELSE_IF_WARN(_host_buffer->memory_usage != vma::MemoryUsage::eCpuOnly, std::fmt("Memory %s 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(std::fmt("Transfer command pool allocation failed with %s" CODE_LOC, to_cstr(result)), result); +// } +// set_object_name(cmd, std::fmt("%s transfer command", _host_buffer->name.data())); +// +// result = cmd.begin({ +// .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, +// }); +// if (failed(result)) { +// return Err::make(std::fmt("Command buffer begin failed with %s" CODE_LOC, to_cstr(result)), result); +// } +// +// cmd.copyBuffer(_staging_buffer.buffer, _host_buffer->buffer, { { .srcOffset = 0, .dstOffset = 0, .size = cast(_staging_buffer.size) } }); +// result = cmd.end(); +// if (failed(result)) { +// return Err::make(std::fmt("Command buffer end failed with %s" CODE_LOC, to_cstr(result)), result); +// } +// +// return SubmitTask::create(borrow(this), std::move(_staging_buffer), queues.transfer, transfer_cmd_pool, { cmd }); +// } +// +// Res> Device::upload_data(const Borrowed &_host_buffer, const std::span &_data) { +// ERROR_IF(!(_host_buffer->usage & vk::BufferUsageFlagBits::eTransferDst), std::fmt("Buffer %s 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, std::fmt("Memory %s 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 &_host_buffer, const std::span &_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(std::fmt("Memory mapping failed with %s" 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(std::fmt("Device %s -> %s", name.data(), _name.data())); + name = _name; + set_object_name(*physical_device.device, std::fmt("%s GPU", _name.data())); + set_object_name(*device, std::fmt("%s Device", _name.data())); +} +// +// QueueFamilyIndices DeviceSelector::PhysicalDeviceInfo::get_queue_families(const Borrowed &_window, const vk::PhysicalDevice _device) const { +// QueueFamilyIndices indices; +// +// auto queue_families_ = _device.getQueueFamilyProperties(); +// +// u32 i = 0; +// for (const auto &queueFamily : queue_families_) { +// u32 this_family_count = 0; +// VERBOSE(std::fmt("Queue(%i): %s", i, to_string(queueFamily.queueFlags).data())); +// +// if (queueFamily.queueCount <= 0) { +// ++i; +// continue; // Skip families with no queues +// } +// +// if (!indices.has_graphics() && (queueFamily.queueFlags & vk::QueueFlagBits::eGraphics)) { +// if (queueFamily.queueCount > this_family_count) { +// indices.graphics_idx = i; +// ++this_family_count; +// } else { +// continue; +// } +// } +// +// if (!indices.has_compute() && (queueFamily.queueFlags & vk::QueueFlagBits::eCompute)) { +// if (queueFamily.queueCount > this_family_count) { +// indices.compute_idx = i; +// ++this_family_count; +// } else { +// continue; +// } +// } +// +// if (!indices.has_transfer() && (queueFamily.queueFlags & vk::QueueFlagBits::eTransfer)) { +// if (queueFamily.queueCount > this_family_count) { +// indices.transfer_idx = i; +// ++this_family_count; +// } else { +// continue; +// } +// } +// +// auto [result, is_present_supported] = _device.getSurfaceSupportKHR(i, _window->surface); +// if (!indices.has_present() && !failed(result) && is_present_supported) { +// if (queueFamily.queueCount > this_family_count) { +// indices.present_idx = i; +// ++this_family_count; +// } else { +// continue; +// } +// } +// +// ++i; +// } +// +// return indices; +// } diff --git a/aster_core/device.h b/aster_core/device.h new file mode 100644 index 0000000..9d81115 --- /dev/null +++ b/aster_core/device.h @@ -0,0 +1,243 @@ +// ============================================= +// Aster: device.h +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#pragma once + +#include "context.h" +#include "global.h" +#include "window.h" + +#include +#include +#include + +class Device; + +struct QueueFamilyIndices { + static constexpr u32 invalid_value = 0xFFFFFFFFu; + + u32 graphics_idx{ invalid_value }; + u32 present_idx{ invalid_value }; + u32 compute_idx{ invalid_value }; + u32 transfer_idx{ invalid_value }; + + [[nodiscard]] b8 has_graphics() const { + return graphics_idx != invalid_value; + } + + [[nodiscard]] b8 has_present() const { + return present_idx != invalid_value; + } + + [[nodiscard]] b8 has_compute() const { + return compute_idx != invalid_value; + } + + [[nodiscard]] b8 has_transfer() const { + return transfer_idx != invalid_value; + } +}; + +struct Queues { + vk::raii::Queue graphics{ nullptr }; + vk::raii::Queue present{ nullptr }; + vk::raii::Queue transfer{ nullptr }; + Option compute; +}; + +template +struct SubmitTask; + +class Device { +public: + struct PhysicalDeviceInfo { + vk::raii::PhysicalDevice device; + vk::PhysicalDeviceProperties properties; + vk::PhysicalDeviceFeatures features; + QueueFamilyIndices queue_families; + + PhysicalDeviceInfo(Window *const _window, const vk::raii::PhysicalDevice &_device) : + device{ _device } { + properties = device.getProperties(); + features = device.getFeatures(); + queue_families = get_queue_families(_window, device); + } + + private: + [[nodiscard]] QueueFamilyIndices get_queue_families(const Window *_window, vk::raii::PhysicalDevice _device) const; + }; + + Device(const Device &_other) = delete; + Device(Device &&_other) noexcept; + Device &operator=(const Device &_other) = delete; + Device &operator=(Device &&_other) noexcept; + + Device(const std::string_view &_name, Context *_context, const PhysicalDeviceInfo &_physical_device_info, const vk::PhysicalDeviceFeatures &_enabled_features); + + ~Device(); + + template + requires vk::isVulkanHandleType::value void set_object_name(const T &_obj, const std::string_view &_name) const { + try { + device.setDebugUtilsObjectNameEXT({ + .objectType = _obj.objectType, + .objectHandle = get_vk_handle(_obj), + .pObjectName = _name.data(), + }); + } catch (const std::exception &err) { + WARN("Debug Utils name setting failed with "s + err.what()); + } + } + + // + // [[nodiscard]] + // Res alloc_temp_command_buffer(vk::CommandPool _pool) const; + // + // [[nodiscard]] Res> upload_data(const Borrowed& _host_buffer, Buffer&& _staging_buffer); + // [[nodiscard]] Res> upload_data(const Borrowed& _host_buffer, const std::span& _data); + // Res<> update_data(const Borrowed& _host_buffer, const std::span& _data) const; + + // fields + PhysicalDeviceInfo 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 +// struct SubmitTask { +// vk::Fence fence; +// Borrowed device{}; +// T payload; +// std::vector cmd; +// vk::CommandPool pool; +// +// [[nodiscard]] +// static Res> create(const Borrowed& _device, T&& _payload, vk::Queue _queue, vk::CommandPool _pool, const std::vector& _cmd, const std::vector& _wait_on = {}, const std::vector& _signal_to = {}) { +// +// SubmitTask task; +// if (auto res = task.submit(_device, std::forward(_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, T&& _payload, vk::Queue _queue, vk::CommandPool _pool, std::vector _cmd, std::vector _wait_on = {}, std::vector _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(_wait_on.size()), +// .pWaitSemaphores = _wait_on.data(), +// .commandBufferCount = cast(_cmd.size()), +// .pCommandBuffers = _cmd.data(), +// .signalSemaphoreCount = cast(_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); +// 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(cmd.size()), cmd.data()); +// return {}; +// } +// }; +// +// template <> +// struct SubmitTask { +// vk::Fence fence; +// Borrowed device; +// std::vector cmd; +// vk::CommandPool pool; +// +// [[nodiscard]] +// static Res> create(const Borrowed& _device, vk::Queue _queue, vk::CommandPool _pool, const std::vector& _cmd, const std::vector& _wait_on = {}, const vk::PipelineStageFlags& _wait_stage = vk::PipelineStageFlagBits::eBottomOfPipe, const std::vector& _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); +// 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(cmd.size()), cmd.data()); +// return {}; +// } +// +// private: +// [[nodiscard]] +// Res<> submit(const Borrowed& _device, vk::Queue _queue, vk::CommandPool _pool, std::vector _cmd, std::vector _wait_on = {}, +// const vk::PipelineStageFlags& _wait_stage = vk::PipelineStageFlagBits::eBottomOfPipe, std::vector _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(_wait_on.size()), +// .pWaitSemaphores = _wait_on.data(), +// .pWaitDstStageMask = &_wait_stage, +// .commandBufferCount = cast(_cmd.size()), +// .pCommandBuffers = _cmd.data(), +// .signalSemaphoreCount = cast(_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 {}; +// } +// }; +// \ No newline at end of file diff --git a/aster_core/global.h b/aster_core/global.h index b6d7820..30e25e2 100644 --- a/aster_core/global.h +++ b/aster_core/global.h @@ -54,7 +54,7 @@ using namespace std::literals::string_view_literals; template requires vk::isVulkanHandleType::value [[nodiscard]] constexpr u64 get_vk_handle(const T &_vk_handle) noexcept { - return reinterpret_cast(cast(_vk_handle)); + return reinterpret_cast(cast(_vk_handle)); } template