refactor(renderer): split up context into multiple objectes & fix some edge cases
This commit is contained in:
parent
d411c9ab2c
commit
fa1bfaae1e
18 changed files with 801 additions and 478 deletions
|
@ -1,15 +1,28 @@
|
|||
add_library_module(renderer
|
||||
system.cpp
|
||||
vk/context.cpp
|
||||
vk/context/instance.cpp
|
||||
vk/context/surface.cpp
|
||||
vk/context/device.cpp
|
||||
vk/context/swapchain.cpp
|
||||
vk/context/context.cpp
|
||||
vk/pipeline.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(renderer
|
||||
PUBLIC
|
||||
app
|
||||
ecs
|
||||
PRIVATE
|
||||
surface
|
||||
pthread
|
||||
)
|
||||
|
||||
add_test_module(renderer
|
||||
system.test.cpp
|
||||
vk/context/instance.test.cpp
|
||||
vk/context/surface.test.cpp
|
||||
vk/context/device.test.cpp
|
||||
vk/context/swapchain.test.cpp
|
||||
vk/context/context.test.cpp
|
||||
vk/pipeline.test.cpp
|
||||
)
|
||||
|
|
|
@ -69,29 +69,73 @@ struct RendererContext
|
|||
};
|
||||
}
|
||||
|
||||
class SystemTest
|
||||
{
|
||||
public:
|
||||
SystemTest()
|
||||
{
|
||||
m_surface_entity->add<surface::SurfaceComponent>(surface::SurfaceComponent::CreateInfo {
|
||||
.title = "",
|
||||
.resolution = resolution,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] auto registry() const -> Ref<ecs::Registry>
|
||||
{
|
||||
return m_registry;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto surface_entity() const -> const ecs::Entity &
|
||||
{
|
||||
return *m_surface_entity;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto stats() const -> Ref<app::SystemStats>
|
||||
{
|
||||
return m_stats;
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<app::SystemStats> m_stats = create_ref<app::SystemStats>();
|
||||
|
||||
Ref<ecs::Registry> m_registry = create_ref<ecs::Registry>();
|
||||
|
||||
Ref<surface::System> m_surface_system = create_ref<surface::System>(m_registry);
|
||||
|
||||
Scope<ecs::Entity> m_surface_entity = create_scope<ecs::Entity>(
|
||||
m_registry,
|
||||
m_registry->create_entity()
|
||||
);
|
||||
};
|
||||
|
||||
Suite raii = [] {
|
||||
Case { "happy path won't throw" } = [&] {
|
||||
ignore = create_system();
|
||||
};
|
||||
|
||||
Case { "happy path has no validation errors" } = [&] {
|
||||
auto [surface, renderer] = create_system();
|
||||
expect_true(renderer.system.get_stats().empty_diagnosis());
|
||||
auto fixture = SystemTest {};
|
||||
std::ignore = System(
|
||||
{
|
||||
.registry = fixture.registry(),
|
||||
.surface_entity = fixture.surface_entity(),
|
||||
.system_stats = fixture.stats(),
|
||||
}
|
||||
);
|
||||
|
||||
expect_true(fixture.stats()->empty_diagnosis());
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
auto [surface_system, surface_entity] = create_surface();
|
||||
auto empty_registry = create_ref<ecs::Registry>();
|
||||
auto empty_entity = ecs::Entity { empty_registry, empty_registry->create_entity() };
|
||||
auto registry = create_ref<ecs::Registry>();
|
||||
auto stats = create_ref<app::SystemStats>();
|
||||
auto fixture = SystemTest {};
|
||||
auto empty_entity = ecs::Entity { fixture.registry(), fixture.registry()->create_entity() };
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = System(
|
||||
{
|
||||
.registry = {},
|
||||
.surface_entity = surface_entity,
|
||||
.system_stats = stats,
|
||||
.surface_entity = fixture.surface_entity(),
|
||||
.system_stats = fixture.stats(),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -99,9 +143,9 @@ Suite raii = [] {
|
|||
expect_throw([&] {
|
||||
ignore = System(
|
||||
System::CreateInfo {
|
||||
.registry = surface_entity.get_registry(),
|
||||
.registry = fixture.registry(),
|
||||
.surface_entity = empty_entity,
|
||||
.system_stats = stats,
|
||||
.system_stats = fixture.stats(),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -109,8 +153,8 @@ Suite raii = [] {
|
|||
expect_throw([&] {
|
||||
ignore = System(
|
||||
System::CreateInfo {
|
||||
.registry = surface_entity.get_registry(),
|
||||
.surface_entity = surface_entity,
|
||||
.registry = fixture.registry(),
|
||||
.surface_entity = fixture.surface_entity(),
|
||||
.system_stats = {},
|
||||
}
|
||||
);
|
||||
|
|
15
modules/renderer/private/vk/context/context.cpp
Normal file
15
modules/renderer/private/vk/context/context.cpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#include <ranges>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Context::Context(const ecs::Entity &surface_entity, Ref<app::SystemStats> system_stats)
|
||||
: m_stats(std::move(system_stats))
|
||||
, m_surface(surface_entity)
|
||||
, m_device(m_surface)
|
||||
, m_swapchain(m_device, m_surface)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
48
modules/renderer/private/vk/context/context.hpp
Normal file
48
modules/renderer/private/vk/context/context.hpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include <app/system.hpp>
|
||||
#include <ecs/entity.hpp>
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <surface/components.hpp>
|
||||
|
||||
//
|
||||
#include <renderer/vk/context/device.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
#include <renderer/vk/context/surface.hpp>
|
||||
#include <renderer/vk/context/swapchain.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
using memory::NullOnMove;
|
||||
|
||||
class Context
|
||||
{
|
||||
public:
|
||||
Context(const ecs::Entity &surface_entity, Ref<app::SystemStats> system_stats);
|
||||
|
||||
[[nodiscard]] auto instance() const -> VkInstance
|
||||
{
|
||||
return Instance::get();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto device() const -> const Device &
|
||||
{
|
||||
return m_device;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto swapchain() const -> const Swapchain &
|
||||
{
|
||||
return m_swapchain;
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<app::SystemStats> m_stats;
|
||||
|
||||
Surface m_surface;
|
||||
|
||||
Device m_device;
|
||||
|
||||
Swapchain m_swapchain;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
151
modules/renderer/private/vk/context/device.cpp
Normal file
151
modules/renderer/private/vk/context/device.cpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
#include <renderer/vk/context/device.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Device::Device(const Surface &surface)
|
||||
{
|
||||
initialize_physical_device();
|
||||
initialize_logical_device();
|
||||
Instance::load_device_functions(m_device);
|
||||
initialize_queue(surface);
|
||||
}
|
||||
|
||||
Device::~Device()
|
||||
{
|
||||
if (m_device)
|
||||
{
|
||||
vkc(vk_device_wait_idle(m_device));
|
||||
vk_destroy_device(m_device, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::initialize_physical_device()
|
||||
{
|
||||
auto count = 0u;
|
||||
vkc(vk_enumerate_physical_devices(Instance::get(), &count, nullptr));
|
||||
ensure(count != 0u, "Failed to find any physical devices with Vulkan support");
|
||||
|
||||
auto devices = std::vector<VkPhysicalDevice>(count);
|
||||
vkc(vk_enumerate_physical_devices(Instance::get(), &count, devices.data()));
|
||||
|
||||
for (auto &device : devices)
|
||||
{
|
||||
auto properties = VkPhysicalDeviceProperties {};
|
||||
auto features = VkPhysicalDeviceFeatures {};
|
||||
|
||||
vk_get_physical_device_properties(device, &properties);
|
||||
vk_get_physical_device_features(device, &features);
|
||||
|
||||
if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
|
||||
&& features.geometryShader)
|
||||
{
|
||||
m_physical_device = device;
|
||||
}
|
||||
}
|
||||
ensure(m_physical_device, "Failed to find any suitable Vulkan physical device");
|
||||
}
|
||||
|
||||
void Device::initialize_logical_device()
|
||||
{
|
||||
const float priorities = .0f;
|
||||
|
||||
auto queue_info = VkDeviceQueueCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = find_suitable_queue_family(),
|
||||
.queueCount = 1u,
|
||||
.pQueuePriorities = &priorities,
|
||||
};
|
||||
|
||||
auto physical_device_features = VkPhysicalDeviceFeatures {};
|
||||
|
||||
auto extensions = std::vector<const char *> {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
};
|
||||
|
||||
auto device_info = VkDeviceCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||||
.queueCreateInfoCount = 1,
|
||||
.pQueueCreateInfos = &queue_info,
|
||||
.enabledExtensionCount = static_cast<uint32_t>(extensions.size()),
|
||||
.ppEnabledExtensionNames = extensions.data(),
|
||||
.pEnabledFeatures = &physical_device_features,
|
||||
};
|
||||
|
||||
ensure(
|
||||
!vk_create_device(m_physical_device, &device_info, nullptr, &m_device),
|
||||
"Failed to create logical vulkan device"
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::find_suitable_queue_family() const -> uint32_t
|
||||
{
|
||||
auto count = 0u;
|
||||
vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr);
|
||||
ensure(count != 0u, "Failed to find any physical devices with Vulkan support");
|
||||
|
||||
auto families = std::vector<VkQueueFamilyProperties>(count);
|
||||
vk_get_physical_device_queue_family_properties(m_physical_device, &count, families.data());
|
||||
|
||||
const auto required_flags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT;
|
||||
for (auto idx = 0u; auto &family : families)
|
||||
{
|
||||
if ((family.queueFlags & required_flags) == required_flags)
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
ensure(false, "Failed to find a suitable Vulkan queue family");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Device::initialize_queue(const Surface &surface)
|
||||
{
|
||||
vk_get_device_queue(m_device, find_suitable_queue_family(), 0, &m_queue);
|
||||
|
||||
auto count = uint32_t { 0u };
|
||||
vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr);
|
||||
|
||||
auto properties = std::vector<VkQueueFamilyProperties>(count);
|
||||
vk_get_physical_device_queue_family_properties(m_physical_device, &count, properties.data());
|
||||
|
||||
for (auto idx = uint32_t { 0u }; const auto &property : properties)
|
||||
{
|
||||
if (property.queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
||||
{
|
||||
m_graphics_queue_family_index = idx;
|
||||
}
|
||||
|
||||
auto has_presentation_support = VkBool32 { false };
|
||||
vkc(vk_get_physical_device_surface_support(
|
||||
m_physical_device,
|
||||
idx,
|
||||
surface.vk(),
|
||||
&has_presentation_support
|
||||
));
|
||||
if (has_presentation_support)
|
||||
{
|
||||
m_present_queue_family_index = idx;
|
||||
}
|
||||
|
||||
++idx;
|
||||
|
||||
if (m_graphics_queue_family_index != VK_QUEUE_FAMILY_IGNORED
|
||||
&& m_present_queue_family_index != VK_QUEUE_FAMILY_IGNORED)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ensure(
|
||||
m_graphics_queue_family_index != VK_QUEUE_FAMILY_IGNORED,
|
||||
"Failed to find graphics queue family"
|
||||
);
|
||||
|
||||
ensure(
|
||||
m_present_queue_family_index != VK_QUEUE_FAMILY_IGNORED,
|
||||
"Failed to find presentation queue family"
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
60
modules/renderer/private/vk/context/device.hpp
Normal file
60
modules/renderer/private/vk/context/device.hpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
#include <renderer/vk/context/surface.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Device
|
||||
{
|
||||
public:
|
||||
Device(const Surface &surface);
|
||||
|
||||
~Device();
|
||||
|
||||
Device(Device &&) = default;
|
||||
|
||||
Device(const Device &) = delete;
|
||||
|
||||
auto operator=(Device &&) -> Device & = default;
|
||||
|
||||
auto operator=(const Device &) const -> Device & = delete;
|
||||
|
||||
[[nodiscard]] auto vk() const -> VkDevice
|
||||
{
|
||||
return m_device;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto physical() const -> VkPhysicalDevice
|
||||
{
|
||||
return m_physical_device;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_family_indices() const -> std::array<uint32_t, 2>
|
||||
{
|
||||
return { m_graphics_queue_family_index, m_present_queue_family_index };
|
||||
}
|
||||
|
||||
private:
|
||||
void initialize_physical_device();
|
||||
|
||||
void initialize_logical_device();
|
||||
|
||||
void initialize_queue(const Surface &surface);
|
||||
|
||||
[[nodiscard]] auto find_suitable_queue_family() const -> uint32_t;
|
||||
|
||||
// logical device
|
||||
memory::NullOnMove<VkPhysicalDevice> m_physical_device = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkDevice> m_device = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkQueue> m_queue = VK_NULL_HANDLE;
|
||||
|
||||
uint32_t m_graphics_queue_family_index = VK_QUEUE_FAMILY_IGNORED;
|
||||
|
||||
uint32_t m_present_queue_family_index = VK_QUEUE_FAMILY_IGNORED;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,8 +1,8 @@
|
|||
#include <ranges>
|
||||
#include <renderer/vk/context.hpp>
|
||||
#include <app/system.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#error "Unsupported platform (currently)"
|
||||
#elif defined(__unix__)
|
||||
#include <dlfcn.h>
|
||||
namespace {
|
||||
|
@ -99,70 +99,35 @@ auto parse_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT message_sever
|
|||
-> app::SystemDiagnosis::Severity;
|
||||
|
||||
auto validation_layers_callback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT const message_severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT const message_types,
|
||||
VkDebugUtilsMessengerCallbackDataEXT const *const callback_data,
|
||||
void *const vulkan_user_data
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT message_types,
|
||||
VkDebugUtilsMessengerCallbackDataEXT const *callback_data,
|
||||
void *vulkan_user_data
|
||||
) -> VkBool32;
|
||||
|
||||
|
||||
Context::Context(const ecs::Entity &surface_entity, Ref<app::SystemStats> system_stats)
|
||||
: m_stats(std::move(system_stats))
|
||||
Instance::Instance()
|
||||
{
|
||||
ensure(m_stats, "Failed to create Vulkan Context: null stats");
|
||||
|
||||
load_library();
|
||||
load_global_functions();
|
||||
|
||||
initialize_instance();
|
||||
load_instance_functions();
|
||||
|
||||
initialize_debug_messenger();
|
||||
|
||||
initialize_physical_device();
|
||||
initialize_logical_device();
|
||||
load_device_functions();
|
||||
|
||||
initialize_surface(surface_entity);
|
||||
initialize_queue();
|
||||
initialize_swapchain();
|
||||
}
|
||||
|
||||
Context::~Context()
|
||||
Instance::~Instance()
|
||||
{
|
||||
try
|
||||
if (m_instance)
|
||||
{
|
||||
if (m_device)
|
||||
{
|
||||
vkc(vk_device_wait_idle(m_device));
|
||||
|
||||
for (auto &view : m_swapchain_image_views)
|
||||
{
|
||||
vk_destroy_image_view(m_device, view, nullptr);
|
||||
}
|
||||
|
||||
vk_destroy_swapchain_khr(m_device, m_swapchain, nullptr);
|
||||
vk_destroy_device(m_device, nullptr);
|
||||
}
|
||||
|
||||
if (m_instance)
|
||||
{
|
||||
vk_destroy_surface_khr(m_instance, m_surface, nullptr);
|
||||
vk_destroy_debug_messenger(m_instance, m_debug_messenger, nullptr);
|
||||
// TODO(Light): fix this issue
|
||||
// @ref:
|
||||
// https://git.light7734.com/light7734/light/commit/f268724034a2ceb63b90dc13aedf86a1eecac62e
|
||||
// vk_destroy_instance(m_instance, nullptr);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_err("Exception: {}", exp.what());
|
||||
vk_destroy_debug_messenger(m_instance, m_debug_messenger, nullptr);
|
||||
vk_destroy_instance(m_instance, nullptr);
|
||||
}
|
||||
|
||||
unload_library();
|
||||
}
|
||||
|
||||
|
||||
void Context::initialize_instance()
|
||||
void Instance::initialize_instance()
|
||||
{
|
||||
auto app_info = VkApplicationInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
||||
|
@ -206,11 +171,10 @@ void Context::initialize_instance()
|
|||
}
|
||||
|
||||
vkc(vk_create_instance(&instance_info, nullptr, &m_instance));
|
||||
|
||||
ensure(m_instance, "Failed to create vulkan instance");
|
||||
}
|
||||
|
||||
void Context::initialize_debug_messenger()
|
||||
void Instance::initialize_debug_messenger()
|
||||
{
|
||||
const auto info = VkDebugUtilsMessengerCreateInfoEXT {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
||||
|
@ -227,7 +191,7 @@ void Context::initialize_debug_messenger()
|
|||
|
||||
.pfnUserCallback = &validation_layers_callback,
|
||||
|
||||
.pUserData = &m_stats,
|
||||
.pUserData = {},
|
||||
};
|
||||
|
||||
ensure(
|
||||
|
@ -236,242 +200,7 @@ void Context::initialize_debug_messenger()
|
|||
);
|
||||
}
|
||||
|
||||
void Context::initialize_physical_device()
|
||||
{
|
||||
auto count = 0u;
|
||||
vkc(vk_enumerate_physical_devices(m_instance, &count, nullptr));
|
||||
ensure(count != 0u, "Failed to find any physical devices with Vulkan support");
|
||||
|
||||
auto devices = std::vector<VkPhysicalDevice>(count);
|
||||
vkc(vk_enumerate_physical_devices(m_instance, &count, devices.data()));
|
||||
|
||||
for (auto &device : devices)
|
||||
{
|
||||
auto properties = VkPhysicalDeviceProperties {};
|
||||
auto features = VkPhysicalDeviceFeatures {};
|
||||
|
||||
vk_get_physical_device_properties(device, &properties);
|
||||
vk_get_physical_device_features(device, &features);
|
||||
|
||||
if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
|
||||
&& features.geometryShader)
|
||||
{
|
||||
m_physical_device = device;
|
||||
}
|
||||
}
|
||||
ensure(m_physical_device, "Failed to find any suitable Vulkan physical device");
|
||||
}
|
||||
|
||||
void Context::initialize_logical_device()
|
||||
{
|
||||
const float priorities = .0f;
|
||||
|
||||
auto queue_info = VkDeviceQueueCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = find_suitable_queue_family(),
|
||||
.queueCount = 1u,
|
||||
.pQueuePriorities = &priorities,
|
||||
};
|
||||
|
||||
auto physical_device_features = VkPhysicalDeviceFeatures {};
|
||||
|
||||
auto extensions = std::vector<const char *> {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
};
|
||||
|
||||
auto device_info = VkDeviceCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
||||
.queueCreateInfoCount = 1,
|
||||
.pQueueCreateInfos = &queue_info,
|
||||
.enabledExtensionCount = static_cast<uint32_t>(extensions.size()),
|
||||
.ppEnabledExtensionNames = extensions.data(),
|
||||
.pEnabledFeatures = &physical_device_features,
|
||||
};
|
||||
|
||||
ensuree
|
||||
!vk_create_device(m_physical_device, &device_info, nullptr, &m_device),
|
||||
"Failed to create logical vulkan device"
|
||||
);
|
||||
}
|
||||
|
||||
void Context::initialize_surface(const ecs::Entity &surface_entity)
|
||||
{
|
||||
const auto &component = surface_entity.get<surface::SurfaceComponent>();
|
||||
|
||||
auto create_info = VkXlibSurfaceCreateInfoKHR {
|
||||
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
|
||||
.dpy = component.get_native_data().display,
|
||||
.window = component.get_native_data().window,
|
||||
};
|
||||
|
||||
vkc(vk_create_xlib_surface_khr(m_instance, &create_info, nullptr, &m_surface));
|
||||
|
||||
const auto &[width, height] = component.get_resolution();
|
||||
m_framebuffer_size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
}
|
||||
|
||||
void Context::initialize_queue()
|
||||
{
|
||||
vk_get_device_queue(m_device, find_suitable_queue_family(), 0, &m_queue);
|
||||
|
||||
auto count = uint32_t { 0u };
|
||||
vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr);
|
||||
|
||||
auto properties = std::vector<VkQueueFamilyProperties>(count);
|
||||
vk_get_physical_device_queue_family_properties(m_physical_device, &count, properties.data());
|
||||
|
||||
for (auto idx = uint32_t { 0u }; const auto &property : properties)
|
||||
{
|
||||
if (property.queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
||||
{
|
||||
m_graphics_queue_family_index = idx;
|
||||
}
|
||||
|
||||
auto has_presentation_support = VkBool32 { false };
|
||||
vkc(vk_get_physical_device_surface_support(
|
||||
m_physical_device,
|
||||
idx,
|
||||
m_surface,
|
||||
&has_presentation_support
|
||||
));
|
||||
if (has_presentation_support)
|
||||
{
|
||||
m_present_queue_family_index = idx;
|
||||
}
|
||||
|
||||
++idx;
|
||||
|
||||
if (m_graphics_queue_family_index != VK_QUEUE_FAMILY_IGNORED
|
||||
&& m_present_queue_family_index != VK_QUEUE_FAMILY_IGNORED)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ensure(
|
||||
m_graphics_queue_family_index != VK_QUEUE_FAMILY_IGNORED,
|
||||
"Failed to find graphics queue family"
|
||||
);
|
||||
|
||||
ensure(
|
||||
m_present_queue_family_index != VK_QUEUE_FAMILY_IGNORED,
|
||||
"Failed to find presentation queue family"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void Context::initialize_swapchain()
|
||||
{
|
||||
auto capabilities = VkSurfaceCapabilitiesKHR {};
|
||||
vkc(vk_get_physical_device_surface_capabilities(m_physical_device, m_surface, &capabilities));
|
||||
|
||||
auto count = uint32_t { 0 };
|
||||
vkc(vk_get_physical_device_surface_formats(m_physical_device, m_surface, &count, nullptr));
|
||||
|
||||
auto formats = std::vector<VkSurfaceFormatKHR>(count);
|
||||
vkc(
|
||||
vk_get_physical_device_surface_formats(m_physical_device, m_surface, &count, formats.data())
|
||||
);
|
||||
ensure(!formats.empty(), "Surface has no formats!");
|
||||
|
||||
// TODO(Light): parameterize
|
||||
constexpr auto desired_swapchain_image_count = uint32_t { 3 };
|
||||
const auto surface_format = formats.front();
|
||||
|
||||
const auto queue_indices = std::array<uint32_t, 2> {
|
||||
m_graphics_queue_family_index,
|
||||
m_present_queue_family_index,
|
||||
};
|
||||
|
||||
auto create_info = VkSwapchainCreateInfoKHR {
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.surface = m_surface,
|
||||
.minImageCount = get_optimal_swapchain_image_count(
|
||||
capabilities,
|
||||
desired_swapchain_image_count
|
||||
),
|
||||
.imageFormat = surface_format.format,
|
||||
.imageColorSpace = surface_format.colorSpace,
|
||||
.imageExtent = m_framebuffer_size,
|
||||
.imageArrayLayers = 1u,
|
||||
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = queue_indices.size(),
|
||||
.pQueueFamilyIndices = queue_indices.data(),
|
||||
.preTransform = capabilities.currentTransform,
|
||||
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
|
||||
.presentMode = VK_PRESENT_MODE_FIFO_RELAXED_KHR, // TODO(Light): parameterize
|
||||
.clipped = VK_TRUE,
|
||||
.oldSwapchain = nullptr,
|
||||
};
|
||||
|
||||
vkc(vk_create_swapchain_khr(m_device, &create_info, nullptr, &m_swapchain));
|
||||
vkc(vk_device_wait_idle(m_device));
|
||||
|
||||
auto image_count = uint32_t { 0u };
|
||||
vk_get_swapchain_images_khr(m_device, m_swapchain, &image_count, nullptr);
|
||||
|
||||
m_swapchain_images.resize(image_count);
|
||||
m_swapchain_image_views.resize(image_count);
|
||||
vk_get_swapchain_images_khr(m_device, m_swapchain, &image_count, m_swapchain_images.data());
|
||||
|
||||
for (auto [image, view] : std::views::zip(m_swapchain_images, m_swapchain_image_views))
|
||||
{
|
||||
auto create_info = VkImageViewCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = surface_format.format,
|
||||
.components = VkComponentMapping {
|
||||
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
.g = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
.b = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
},
|
||||
.subresourceRange = VkImageSubresourceRange {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0u,
|
||||
.levelCount = 1u,
|
||||
.baseArrayLayer = 0u,
|
||||
.layerCount = 1u,
|
||||
}
|
||||
};
|
||||
|
||||
vkc(vk_create_image_view(m_device, &create_info, nullptr, &view));
|
||||
}
|
||||
}
|
||||
|
||||
auto Context::get_optimal_swapchain_image_count(
|
||||
VkSurfaceCapabilitiesKHR capabilities,
|
||||
uint32_t desired_image_count
|
||||
) -> uint32_t
|
||||
{
|
||||
const auto min_image_count = capabilities.minImageCount;
|
||||
const auto max_image_count = capabilities.maxImageCount;
|
||||
|
||||
const auto has_max_limit = max_image_count != 0;
|
||||
|
||||
// Desired image count is in range
|
||||
if ((!has_max_limit || max_image_count >= desired_image_count)
|
||||
&& min_image_count <= desired_image_count)
|
||||
{
|
||||
return desired_image_count;
|
||||
}
|
||||
|
||||
// Fall-back to 2 if in ange
|
||||
if (min_image_count <= 2 && max_image_count >= 2)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Fall-back to min_image_count
|
||||
return min_image_count;
|
||||
}
|
||||
|
||||
void Context::load_library()
|
||||
void Instance::load_library()
|
||||
{
|
||||
library = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
|
||||
ensure(library, "Failed to dlopen libvulkan.so");
|
||||
|
@ -483,7 +212,18 @@ void Context::load_library()
|
|||
ensure(vk_get_instance_proc_address, "Failed to load vulkan function: vkGetInstanceProcAddr");
|
||||
}
|
||||
|
||||
void Context::load_global_functions()
|
||||
void Instance::unload_library()
|
||||
{
|
||||
if (!library)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// dlclose(library);
|
||||
// library = nullptr;
|
||||
}
|
||||
|
||||
void Instance::load_global_functions()
|
||||
{
|
||||
constexpr auto load_fn = []<typename T>(T &pfn, const char *fn_name) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
|
@ -497,7 +237,7 @@ void Context::load_global_functions()
|
|||
load_fn(vk_enumerate_instance_layer_properties, "vkEnumerateInstanceLayerProperties");
|
||||
}
|
||||
|
||||
void Context::load_instance_functions()
|
||||
void Instance::load_instance_functions()
|
||||
{
|
||||
const auto load_fn = [&]<typename T>(T &pfn, const char *fn_name) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
|
@ -541,11 +281,11 @@ void Context::load_instance_functions()
|
|||
load_fn(vk_destroy_surface_khr, "vkDestroySurfaceKHR");
|
||||
}
|
||||
|
||||
void Context::load_device_functions()
|
||||
void Instance::load_device_functions_impl(VkDevice device)
|
||||
{
|
||||
const auto load_fn = [&]<typename T>(T &pfn, const char *fn_name) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
pfn = reinterpret_cast<T>(vk_get_device_proc_address(m_device, fn_name));
|
||||
pfn = reinterpret_cast<T>(vk_get_device_proc_address(device, fn_name));
|
||||
ensure(pfn, "Failed to load vulkan device function: {}", fn_name);
|
||||
// log_trc("Loaded device function: {}", fn_name);
|
||||
};
|
||||
|
@ -592,28 +332,6 @@ void Context::load_device_functions()
|
|||
load_fn(vk_cmd_set_scissors, "vkCmdSetScissor");
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Context::find_suitable_queue_family() const -> uint32_t
|
||||
{
|
||||
auto count = 0u;
|
||||
vk_get_physical_device_queue_family_properties(m_physical_device, &count, nullptr);
|
||||
ensure(count != 0u, "Failed to find any physical devices with Vulkan support");
|
||||
|
||||
auto families = std::vector<VkQueueFamilyProperties>(count);
|
||||
vk_get_physical_device_queue_family_properties(m_physical_device, &count, families.data());
|
||||
|
||||
const auto required_flags = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT;
|
||||
for (auto idx = 0u; auto &family : families)
|
||||
{
|
||||
if ((family.queueFlags & required_flags) == required_flags)
|
||||
{
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
ensure(false, "Failed to find a suitable Vulkan queue family");
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto parse_message_type(VkDebugUtilsMessageTypeFlagsEXT message_types) -> const char *
|
||||
{
|
||||
if (message_types == VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT)
|
||||
|
@ -660,7 +378,11 @@ auto validation_layers_callback(
|
|||
void *const vulkan_user_data
|
||||
) -> VkBool32
|
||||
{
|
||||
std::cout << callback_data->pMessage << std::endl;
|
||||
return VK_FALSE;
|
||||
|
||||
log_dbg("VALIDATION: {}", callback_data->pMessage);
|
||||
|
||||
ensure(vulkan_user_data, "Validation layers's user data is not set!");
|
||||
|
||||
auto stats = *(Ref<app::SystemStats> *)vulkan_user_data; // NOLINT
|
||||
|
@ -690,5 +412,4 @@ auto validation_layers_callback(
|
|||
return static_cast<VkBool32>(VK_FALSE);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -5,16 +5,17 @@
|
|||
#include <vulkan/vulkan.h>
|
||||
#include <vulkan/vulkan_xlib.h>
|
||||
|
||||
//
|
||||
#include <app/system.hpp>
|
||||
#include <ecs/entity.hpp>
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <surface/components.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
using memory::NullOnMove;
|
||||
|
||||
inline void vkc(VkResult result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Vulkan call failed with result: {}", std::to_underlying(result))
|
||||
};
|
||||
}
|
||||
}
|
||||
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
// global functions
|
||||
extern PFN_vkGetInstanceProcAddr vk_get_instance_proc_address;
|
||||
|
@ -97,118 +98,64 @@ extern PFN_vkCreateXlibSurfaceKHR vk_create_xlib_surface_khr;
|
|||
extern PFN_vkDestroySurfaceKHR vk_destroy_surface_khr;
|
||||
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
inline void vkc(VkResult result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Vulkan call failed with result: {}", std::to_underlying(result))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Context
|
||||
/**
|
||||
* Responsible for dynamically loading Vulkan library/functions.
|
||||
*
|
||||
* @note: The delayed vkInstance destruction is due to a work-around for libx11:
|
||||
* @ref:
|
||||
* https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/commit/0017308648b6bf8eef10ef0ffb9470576c0c2e9e
|
||||
* https://www.xfree86.org/4.7.0/DRI11.html
|
||||
* https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/issues/1894
|
||||
*/
|
||||
class Instance
|
||||
{
|
||||
public:
|
||||
Context(const ecs::Entity &surface_entity, Ref<app::SystemStats> system_stats);
|
||||
|
||||
~Context();
|
||||
|
||||
Context(Context &&other) noexcept = default;
|
||||
|
||||
auto operator=(Context &&other) noexcept -> Context & = default;
|
||||
|
||||
Context(const Context &) = delete;
|
||||
|
||||
auto operator=(const Context &) -> Context & = delete;
|
||||
|
||||
[[nodiscard]] auto instance() -> VkInstance
|
||||
static auto get() -> VkInstance
|
||||
{
|
||||
return m_instance;
|
||||
return Instance::instance().m_instance;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto physical_device() -> VkPhysicalDevice
|
||||
static auto load_device_functions(VkDevice device)
|
||||
{
|
||||
return m_physical_device;
|
||||
instance().load_device_functions_impl(device);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto device() -> VkDevice
|
||||
{
|
||||
return m_device;
|
||||
}
|
||||
Instance(Instance &&) = delete;
|
||||
|
||||
auto queue() -> VkQueue
|
||||
{
|
||||
return m_queue;
|
||||
};
|
||||
Instance(const Instance &) = delete;
|
||||
|
||||
auto debug_messenger() -> VkDebugUtilsMessengerEXT
|
||||
{
|
||||
return m_debug_messenger;
|
||||
};
|
||||
auto operator=(const Instance &) -> Instance & = delete;
|
||||
|
||||
[[nodiscard]] auto get_stats() const -> const app::SystemStats &
|
||||
{
|
||||
return *m_stats;
|
||||
}
|
||||
auto operator=(Instance &&) -> Instance & = delete;
|
||||
|
||||
private:
|
||||
static auto instance() -> Instance &
|
||||
{
|
||||
static auto instance = Instance {};
|
||||
return instance;
|
||||
}
|
||||
|
||||
Instance();
|
||||
|
||||
~Instance();
|
||||
|
||||
void initialize_instance();
|
||||
|
||||
void initialize_debug_messenger();
|
||||
|
||||
void initialize_physical_device();
|
||||
|
||||
void initialize_logical_device();
|
||||
|
||||
void initialize_queue();
|
||||
|
||||
void initialize_surface(const ecs::Entity &surface_entity);
|
||||
|
||||
void initialize_swapchain();
|
||||
|
||||
void load_library();
|
||||
|
||||
void unload_library();
|
||||
|
||||
void load_global_functions();
|
||||
|
||||
void load_instance_functions();
|
||||
|
||||
void load_device_functions();
|
||||
void load_device_functions_impl(VkDevice device);
|
||||
|
||||
auto get_optimal_swapchain_image_count(
|
||||
VkSurfaceCapabilitiesKHR capabilities,
|
||||
uint32_t desired_image_count = 3
|
||||
) -> uint32_t;
|
||||
VkInstance m_instance = VK_NULL_HANDLE;
|
||||
|
||||
[[nodiscard]] auto find_suitable_queue_family() const -> uint32_t;
|
||||
|
||||
Ref<ecs::Registry> m_registry;
|
||||
|
||||
NullOnMove<VkInstance> m_instance = VK_NULL_HANDLE;
|
||||
|
||||
NullOnMove<VkPhysicalDevice> m_physical_device = VK_NULL_HANDLE;
|
||||
|
||||
NullOnMove<VkDevice> m_device = VK_NULL_HANDLE;
|
||||
|
||||
NullOnMove<VkQueue> m_queue = VK_NULL_HANDLE;
|
||||
|
||||
NullOnMove<VkDebugUtilsMessengerEXT> m_debug_messenger = VK_NULL_HANDLE;
|
||||
|
||||
NullOnMove<VkSurfaceKHR> m_surface = VK_NULL_HANDLE;
|
||||
|
||||
VkExtent2D m_framebuffer_size {};
|
||||
|
||||
uint32_t m_graphics_queue_family_index = VK_QUEUE_FAMILY_IGNORED;
|
||||
|
||||
uint32_t m_present_queue_family_index = VK_QUEUE_FAMILY_IGNORED;
|
||||
|
||||
NullOnMove<VkSwapchainKHR> m_swapchain = VK_NULL_HANDLE;
|
||||
|
||||
std::vector<VkImage> m_swapchain_images;
|
||||
|
||||
std::vector<VkImageView> m_swapchain_image_views;
|
||||
|
||||
Ref<app::SystemStats> m_stats;
|
||||
VkDebugUtilsMessengerEXT m_debug_messenger = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
34
modules/renderer/private/vk/context/surface.cpp
Normal file
34
modules/renderer/private/vk/context/surface.cpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include <renderer/vk/context/surface.hpp>
|
||||
#include <surface/components.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Surface::Surface(const ecs::Entity &surface_entity)
|
||||
{
|
||||
const auto &component = surface_entity.get<surface::SurfaceComponent>();
|
||||
|
||||
auto create_info = VkXlibSurfaceCreateInfoKHR {
|
||||
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
|
||||
.dpy = component.get_native_data().display,
|
||||
.window = component.get_native_data().window,
|
||||
};
|
||||
|
||||
auto *instance = Instance::get();
|
||||
vkc(vk_create_xlib_surface_khr(instance, &create_info, nullptr, &m_surface));
|
||||
|
||||
const auto &[width, height] = component.get_resolution();
|
||||
m_framebuffer_size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
}
|
||||
|
||||
Surface::~Surface()
|
||||
{
|
||||
if (Instance::get())
|
||||
{
|
||||
vk_destroy_surface_khr(Instance::get(), m_surface, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
40
modules/renderer/private/vk/context/surface.hpp
Normal file
40
modules/renderer/private/vk/context/surface.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <ecs/entity.hpp>
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Surface
|
||||
{
|
||||
public:
|
||||
Surface(const ecs::Entity &surface_entity);
|
||||
|
||||
~Surface();
|
||||
|
||||
Surface(Surface &&) = default;
|
||||
|
||||
Surface(const Surface &) = delete;
|
||||
|
||||
auto operator=(Surface &&) -> Surface & = default;
|
||||
|
||||
auto operator=(const Surface &) const -> Surface & = delete;
|
||||
|
||||
[[nodiscard]] auto vk() const -> VkSurfaceKHR
|
||||
{
|
||||
return m_surface;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_framebuffer_size() const -> VkExtent2D
|
||||
{
|
||||
return m_framebuffer_size;
|
||||
}
|
||||
|
||||
private:
|
||||
memory::NullOnMove<VkSurfaceKHR> m_surface = VK_NULL_HANDLE;
|
||||
|
||||
VkExtent2D m_framebuffer_size {};
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
135
modules/renderer/private/vk/context/swapchain.cpp
Normal file
135
modules/renderer/private/vk/context/swapchain.cpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
#include <ranges>
|
||||
#include <renderer/vk/context/swapchain.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Swapchain::Swapchain(const Device &device, const Surface &surface): m_device(device.vk())
|
||||
{
|
||||
auto *physical_device = device.physical();
|
||||
|
||||
auto capabilities = VkSurfaceCapabilitiesKHR {};
|
||||
vkc(vk_get_physical_device_surface_capabilities(physical_device, surface.vk(), &capabilities));
|
||||
|
||||
auto count = uint32_t { 0 };
|
||||
vkc(vk_get_physical_device_surface_formats(physical_device, surface.vk(), &count, nullptr));
|
||||
|
||||
auto formats = std::vector<VkSurfaceFormatKHR>(count);
|
||||
vkc(vk_get_physical_device_surface_formats(
|
||||
physical_device,
|
||||
surface.vk(),
|
||||
&count,
|
||||
formats.data()
|
||||
));
|
||||
ensure(!formats.empty(), "Surface has no formats!");
|
||||
|
||||
// TODO(Light): parameterize
|
||||
constexpr auto desired_swapchain_image_count = uint32_t { 3 };
|
||||
const auto surface_format = formats.front();
|
||||
const auto queue_indices = device.get_family_indices();
|
||||
|
||||
auto create_info = VkSwapchainCreateInfoKHR {
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.surface = surface.vk(),
|
||||
.minImageCount = get_optimal_image_count(capabilities, desired_swapchain_image_count),
|
||||
.imageFormat = surface_format.format,
|
||||
.imageColorSpace = surface_format.colorSpace,
|
||||
.imageExtent = surface.get_framebuffer_size(),
|
||||
.imageArrayLayers = 1u,
|
||||
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = queue_indices.size(),
|
||||
.pQueueFamilyIndices = queue_indices.data(),
|
||||
.preTransform = capabilities.currentTransform,
|
||||
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
|
||||
.presentMode = VK_PRESENT_MODE_FIFO_RELAXED_KHR, // TODO(Light): parameterize
|
||||
.clipped = VK_TRUE,
|
||||
.oldSwapchain = nullptr,
|
||||
};
|
||||
|
||||
vkc(vk_create_swapchain_khr(device.vk(), &create_info, nullptr, &m_swapchain));
|
||||
vkc(vk_device_wait_idle(device.vk()));
|
||||
|
||||
auto image_count = uint32_t { 0u };
|
||||
vk_get_swapchain_images_khr(device.vk(), m_swapchain, &image_count, nullptr);
|
||||
|
||||
m_swapchain_images.resize(image_count);
|
||||
m_swapchain_image_views.resize(image_count);
|
||||
vk_get_swapchain_images_khr(device.vk(), m_swapchain, &image_count, m_swapchain_images.data());
|
||||
|
||||
for (auto [image, view] : std::views::zip(m_swapchain_images, m_swapchain_image_views))
|
||||
{
|
||||
auto create_info = VkImageViewCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = surface_format.format,
|
||||
.components = VkComponentMapping {
|
||||
.r = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
.g = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
.b = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
.a = VK_COMPONENT_SWIZZLE_IDENTITY,
|
||||
},
|
||||
.subresourceRange = VkImageSubresourceRange {
|
||||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
|
||||
.baseMipLevel = 0u,
|
||||
.levelCount = 1u,
|
||||
.baseArrayLayer = 0u,
|
||||
.layerCount = 1u,
|
||||
}
|
||||
};
|
||||
|
||||
vkc(vk_create_image_view(device.vk(), &create_info, nullptr, &view));
|
||||
}
|
||||
}
|
||||
|
||||
Swapchain::~Swapchain()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_device)
|
||||
{
|
||||
vkc(vk_device_wait_idle(m_device));
|
||||
for (auto &view : m_swapchain_image_views)
|
||||
{
|
||||
vk_destroy_image_view(m_device, view, nullptr);
|
||||
}
|
||||
|
||||
vk_destroy_swapchain_khr(m_device, m_swapchain, nullptr);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_err("Failed to destroy swapchain:");
|
||||
log_err("\twhat: {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Swapchain::get_optimal_image_count(
|
||||
VkSurfaceCapabilitiesKHR capabilities,
|
||||
uint32_t desired_image_count
|
||||
) const -> uint32_t
|
||||
{
|
||||
const auto min_image_count = capabilities.minImageCount;
|
||||
const auto max_image_count = capabilities.maxImageCount;
|
||||
|
||||
const auto has_max_limit = max_image_count != 0;
|
||||
|
||||
// Desired image count is in range
|
||||
if ((!has_max_limit || max_image_count >= desired_image_count)
|
||||
&& min_image_count <= desired_image_count)
|
||||
{
|
||||
return desired_image_count;
|
||||
}
|
||||
|
||||
// Fall-back to 2 if in ange
|
||||
if (min_image_count <= 2 && max_image_count >= 2)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Fall-back to min_image_count
|
||||
return min_image_count;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lt::renderer::vk
|
39
modules/renderer/private/vk/context/swapchain.hpp
Normal file
39
modules/renderer/private/vk/context/swapchain.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/vk/context/device.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Swapchain
|
||||
{
|
||||
public:
|
||||
Swapchain(const Device &device, const Surface &surface);
|
||||
|
||||
~Swapchain();
|
||||
|
||||
Swapchain(Swapchain &&) = default;
|
||||
|
||||
Swapchain(const Swapchain &) = delete;
|
||||
|
||||
auto operator=(Swapchain &&) -> Swapchain & = default;
|
||||
|
||||
auto operator=(const Swapchain &) const -> Swapchain & = delete;
|
||||
|
||||
private:
|
||||
[[nodiscard]] auto get_optimal_image_count(
|
||||
VkSurfaceCapabilitiesKHR capabilities,
|
||||
uint32_t desired_image_count
|
||||
) const -> uint32_t;
|
||||
|
||||
memory::NullOnMove<VkDevice> m_device;
|
||||
|
||||
memory::NullOnMove<VkSwapchainKHR> m_swapchain = VK_NULL_HANDLE;
|
||||
|
||||
std::vector<VkImage> m_swapchain_images;
|
||||
|
||||
std::vector<VkImageView> m_swapchain_image_views;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
18
modules/renderer/private/vk/pipeline.cpp
Normal file
18
modules/renderer/private/vk/pipeline.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#include <renderer/vk/pipeline.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Pipeline::Pipeline(CreateInfo info): m_context(std::move(info.context))
|
||||
{
|
||||
ensure(m_context, "Failed to create vk pipeline: null context");
|
||||
}
|
||||
|
||||
Pipeline::~Pipeline()
|
||||
{
|
||||
if (m_context)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
36
modules/renderer/private/vk/pipeline.hpp
Normal file
36
modules/renderer/private/vk/pipeline.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Pipeline
|
||||
{
|
||||
public:
|
||||
struct CreateInfo
|
||||
{
|
||||
Ref<Context> context;
|
||||
};
|
||||
|
||||
Pipeline(CreateInfo info);
|
||||
|
||||
~Pipeline();
|
||||
|
||||
Pipeline(Pipeline &&) = default;
|
||||
|
||||
Pipeline(const Pipeline &) = delete;
|
||||
|
||||
auto operator=(Pipeline &&) -> Pipeline & = default;
|
||||
|
||||
auto operator=(const Pipeline &) -> Pipeline & = delete;
|
||||
|
||||
private:
|
||||
VkPipeline m_pipeline = {};
|
||||
|
||||
VkPipelineLayout m_pipeline_layout = {};
|
||||
|
||||
Ref<Context> m_context;
|
||||
};
|
||||
|
||||
}; // namespace lt::renderer::vk
|
69
modules/renderer/private/vk/pipeline.test.cpp
Normal file
69
modules/renderer/private/vk/pipeline.test.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#include <ranges>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/pipeline.hpp>
|
||||
#include <surface/system.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
||||
using namespace lt;
|
||||
|
||||
using std::ignore;
|
||||
using test::Case;
|
||||
using test::expect_throw;
|
||||
using test::expect_true;
|
||||
using test::Suite;
|
||||
|
||||
using renderer::vk::Context;
|
||||
using renderer::vk::Pipeline;
|
||||
|
||||
constexpr auto resolution = math::uvec2 { 800, 600 };
|
||||
|
||||
class VkPipelineTest
|
||||
{
|
||||
public:
|
||||
VkPipelineTest()
|
||||
{
|
||||
m_registry = create_ref<ecs::Registry>();
|
||||
|
||||
m_surface_system = create_ref<surface::System>(m_registry);
|
||||
|
||||
m_surface_entity = create_scope<ecs::Entity>(m_registry, m_registry->create_entity());
|
||||
m_surface_entity->add<surface::SurfaceComponent>(surface::SurfaceComponent::CreateInfo {
|
||||
.title = "",
|
||||
.resolution = resolution,
|
||||
});
|
||||
|
||||
m_context = create_ref<Context>(*m_surface_entity, m_stats);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto context() -> Ref<Context>
|
||||
{
|
||||
return m_context;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto device() -> VkDevice
|
||||
{
|
||||
return m_context->device().vk();
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<app::SystemStats> m_stats;
|
||||
|
||||
Ref<ecs::Registry> m_registry;
|
||||
|
||||
Ref<surface::System> m_surface_system;
|
||||
|
||||
Scope<ecs::Entity> m_surface_entity;
|
||||
|
||||
Ref<Context> m_context;
|
||||
};
|
||||
|
||||
Suite raii = [] {
|
||||
Case { "happy path won't throw" } = [] {
|
||||
auto fixture = VkPipelineTest {};
|
||||
std::ignore = Pipeline { { .context = fixture.context() } };
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { std::ignore = Pipeline { { .context = nullptr } }; });
|
||||
};
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#define VK_NO_PROTOTYPES
|
||||
#define VK_USE_PLATFORM_XLIB_KHR
|
||||
#include <vulkan/vulkan.h>
|
||||
#include <vulkan/vulkan_xlib.h>
|
||||
|
||||
//
|
||||
#include <ecs/entity.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Surface
|
||||
{
|
||||
public:
|
||||
Surface(ecs::Entity entity)
|
||||
{
|
||||
}
|
||||
|
||||
~Surface();
|
||||
|
||||
private:
|
||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,22 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#define VK_NO_PROTOTYPES
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Swapchain
|
||||
{
|
||||
public:
|
||||
Swapchain()
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
VkSwapchainKHR m_swapchain {};
|
||||
|
||||
std::vector<VkImage> images;
|
||||
std::vector<VkImageView> image_views;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
#include <app/system.hpp>
|
||||
#include <ecs/entity.hpp>
|
||||
#include <renderer/validation.hpp>
|
||||
#include <renderer/vk/context.hpp>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
|
@ -19,8 +18,10 @@ public:
|
|||
|
||||
[[nodiscard]] System(CreateInfo info)
|
||||
: m_registry(std::move(info.registry))
|
||||
, m_context(info.surface_entity, std::move(info.system_stats))
|
||||
, m_stats(info.system_stats)
|
||||
, m_context(info.surface_entity, info.system_stats)
|
||||
{
|
||||
ensure(m_stats, "Failed to initialize system: null stats");
|
||||
ensure(m_registry, "Failed to initialize renderer system: null registry");
|
||||
}
|
||||
|
||||
|
@ -50,7 +51,7 @@ public:
|
|||
|
||||
[[nodiscard]] auto get_stats() const -> const app::SystemStats &
|
||||
{
|
||||
return m_context.get_stats();
|
||||
return *m_stats;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
|
||||
|
@ -61,7 +62,7 @@ public:
|
|||
private:
|
||||
Ref<ecs::Registry> m_registry;
|
||||
|
||||
renderer::Validation m_validation;
|
||||
Ref<app::SystemStats> m_stats;
|
||||
|
||||
vk::Context m_context;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue