feat(renderer): separate things into backend/frontend + major vk backend refactors
Some checks reported errors
continuous-integration/drone/push Build was killed
Some checks reported errors
continuous-integration/drone/push Build was killed
This commit is contained in:
parent
9ca94dc7d7
commit
16f3a80fd3
68 changed files with 2407 additions and 1795 deletions
|
@ -128,7 +128,7 @@ cppcoreguidelines-pro-type-const-cast,
|
|||
cppcoreguidelines-pro-type-cstyle-cast,
|
||||
cppcoreguidelines-pro-type-member-init,
|
||||
cppcoreguidelines-pro-type-reinterpret-cast,
|
||||
cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
-cppcoreguidelines-pro-type-static-cast-downcast,
|
||||
cppcoreguidelines-pro-type-vararg,
|
||||
cppcoreguidelines-rvalue-reference-param-not-moved,
|
||||
cppcoreguidelines-slicing,
|
||||
|
|
3
modules/renderer/.clangd
Normal file
3
modules/renderer/.clangd
Normal file
|
@ -0,0 +1,3 @@
|
|||
Diagnostics:
|
||||
UnusedIncludes: None
|
||||
MissingIncludes: None
|
|
@ -1,14 +1,21 @@
|
|||
add_library_module(renderer
|
||||
system.cpp
|
||||
vk/debug/messenger.cpp
|
||||
vk/context/instance.cpp
|
||||
vk/context/surface.cpp
|
||||
vk/context/device.cpp
|
||||
vk/context/swapchain.cpp
|
||||
vk/context/context.cpp
|
||||
vk/renderer/pass.cpp
|
||||
vk/renderer/renderer.cpp
|
||||
vk/pipeline.cpp
|
||||
|
||||
# Vulkan - backend
|
||||
backend/vk/messenger.cpp
|
||||
backend/vk/context/context.cpp
|
||||
backend/vk/context/device.cpp
|
||||
backend/vk/context/gpu.cpp
|
||||
backend/vk/context/instance.cpp
|
||||
backend/vk/context/surface.cpp
|
||||
backend/vk/context/swapchain.cpp
|
||||
backend/vk/renderer/pass.cpp
|
||||
backend/vk/renderer/renderer.cpp
|
||||
|
||||
# Vulkan - frontend
|
||||
frontend/messenger.cpp
|
||||
frontend/context/context.cpp
|
||||
frontend/renderer/renderer.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(renderer
|
||||
|
@ -23,21 +30,21 @@ PRIVATE
|
|||
pthread
|
||||
)
|
||||
|
||||
add_test_module(renderer
|
||||
system.test.cpp
|
||||
vk/test_utils.cpp
|
||||
vk/debug/messenger.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/renderer/pass.test.cpp
|
||||
vk/renderer/renderer.test.cpp
|
||||
vk/pipeline.test.cpp
|
||||
)
|
||||
target_link_libraries(renderer_tests
|
||||
PRIVATE
|
||||
surface
|
||||
pthread
|
||||
)
|
||||
# add_test_module(renderer
|
||||
# system.test.cpp
|
||||
# vk/test_utils.cpp
|
||||
# vk/debug/messenger.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/renderer/pass.test.cpp
|
||||
# vk/renderer/renderer.test.cpp
|
||||
# vk/pipeline.test.cpp
|
||||
# )
|
||||
# target_link_libraries(renderer_tests
|
||||
# PRIVATE
|
||||
# surface
|
||||
# pthread
|
||||
# )
|
||||
|
|
40
modules/renderer/private/backend/vk/context/context.cpp
Normal file
40
modules/renderer/private/backend/vk/context/context.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#include <renderer/backend/vk/context/context.hpp>
|
||||
#include <renderer/backend/vk/context/device.hpp>
|
||||
#include <renderer/backend/vk/context/gpu.hpp>
|
||||
#include <renderer/backend/vk/context/instance.hpp>
|
||||
#include <renderer/backend/vk/context/surface.hpp>
|
||||
#include <renderer/backend/vk/context/swapchain.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Context::Context(const ecs::Entity &surface_entity)
|
||||
: m_instance(Instance::get())
|
||||
, m_surface(create_scope<Surface>(m_instance, surface_entity))
|
||||
, m_gpu(create_scope<Gpu>(m_instance))
|
||||
, m_device(create_scope<Device>(m_gpu.get(), m_surface.get()))
|
||||
, m_swapchain(create_scope<Swapchain>(m_surface.get(), m_gpu.get(), m_device.get()))
|
||||
{
|
||||
ensure(
|
||||
static_cast<Instance *>(m_instance)->vk(),
|
||||
"Failed to create vulkan context: null instance"
|
||||
);
|
||||
|
||||
ensure(
|
||||
static_cast<Surface *>(m_surface.get())->vk(),
|
||||
"Failed to create vulkan context: null surface"
|
||||
);
|
||||
|
||||
ensure(static_cast<Gpu *>(m_gpu.get())->vk(), "Failed to create vulkan context: null gpu");
|
||||
|
||||
ensure(
|
||||
static_cast<Device *>(m_device.get())->vk(),
|
||||
"Failed to create vulkan context: null device"
|
||||
);
|
||||
|
||||
ensure(
|
||||
static_cast<Swapchain *>(m_swapchain.get())->vk(),
|
||||
"Failed to create vulkan context: null swapchain"
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
60
modules/renderer/private/backend/vk/context/context.hpp
Normal file
60
modules/renderer/private/backend/vk/context/context.hpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
#pragma once
|
||||
|
||||
#include <ecs/entity.hpp>
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/frontend/context/context.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
using memory::NullOnMove;
|
||||
|
||||
/** A layer of glue between main graphics objects. */
|
||||
class Context: public IContext
|
||||
{
|
||||
public:
|
||||
Context(const ecs::Entity &surface_entity);
|
||||
|
||||
[[nodiscard]] auto instance() const -> IInstance * override
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto gpu() const -> IGpu * override
|
||||
{
|
||||
return m_gpu.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto device() const -> IDevice * override
|
||||
{
|
||||
return m_device.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto swapchain() const -> ISwapchain * override
|
||||
{
|
||||
return m_swapchain.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto surface() const -> ISurface * override
|
||||
{
|
||||
return m_surface.get();
|
||||
}
|
||||
|
||||
void recreate_swapchain() override
|
||||
{
|
||||
m_swapchain.reset();
|
||||
// m_swapchain = create_scope<vk::Swapchain>(m_device, m_surface);
|
||||
}
|
||||
|
||||
private:
|
||||
IInstance *m_instance;
|
||||
|
||||
Scope<ISurface> m_surface;
|
||||
|
||||
Scope<IGpu> m_gpu;
|
||||
|
||||
Scope<IDevice> m_device;
|
||||
|
||||
Scope<ISwapchain> m_swapchain;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
384
modules/renderer/private/backend/vk/context/device.cpp
Normal file
384
modules/renderer/private/backend/vk/context/device.cpp
Normal file
|
@ -0,0 +1,384 @@
|
|||
//
|
||||
#include <renderer/backend/vk/context/device.hpp>
|
||||
#include <renderer/backend/vk/context/gpu.hpp>
|
||||
#include <renderer/backend/vk/context/surface.hpp>
|
||||
#include <renderer/backend/vk/utils.hpp>
|
||||
|
||||
//
|
||||
#include <renderer/backend/vk/context/instance.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Device::Device(IGpu *gpu, ISurface *surface)
|
||||
: m_gpu(static_cast<Gpu *>(gpu))
|
||||
, m_surface(static_cast<Surface *>(surface))
|
||||
{
|
||||
ensure(m_surface->vk(), "Failed to initialize vk::Device: null vulkan surface");
|
||||
|
||||
initialize_queue_indices();
|
||||
initialize_logical_device();
|
||||
Instance::load_device_functions(m_device);
|
||||
|
||||
vk_get_device_queue(m_device, m_graphics_queue_family_index, 0, &m_graphics_queue);
|
||||
vk_get_device_queue(m_device, m_present_queue_family_index, 0, &m_present_queue);
|
||||
|
||||
if (m_present_queue == m_graphics_queue)
|
||||
{
|
||||
name(m_present_queue, "graphics|present queue");
|
||||
}
|
||||
else
|
||||
{
|
||||
name(m_graphics_queue, "graphics queue");
|
||||
name(m_present_queue, "present queue");
|
||||
}
|
||||
}
|
||||
|
||||
Device::~Device()
|
||||
{
|
||||
if (!m_gpu)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vkc(vk_device_wait_idle(m_device));
|
||||
vk_destroy_device(m_device, nullptr);
|
||||
}
|
||||
|
||||
void Device::initialize_logical_device()
|
||||
{
|
||||
const float priorities = .0f;
|
||||
|
||||
auto queue_infos = std::vector<VkDeviceQueueCreateInfo> {};
|
||||
auto queue_families = std::set { m_graphics_queue_family_index, m_present_queue_family_index };
|
||||
|
||||
for (auto queue_family : queue_families)
|
||||
{
|
||||
queue_infos.emplace_back(
|
||||
VkDeviceQueueCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = 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 = static_cast<uint32_t>(queue_infos.size()),
|
||||
.pQueueCreateInfos = queue_infos.data(),
|
||||
.enabledExtensionCount = static_cast<uint32_t>(extensions.size()),
|
||||
.ppEnabledExtensionNames = extensions.data(),
|
||||
.pEnabledFeatures = &physical_device_features,
|
||||
};
|
||||
|
||||
ensure(
|
||||
!vk_create_device(m_gpu->vk(), &device_info, nullptr, &m_device),
|
||||
"Failed to create logical vulkan device"
|
||||
);
|
||||
}
|
||||
|
||||
void Device::initialize_queue_indices()
|
||||
{
|
||||
auto properties = m_gpu->get_queue_family_properties();
|
||||
for (auto idx = uint32_t { 0u }; const auto &property : properties)
|
||||
{
|
||||
if (property.queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
||||
{
|
||||
m_graphics_queue_family_index = idx;
|
||||
}
|
||||
|
||||
if (m_gpu->queue_family_supports_presentation(m_surface->vk(), idx))
|
||||
{
|
||||
m_present_queue_family_index = idx;
|
||||
}
|
||||
|
||||
if (m_graphics_queue_family_index != VK_QUEUE_FAMILY_IGNORED
|
||||
&& m_present_queue_family_index != VK_QUEUE_FAMILY_IGNORED)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
++idx;
|
||||
}
|
||||
|
||||
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 Device::submit(VkSubmitInfo info, VkFence fence) const
|
||||
{
|
||||
vkc(vk_queue_submit(m_graphics_queue, 1u, &info, fence));
|
||||
}
|
||||
|
||||
void Device::present(VkPresentInfoKHR info) const
|
||||
{
|
||||
vk_queue_present_khr(m_present_queue, &info);
|
||||
}
|
||||
|
||||
void Device::wait_idle() const
|
||||
{
|
||||
vkc(vk_device_wait_idle(m_device));
|
||||
}
|
||||
|
||||
void Device::wait_for_fence(VkFence fence) const
|
||||
{
|
||||
vkc(vk_wait_for_fences(m_device, 1u, &fence, true, std::numeric_limits<uint64_t>::max()));
|
||||
}
|
||||
|
||||
void Device::reset_fence(VkFence fence) const
|
||||
{
|
||||
vkc(vk_reset_fences(m_device, 1u, &fence));
|
||||
}
|
||||
|
||||
void Device::reset_fences(std::span<VkFence> fences) const
|
||||
{
|
||||
vkc(vk_reset_fences(m_device, fences.size(), fences.data()));
|
||||
}
|
||||
|
||||
auto Device::acquire_image(VkSwapchainKHR swapchain, VkSemaphore semaphore, uint64_t timeout)
|
||||
-> std::optional<uint32_t>
|
||||
{
|
||||
auto image_idx = uint32_t {};
|
||||
const auto result = vk_acquire_next_image_khr(
|
||||
m_device,
|
||||
swapchain,
|
||||
timeout,
|
||||
semaphore,
|
||||
VK_NULL_HANDLE,
|
||||
&image_idx
|
||||
);
|
||||
|
||||
if (result == VK_SUCCESS)
|
||||
{
|
||||
return image_idx;
|
||||
}
|
||||
|
||||
if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
vkc(result); // throws
|
||||
return {};
|
||||
}
|
||||
|
||||
void Device::wait_for_fences(std::span<VkFence> fences) const
|
||||
{
|
||||
vkc(vk_wait_for_fences(
|
||||
m_device,
|
||||
fences.size(),
|
||||
fences.data(),
|
||||
true,
|
||||
std::numeric_limits<uint64_t>::max()
|
||||
));
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::get_swapchain_images(VkSwapchainKHR swapchain) const
|
||||
-> std::vector<VkImage>
|
||||
{
|
||||
auto count = uint32_t { 0u };
|
||||
vkc(vk_get_swapchain_images_khr(m_device, swapchain, &count, nullptr));
|
||||
ensure(count != 0u, "Failed to get swapchain images");
|
||||
|
||||
auto images = std::vector<VkImage>(count);
|
||||
vkc(vk_get_swapchain_images_khr(m_device, swapchain, &count, images.data()));
|
||||
return images;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::create_swapchain(VkSwapchainCreateInfoKHR info) const -> VkSwapchainKHR
|
||||
{
|
||||
auto *swapchain = VkSwapchainKHR {};
|
||||
vkc(vk_create_swapchain_khr(m_device, &info, nullptr, &swapchain));
|
||||
return swapchain;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::create_framebuffer(VkFramebufferCreateInfo info) const -> VkFramebuffer
|
||||
{
|
||||
auto *framebuffer = VkFramebuffer {};
|
||||
vkc(vk_create_frame_buffer(m_device, &info, m_allocator, &framebuffer));
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::create_image_view(VkImageViewCreateInfo info) const -> VkImageView
|
||||
{
|
||||
auto *view = VkImageView {};
|
||||
vkc(vk_create_image_view(m_device, &info, m_allocator, &view));
|
||||
return view;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::create_graphics_pipeline(VkGraphicsPipelineCreateInfo info) const
|
||||
-> VkPipeline
|
||||
{
|
||||
auto *graphics_pipeline = VkPipeline {};
|
||||
vkc(vk_create_graphics_pipelines(
|
||||
m_device,
|
||||
VK_NULL_HANDLE,
|
||||
1u,
|
||||
&info,
|
||||
m_allocator,
|
||||
&graphics_pipeline
|
||||
|
||||
));
|
||||
return graphics_pipeline;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::create_pass(VkRenderPassCreateInfo info) const -> VkRenderPass
|
||||
{
|
||||
auto *pass = VkRenderPass {};
|
||||
vkc(vk_create_render_pass(m_device, &info, nullptr, &pass));
|
||||
return pass;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::create_pipeline_layout(VkPipelineLayoutCreateInfo info) const
|
||||
-> VkPipelineLayout
|
||||
{
|
||||
auto *pipeline_layout = VkPipelineLayout {};
|
||||
vkc(vk_create_pipeline_layout(m_device, &info, nullptr, &pipeline_layout));
|
||||
return pipeline_layout;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::create_shader_module(VkShaderModuleCreateInfo info) const
|
||||
-> VkShaderModule
|
||||
{
|
||||
auto *module = VkShaderModule {};
|
||||
vkc(vk_create_shader_module(m_device, &info, m_allocator, &module));
|
||||
return module;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::create_command_pool(VkCommandPoolCreateInfo info) const -> VkCommandPool
|
||||
{
|
||||
auto *command_pool = VkCommandPool {};
|
||||
vkc(vk_create_command_pool(m_device, &info, m_allocator, &command_pool));
|
||||
return command_pool;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::create_semaphores(uint32_t count) const -> std::vector<VkSemaphore>
|
||||
{
|
||||
auto info = VkSemaphoreCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
};
|
||||
|
||||
auto semaphores = std::vector<VkSemaphore>(count);
|
||||
for (auto &semaphore : semaphores)
|
||||
{
|
||||
vk_create_semaphore(m_device, &info, m_allocator, &semaphore);
|
||||
}
|
||||
return semaphores;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::create_fences(VkFenceCreateInfo info, uint32_t count) const
|
||||
-> std::vector<VkFence>
|
||||
{
|
||||
auto fences = std::vector<VkFence>(count);
|
||||
for (auto &fence : fences)
|
||||
{
|
||||
vk_create_fence(m_device, &info, m_allocator, &fence);
|
||||
}
|
||||
return fences;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Device::allocate_command_buffers(VkCommandBufferAllocateInfo info) const
|
||||
-> std::vector<VkCommandBuffer>
|
||||
{
|
||||
auto command_buffers = std::vector<VkCommandBuffer>(info.commandBufferCount);
|
||||
vkc(vk_allocate_command_buffers(m_device, &info, command_buffers.data()));
|
||||
return command_buffers;
|
||||
}
|
||||
|
||||
void Device::destroy_swapchain(VkSwapchainKHR swapchain) const
|
||||
{
|
||||
vk_destroy_swapchain_khr(m_device, swapchain, m_allocator);
|
||||
}
|
||||
|
||||
void Device::destroy_framebuffer(VkFramebuffer framebuffer) const
|
||||
{
|
||||
vk_destroy_frame_buffer(m_device, framebuffer, m_allocator);
|
||||
}
|
||||
|
||||
void Device::destroy_framebuffers(std::span<VkFramebuffer> framebuffers) const
|
||||
{
|
||||
for (auto &framebuffer : framebuffers)
|
||||
{
|
||||
destroy_framebuffer(framebuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::destroy_image_view(VkImageView image_view) const
|
||||
{
|
||||
vk_destroy_image_view(m_device, image_view, m_allocator);
|
||||
}
|
||||
|
||||
void Device::destroy_image_views(std::span<VkImageView> image_views) const
|
||||
{
|
||||
for (auto &image_view : image_views)
|
||||
{
|
||||
destroy_image_view(image_view);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::destroy_pipeline(VkPipeline pipeline) const
|
||||
{
|
||||
vk_destroy_pipeline(m_device, pipeline, m_allocator);
|
||||
}
|
||||
|
||||
void Device::destroy_pass(VkRenderPass pass) const
|
||||
{
|
||||
vk_destroy_render_pass(m_device, pass, m_allocator);
|
||||
}
|
||||
|
||||
void Device::destroy_pipeline_layout(VkPipelineLayout pipeline_layout) const
|
||||
{
|
||||
vk_destroy_pipeline_layout(m_device, pipeline_layout, m_allocator);
|
||||
}
|
||||
|
||||
void Device::destroy_shader_module(VkShaderModule shader_module) const
|
||||
{
|
||||
vk_destroy_shader_module(m_device, shader_module, m_allocator);
|
||||
}
|
||||
|
||||
void Device::destroy_command_pool(VkCommandPool command_pool) const
|
||||
{
|
||||
vk_destroy_command_pool(m_device, command_pool, m_allocator);
|
||||
}
|
||||
|
||||
void Device::destroy_semaphore(VkSemaphore semaphore) const
|
||||
{
|
||||
vk_destroy_semaphore(m_device, semaphore, m_allocator);
|
||||
}
|
||||
|
||||
void Device::destroy_semaphores(std::span<VkSemaphore> semaphores) const
|
||||
{
|
||||
for (auto &semaphore : semaphores)
|
||||
{
|
||||
destroy_semaphore(semaphore);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::destroy_fence(VkFence fence) const
|
||||
{
|
||||
vk_destroy_fence(m_device, fence, m_allocator);
|
||||
}
|
||||
|
||||
void Device::destroy_fences(std::span<VkFence> fences) const
|
||||
{
|
||||
for (auto &fence : fences)
|
||||
{
|
||||
destroy_fence(fence);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
221
modules/renderer/private/backend/vk/context/device.hpp
Normal file
221
modules/renderer/private/backend/vk/context/device.hpp
Normal file
|
@ -0,0 +1,221 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/backend/vk/vulkan.hpp>
|
||||
#include <renderer/frontend/context/device.hpp>
|
||||
#include <renderer/frontend/context/gpu.hpp>
|
||||
#include <renderer/frontend/context/surface.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Device: public IDevice
|
||||
{
|
||||
public:
|
||||
Device(IGpu *gpu, ISurface *surface);
|
||||
|
||||
~Device() override;
|
||||
|
||||
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 get_family_indices() const -> std::array<uint32_t, 2>
|
||||
{
|
||||
return { m_graphics_queue_family_index, m_present_queue_family_index };
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_graphics_queue() const -> VkQueue
|
||||
{
|
||||
return m_graphics_queue;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_present_queue() const -> VkQueue
|
||||
{
|
||||
return m_present_queue;
|
||||
}
|
||||
|
||||
/** utilities */
|
||||
template<typename T, typename... Args>
|
||||
void name(const T &object, std::format_string<Args...> fmt, Args &&...args)
|
||||
{
|
||||
const auto name = std::format(fmt, std::forward<Args>(args)...);
|
||||
auto info = VkDebugUtilsObjectNameInfoEXT {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.objectType = get_object_type(object),
|
||||
.objectHandle = (uint64_t)(object),
|
||||
.pObjectName = name.c_str(),
|
||||
};
|
||||
|
||||
vk_set_debug_object_name(m_device, &info);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void name(const T &object, const char *name)
|
||||
{
|
||||
auto info = VkDebugUtilsObjectNameInfoEXT {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.objectType = get_object_type(object),
|
||||
.objectHandle = (uint64_t)(object),
|
||||
.pObjectName = name,
|
||||
};
|
||||
|
||||
vk_set_debug_object_name(m_device, &info);
|
||||
}
|
||||
|
||||
|
||||
/** work functions */
|
||||
void submit(VkSubmitInfo info, VkFence fence) const;
|
||||
|
||||
void present(VkPresentInfoKHR info) const;
|
||||
|
||||
void wait_idle() const;
|
||||
|
||||
void wait_for_fence(VkFence fence) const;
|
||||
|
||||
void wait_for_fences(std::span<VkFence> fences) const;
|
||||
|
||||
void reset_fence(VkFence fence) const;
|
||||
|
||||
void reset_fences(std::span<VkFence> fences) const;
|
||||
|
||||
/** getter functions */
|
||||
[[nodiscard]] auto acquire_image(
|
||||
VkSwapchainKHR swapchain,
|
||||
VkSemaphore semaphore,
|
||||
uint64_t timeout = 1'000'000
|
||||
) -> std::optional<uint32_t>;
|
||||
|
||||
[[nodiscard]] auto get_swapchain_images(VkSwapchainKHR swapchain) const -> std::vector<VkImage>;
|
||||
|
||||
/** create functions */
|
||||
[[nodiscard]] auto create_swapchain(VkSwapchainCreateInfoKHR info) const -> VkSwapchainKHR;
|
||||
|
||||
[[nodiscard]] auto create_framebuffer(VkFramebufferCreateInfo info) const -> VkFramebuffer;
|
||||
|
||||
[[nodiscard]] auto create_image_view(VkImageViewCreateInfo info) const -> VkImageView;
|
||||
|
||||
[[nodiscard]] auto create_graphics_pipeline(VkGraphicsPipelineCreateInfo info) const
|
||||
-> VkPipeline;
|
||||
|
||||
[[nodiscard]] auto create_pass(VkRenderPassCreateInfo info) const -> VkRenderPass;
|
||||
|
||||
[[nodiscard]] auto create_pipeline_layout(VkPipelineLayoutCreateInfo info) const
|
||||
-> VkPipelineLayout;
|
||||
|
||||
[[nodiscard]] auto create_shader_module(VkShaderModuleCreateInfo info) const -> VkShaderModule;
|
||||
|
||||
[[nodiscard]] auto create_command_pool(VkCommandPoolCreateInfo info) const -> VkCommandPool;
|
||||
|
||||
[[nodiscard]] auto create_semaphores(uint32_t count) const -> std::vector<VkSemaphore>;
|
||||
|
||||
[[nodiscard]] auto create_fences(VkFenceCreateInfo info, uint32_t count) const
|
||||
-> std::vector<VkFence>;
|
||||
|
||||
/** allocation functions */
|
||||
[[nodiscard]] auto allocate_command_buffers(VkCommandBufferAllocateInfo info) const
|
||||
-> std::vector<VkCommandBuffer>;
|
||||
|
||||
/** destroy functions */
|
||||
void destroy_swapchain(VkSwapchainKHR swapchain) const;
|
||||
|
||||
void destroy_framebuffer(VkFramebuffer framebuffer) const;
|
||||
|
||||
void destroy_framebuffers(std::span<VkFramebuffer> framebuffers) const;
|
||||
|
||||
void destroy_image_view(VkImageView image_view) const;
|
||||
|
||||
void destroy_image_views(std::span<VkImageView> image_views) const;
|
||||
|
||||
void destroy_pipeline(VkPipeline pipeline) const;
|
||||
|
||||
void destroy_pass(VkRenderPass pass) const;
|
||||
|
||||
void destroy_pipeline_layout(VkPipelineLayout pipeline_layout) const;
|
||||
|
||||
void destroy_shader_module(VkShaderModule shader_module) const;
|
||||
|
||||
void destroy_command_pool(VkCommandPool command_pool) const;
|
||||
|
||||
void destroy_semaphore(VkSemaphore semaphore) const;
|
||||
|
||||
void destroy_semaphores(std::span<VkSemaphore> semaphores) const;
|
||||
|
||||
void destroy_fence(VkFence fence) const;
|
||||
|
||||
void destroy_fences(std::span<VkFence> fences) const;
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
static auto get_object_type(const T &object) -> VkObjectType
|
||||
{
|
||||
std::ignore = object;
|
||||
|
||||
if constexpr (std::is_same_v<T, VkQueue>)
|
||||
{
|
||||
return VK_OBJECT_TYPE_QUEUE;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, VkFence>)
|
||||
{
|
||||
return VK_OBJECT_TYPE_FENCE;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, VkSemaphore>)
|
||||
{
|
||||
return VK_OBJECT_TYPE_SEMAPHORE;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, VkSwapchainKHR>)
|
||||
{
|
||||
return VK_OBJECT_TYPE_SWAPCHAIN_KHR;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, VkImage>)
|
||||
{
|
||||
return VK_OBJECT_TYPE_IMAGE;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, VkImageView>)
|
||||
{
|
||||
return VK_OBJECT_TYPE_IMAGE_VIEW;
|
||||
}
|
||||
|
||||
static_assert("invalid type");
|
||||
}
|
||||
|
||||
|
||||
void initialize_physical_device();
|
||||
|
||||
void initialize_logical_device();
|
||||
|
||||
void initialize_queue_indices();
|
||||
|
||||
[[nodiscard]] auto find_suitable_queue_family() const -> uint32_t;
|
||||
|
||||
memory::NullOnMove<class Gpu *> m_gpu {};
|
||||
|
||||
class Surface *m_surface {};
|
||||
|
||||
VkAllocationCallbacks *m_allocator = nullptr;
|
||||
|
||||
VkDevice m_device = VK_NULL_HANDLE;
|
||||
|
||||
VkQueue m_graphics_queue = VK_NULL_HANDLE;
|
||||
|
||||
VkQueue m_present_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
|
71
modules/renderer/private/backend/vk/context/gpu.cpp
Normal file
71
modules/renderer/private/backend/vk/context/gpu.cpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
#include <renderer/backend/vk/context/gpu.hpp>
|
||||
#include <renderer/backend/vk/context/instance.hpp>
|
||||
#include <renderer/backend/vk/utils.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Gpu::Gpu(IInstance *instance)
|
||||
{
|
||||
auto gpus = static_cast<Instance *>(instance)->enumerate_gpus();
|
||||
|
||||
for (auto &gpu : gpus)
|
||||
{
|
||||
auto properties = VkPhysicalDeviceProperties {};
|
||||
auto features = VkPhysicalDeviceFeatures {};
|
||||
|
||||
vk_get_physical_device_properties(gpu, &properties);
|
||||
vk_get_physical_device_features(gpu, &features);
|
||||
|
||||
if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
|
||||
&& features.geometryShader)
|
||||
{
|
||||
m_gpu = gpu;
|
||||
}
|
||||
}
|
||||
ensure(m_gpu, "Failed to find any suitable Vulkan physical device");
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Gpu::get_queue_family_properties() const -> std::vector<VkQueueFamilyProperties>
|
||||
{
|
||||
auto count = uint32_t { 0u };
|
||||
vk_get_physical_device_queue_family_properties(m_gpu, &count, nullptr);
|
||||
|
||||
auto properties = std::vector<VkQueueFamilyProperties>(count);
|
||||
vk_get_physical_device_queue_family_properties(m_gpu, &count, properties.data());
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Gpu::queue_family_supports_presentation(
|
||||
VkSurfaceKHR surface,
|
||||
uint32_t queue_family_idx
|
||||
) -> bool
|
||||
{
|
||||
auto supported = VkBool32 { false };
|
||||
vkc(vk_get_physical_device_surface_support(m_gpu, queue_family_idx, surface, &supported));
|
||||
|
||||
return supported;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Gpu::get_surface_capabilities(VkSurfaceKHR surface) const
|
||||
-> VkSurfaceCapabilitiesKHR
|
||||
{
|
||||
auto capabilities = VkSurfaceCapabilitiesKHR {};
|
||||
vkc(vk_get_physical_device_surface_capabilities(m_gpu, surface, &capabilities));
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Gpu::get_surface_formats(VkSurfaceKHR surface) const
|
||||
-> std::vector<VkSurfaceFormatKHR>
|
||||
{
|
||||
auto count = uint32_t { 0u };
|
||||
vkc(vk_get_physical_device_surface_formats(m_gpu, surface, &count, nullptr));
|
||||
|
||||
auto formats = std::vector<VkSurfaceFormatKHR>(count);
|
||||
vkc(vk_get_physical_device_surface_formats(m_gpu, surface, &count, formats.data()));
|
||||
|
||||
return formats;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lt::renderer::vk
|
37
modules/renderer/private/backend/vk/context/gpu.hpp
Normal file
37
modules/renderer/private/backend/vk/context/gpu.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/backend/vk/vulkan.hpp>
|
||||
#include <renderer/frontend/context/gpu.hpp>
|
||||
#include <renderer/frontend/context/instance.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Gpu: public IGpu
|
||||
{
|
||||
public:
|
||||
Gpu(IInstance *instance);
|
||||
|
||||
[[nodiscard]] auto vk() const -> VkPhysicalDevice
|
||||
{
|
||||
return m_gpu;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_queue_family_properties() const -> std::vector<VkQueueFamilyProperties>;
|
||||
|
||||
[[nodiscard]] auto queue_family_supports_presentation(
|
||||
VkSurfaceKHR surface,
|
||||
uint32_t queue_family_idx
|
||||
) -> bool;
|
||||
|
||||
[[nodiscard]] auto get_surface_capabilities(VkSurfaceKHR surface) const
|
||||
-> VkSurfaceCapabilitiesKHR;
|
||||
|
||||
[[nodiscard]] auto get_surface_formats(VkSurfaceKHR surface) const
|
||||
-> std::vector<VkSurfaceFormatKHR>;
|
||||
|
||||
private:
|
||||
memory::NullOnMove<VkPhysicalDevice> m_gpu = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,6 +1,6 @@
|
|||
#include <app/system.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
#include <renderer/vk/debug/validation.hpp>
|
||||
#include <renderer/backend/vk/context/instance.hpp>
|
||||
#include <renderer/backend/vk/utils.hpp>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#error "Unsupported platform (currently)"
|
||||
|
@ -92,23 +92,10 @@ PFN_vkGetPhysicalDeviceSurfaceSupportKHR vk_get_physical_device_surface_support
|
|||
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vk_get_physical_device_surface_capabilities {};
|
||||
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vk_get_physical_device_surface_formats {};
|
||||
|
||||
PFN_vkCreateXlibSurfaceKHR vk_create_xlib_surface_khr {};
|
||||
PFN_vkDestroySurfaceKHR vk_destroy_surface_khr {};
|
||||
auto vk_create_xlib_surface_khr = PFN_vkCreateXlibSurfaceKHR {};
|
||||
auto vk_destroy_surface_khr = PFN_vkDestroySurfaceKHR {};
|
||||
// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
auto parse_message_type(VkDebugUtilsMessageTypeFlagsEXT message_types) -> const char *;
|
||||
|
||||
auto parse_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity)
|
||||
-> app::SystemDiagnosis::Severity;
|
||||
|
||||
auto validation_layers_callback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT message_types,
|
||||
VkDebugUtilsMessengerCallbackDataEXT const *callback_data,
|
||||
void *vulkan_user_data
|
||||
) -> VkBool32;
|
||||
|
||||
|
||||
Instance::Instance()
|
||||
{
|
||||
load_library();
|
||||
|
@ -375,84 +362,41 @@ void Instance::load_device_functions_impl(VkDevice device)
|
|||
load_fn(vk_reset_command_buffer, "vkResetCommandBuffer");
|
||||
}
|
||||
|
||||
auto parse_message_type(VkDebugUtilsMessageTypeFlagsEXT message_types) -> const char *
|
||||
auto Instance::enumerate_gpus() const -> std::vector<VkPhysicalDevice>
|
||||
{
|
||||
if (message_types == VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT)
|
||||
{
|
||||
return "GENERAL";
|
||||
}
|
||||
auto count = 0u;
|
||||
vkc(vk_enumerate_physical_devices(m_instance, &count, nullptr));
|
||||
ensure(count != 0u, "Failed to find any gpus with Vulkan support");
|
||||
|
||||
if (message_types
|
||||
== (VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT))
|
||||
{
|
||||
return "VALIDATION | PERFORMANCE";
|
||||
}
|
||||
|
||||
if (message_types == VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)
|
||||
{
|
||||
return "VALIDATION";
|
||||
}
|
||||
|
||||
return "PERFORMANCE";
|
||||
auto gpus = std::vector<VkPhysicalDevice>(count);
|
||||
vkc(vk_enumerate_physical_devices(m_instance, &count, gpus.data()));
|
||||
return gpus;
|
||||
}
|
||||
|
||||
auto parse_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity)
|
||||
-> app::SystemDiagnosis::Severity
|
||||
auto Instance::create_xlib_surface(VkXlibSurfaceCreateInfoKHR info) const -> VkSurfaceKHR
|
||||
{
|
||||
using enum app::SystemDiagnosis::Severity;
|
||||
auto *value = VkSurfaceKHR {};
|
||||
vk_create_xlib_surface_khr(m_instance, &info, m_allocator, &value);
|
||||
|
||||
switch (message_severity)
|
||||
{
|
||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: return verbose;
|
||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: return info;
|
||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: return warning;
|
||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: return error;
|
||||
default: ensure(false, "Invalid message severity: {}", static_cast<int>(message_severity));
|
||||
}
|
||||
|
||||
return {};
|
||||
return value;
|
||||
}
|
||||
|
||||
auto validation_layers_callback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT const message_severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT const message_types,
|
||||
VkDebugUtilsMessengerCallbackDataEXT const *const callback_data,
|
||||
void *const vulkan_user_data
|
||||
) -> VkBool32
|
||||
[[nodiscard]] auto Instance::create_messenger(VkDebugUtilsMessengerCreateInfoEXT info) const
|
||||
-> VkDebugUtilsMessengerEXT
|
||||
{
|
||||
std::cout << callback_data->pMessage << std::endl;
|
||||
return VK_FALSE;
|
||||
auto *messenger = VkDebugUtilsMessengerEXT {};
|
||||
vkc(vk_create_debug_messenger(m_instance, &info, m_allocator, &messenger));
|
||||
return messenger;
|
||||
}
|
||||
|
||||
log_dbg("VALIDATION: {}", callback_data->pMessage);
|
||||
void Instance::destroy_surface(VkSurfaceKHR surface) const
|
||||
{
|
||||
vk_destroy_surface_khr(m_instance, surface, m_allocator);
|
||||
}
|
||||
|
||||
ensure(vulkan_user_data, "Validation layers's user data is not set!");
|
||||
|
||||
auto stats = *(Ref<app::SystemStats> *)vulkan_user_data; // NOLINT
|
||||
|
||||
const auto &type = parse_message_type(message_types);
|
||||
|
||||
auto message = std::format(
|
||||
"Vulkan Validation Message:\ntype: {}\nseverity: {}\nmessage: {}",
|
||||
type,
|
||||
std::to_underlying(parse_message_severity(message_severity)),
|
||||
callback_data->pMessage
|
||||
);
|
||||
|
||||
auto severity = parse_message_severity(message_severity);
|
||||
if (std::to_underlying(severity) < 2)
|
||||
{
|
||||
return static_cast<VkBool32>(VK_FALSE);
|
||||
}
|
||||
|
||||
stats->push_diagnosis(
|
||||
app::SystemDiagnosis {
|
||||
.message = message,
|
||||
.code = {}, // TODO(Light): extract vulkan validation-layers code from the message
|
||||
.severity = severity,
|
||||
}
|
||||
);
|
||||
return static_cast<VkBool32>(VK_FALSE);
|
||||
void Instance::destroy_messenger(VkDebugUtilsMessengerEXT messenger) const
|
||||
{
|
||||
vk_destroy_debug_messenger(m_instance, messenger, m_allocator);
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,31 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <renderer/vk/vulkan.hpp>
|
||||
#include <renderer/backend/vk/vulkan.hpp>
|
||||
#include <renderer/frontend/context/instance.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
/**
|
||||
* Responsible for dynamically loading Vulkan library/functions.
|
||||
*
|
||||
* @note: The delayed vkInstance destruction is due to a work-around for libx11:
|
||||
* @note: The delayed vkInstance destruction is due to a work-around for a libx11 quirk:
|
||||
* @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
|
||||
class Instance: public IInstance
|
||||
{
|
||||
public:
|
||||
static auto get() -> VkInstance
|
||||
static auto get() -> IInstance *
|
||||
{
|
||||
return Instance::instance().m_instance;
|
||||
return &Instance::instance();
|
||||
}
|
||||
|
||||
static auto load_device_functions(VkDevice device)
|
||||
{
|
||||
instance().load_device_functions_impl(device);
|
||||
static_cast<Instance &>(instance()).load_device_functions_impl(device);
|
||||
}
|
||||
|
||||
~Instance() override;
|
||||
|
||||
Instance(Instance &&) = delete;
|
||||
|
||||
Instance(const Instance &) = delete;
|
||||
|
@ -34,8 +37,26 @@ public:
|
|||
|
||||
auto operator=(Instance &&) -> Instance & = delete;
|
||||
|
||||
[[nodiscard]] auto vk() const -> VkInstance
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
/* create functions */
|
||||
[[nodiscard]] auto create_xlib_surface(VkXlibSurfaceCreateInfoKHR info) const -> VkSurfaceKHR;
|
||||
|
||||
[[nodiscard]] auto create_messenger(VkDebugUtilsMessengerCreateInfoEXT info) const
|
||||
-> VkDebugUtilsMessengerEXT;
|
||||
|
||||
/* destroy functions */
|
||||
void destroy_surface(VkSurfaceKHR surface) const;
|
||||
|
||||
void destroy_messenger(VkDebugUtilsMessengerEXT messenger) const;
|
||||
|
||||
[[nodiscard]] auto enumerate_gpus() const -> std::vector<VkPhysicalDevice>;
|
||||
|
||||
private:
|
||||
static auto instance() -> Instance &
|
||||
static auto instance() -> IInstance &
|
||||
{
|
||||
static auto instance = Instance {};
|
||||
return instance;
|
||||
|
@ -43,8 +64,6 @@ private:
|
|||
|
||||
Instance();
|
||||
|
||||
~Instance();
|
||||
|
||||
void initialize_instance();
|
||||
|
||||
void load_library();
|
||||
|
@ -58,6 +77,8 @@ private:
|
|||
void load_device_functions_impl(VkDevice device);
|
||||
|
||||
VkInstance m_instance = VK_NULL_HANDLE;
|
||||
|
||||
VkAllocationCallbacks *m_allocator = nullptr;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,31 +1,35 @@
|
|||
#include <renderer/vk/context/surface.hpp>
|
||||
#include <renderer/backend/vk/context/instance.hpp>
|
||||
#include <renderer/backend/vk/context/surface.hpp>
|
||||
#include <surface/components.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Surface::Surface(ecs::Entity surface_entity): m_surface_entity(surface_entity)
|
||||
Surface::Surface(IInstance *instance, const ecs::Entity &surface_entity)
|
||||
: m_surface_entity(surface_entity)
|
||||
, m_instance(static_cast<Instance *>(instance))
|
||||
{
|
||||
const auto &component = surface_entity.get<surface::SurfaceComponent>();
|
||||
|
||||
ensure(component.get_native_data().display, "Failed to initialize vk::Surface: null x-display");
|
||||
ensure(component.get_native_data().window, "Failed to initialize vk::Surface: null x-window");
|
||||
|
||||
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();
|
||||
auto result = vk_create_xlib_surface_khr(instance, &create_info, nullptr, &m_surface);
|
||||
m_surface = m_instance->create_xlib_surface(
|
||||
VkXlibSurfaceCreateInfoKHR {
|
||||
.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR,
|
||||
.dpy = component.get_native_data().display,
|
||||
.window = component.get_native_data().window,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Surface::~Surface()
|
||||
{
|
||||
if (Instance::get())
|
||||
if (!m_instance)
|
||||
{
|
||||
vk_destroy_surface_khr(Instance::get(), m_surface, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
m_instance->destroy_surface(m_surface);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Surface::get_framebuffer_size() const -> VkExtent2D
|
|
@ -2,16 +2,20 @@
|
|||
|
||||
#include <ecs/entity.hpp>
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
#include <renderer/backend/vk/vulkan.hpp>
|
||||
#include <renderer/frontend/context/instance.hpp>
|
||||
#include <renderer/frontend/context/surface.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Surface
|
||||
class Instance;
|
||||
|
||||
class Surface: public ISurface
|
||||
{
|
||||
public:
|
||||
Surface(ecs::Entity surface_entity);
|
||||
Surface(IInstance *instance, const ecs::Entity &surface_entity);
|
||||
|
||||
~Surface();
|
||||
~Surface() override;
|
||||
|
||||
Surface(Surface &&) = default;
|
||||
|
||||
|
@ -29,7 +33,9 @@ public:
|
|||
[[nodiscard]] auto get_framebuffer_size() const -> VkExtent2D;
|
||||
|
||||
private:
|
||||
memory::NullOnMove<VkSurfaceKHR> m_surface = VK_NULL_HANDLE;
|
||||
class Instance *m_instance {};
|
||||
|
||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
|
||||
ecs::Entity m_surface_entity;
|
||||
};
|
152
modules/renderer/private/backend/vk/context/swapchain.cpp
Normal file
152
modules/renderer/private/backend/vk/context/swapchain.cpp
Normal file
|
@ -0,0 +1,152 @@
|
|||
#include <ranges>
|
||||
#include <renderer/backend/vk/context/device.hpp>
|
||||
#include <renderer/backend/vk/context/gpu.hpp>
|
||||
#include <renderer/backend/vk/context/instance.hpp>
|
||||
#include <renderer/backend/vk/context/surface.hpp>
|
||||
#include <renderer/backend/vk/context/swapchain.hpp>
|
||||
#include <renderer/backend/vk/utils.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Swapchain::Swapchain(ISurface *surface, IGpu *gpu, IDevice *device)
|
||||
: m_surface(static_cast<Surface *>(surface))
|
||||
, m_gpu(static_cast<Gpu *>(gpu))
|
||||
, m_device(static_cast<Device *>(device))
|
||||
{
|
||||
static auto idx = 0u;
|
||||
|
||||
const auto capabilities = m_gpu->get_surface_capabilities(m_surface->vk());
|
||||
const auto formats = m_gpu->get_surface_formats(m_surface->vk());
|
||||
|
||||
// TODO(Light): parameterize
|
||||
constexpr auto desired_image_count = uint32_t { 3 };
|
||||
const auto surface_format = formats.front();
|
||||
const auto queue_indices = m_device->get_family_indices();
|
||||
m_format = surface_format.format;
|
||||
|
||||
m_swapchain = m_device->create_swapchain(
|
||||
VkSwapchainCreateInfoKHR {
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.surface = m_surface->vk(),
|
||||
.minImageCount = get_optimal_image_count(capabilities, desired_image_count),
|
||||
.imageFormat = surface_format.format,
|
||||
.imageColorSpace = surface_format.colorSpace,
|
||||
.imageExtent = capabilities.currentExtent,
|
||||
.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_KHR, // TODO(Light): parameterize
|
||||
.clipped = VK_TRUE,
|
||||
.oldSwapchain = nullptr,
|
||||
}
|
||||
);
|
||||
m_device->name(m_swapchain, "swapchain {}", idx++);
|
||||
m_device->wait_idle();
|
||||
|
||||
|
||||
m_images = m_device->get_swapchain_images(m_swapchain);
|
||||
m_image_views.resize(m_images.size());
|
||||
for (auto idx = 0u; auto [image, view] : std::views::zip(m_images, m_image_views))
|
||||
{
|
||||
view = m_device->create_image_view(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,
|
||||
}
|
||||
});
|
||||
|
||||
m_device->name(image, "swapchain image {}", idx++);
|
||||
m_device->name(view, "swapchain image view {}", idx++);
|
||||
}
|
||||
}
|
||||
|
||||
Swapchain::~Swapchain()
|
||||
{
|
||||
if (!m_surface)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
m_device->wait_idle();
|
||||
m_device->destroy_image_views(m_image_views);
|
||||
m_device->destroy_swapchain(m_swapchain);
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_err("Failed to destroy swapchain:");
|
||||
log_err("\twhat: {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] auto Swapchain::create_framebuffers_for_pass(VkRenderPass pass) const
|
||||
-> std::vector<VkFramebuffer>
|
||||
{
|
||||
auto framebuffers = std::vector<VkFramebuffer>(m_image_views.size());
|
||||
|
||||
for (auto idx = 0u; auto &framebuffer : framebuffers)
|
||||
{
|
||||
framebuffer = m_device->create_framebuffer(
|
||||
VkFramebufferCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
.renderPass = pass,
|
||||
.attachmentCount = 1u,
|
||||
.pAttachments = &m_image_views[idx++],
|
||||
.width = m_resolution.width,
|
||||
.height = m_resolution.height,
|
||||
.layers = 1u,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return framebuffers;
|
||||
}
|
||||
|
||||
[[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
|
78
modules/renderer/private/backend/vk/context/swapchain.hpp
Normal file
78
modules/renderer/private/backend/vk/context/swapchain.hpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/backend/vk/utils.hpp>
|
||||
#include <renderer/backend/vk/vulkan.hpp>
|
||||
#include <renderer/frontend/context/device.hpp>
|
||||
#include <renderer/frontend/context/gpu.hpp>
|
||||
#include <renderer/frontend/context/surface.hpp>
|
||||
#include <renderer/frontend/context/swapchain.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Swapchain: public ISwapchain
|
||||
{
|
||||
public:
|
||||
Swapchain(ISurface *surface, IGpu *gpu, IDevice *device);
|
||||
~Swapchain() override;
|
||||
|
||||
Swapchain(Swapchain &&) = default;
|
||||
|
||||
Swapchain(const Swapchain &) = delete;
|
||||
|
||||
auto operator=(Swapchain &&) -> Swapchain & = default;
|
||||
|
||||
auto operator=(const Swapchain &) const -> Swapchain & = delete;
|
||||
|
||||
[[nodiscard]] auto vk() const -> VkSwapchainKHR
|
||||
{
|
||||
return m_swapchain;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto vk_ptr() -> VkSwapchainKHR *
|
||||
{
|
||||
return &m_swapchain;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_resolution() const -> VkExtent2D
|
||||
{
|
||||
return m_resolution;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_format() const -> VkFormat
|
||||
{
|
||||
return m_format;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_image_count() const -> size_t
|
||||
{
|
||||
return m_images.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto create_framebuffers_for_pass(VkRenderPass pass) const
|
||||
-> std::vector<VkFramebuffer>;
|
||||
|
||||
private:
|
||||
[[nodiscard]] auto get_optimal_image_count(
|
||||
VkSurfaceCapabilitiesKHR capabilities,
|
||||
uint32_t desired_image_count
|
||||
) const -> uint32_t;
|
||||
|
||||
memory::NullOnMove<class Surface *> m_surface {};
|
||||
|
||||
class Gpu *m_gpu {};
|
||||
|
||||
class Device *m_device {};
|
||||
|
||||
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE;
|
||||
|
||||
std::vector<VkImage> m_images;
|
||||
|
||||
std::vector<VkImageView> m_image_views;
|
||||
|
||||
VkExtent2D m_resolution {};
|
||||
|
||||
VkFormat m_format {};
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
177
modules/renderer/private/backend/vk/messenger.cpp
Normal file
177
modules/renderer/private/backend/vk/messenger.cpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
#include <renderer/backend/vk/messenger.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Messenger::Messenger(IInstance *instance, ecs::Entity entity)
|
||||
: m_instance(static_cast<Instance *>(instance))
|
||||
, m_entity(std::move(entity))
|
||||
|
||||
{
|
||||
const auto &component = entity.get<MessengerComponent>();
|
||||
|
||||
m_debug_messenger = m_instance->create_messenger(
|
||||
VkDebugUtilsMessengerCreateInfoEXT {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
||||
.messageSeverity = to_native_severity(component.severities),
|
||||
.messageType = to_native_type(component.types),
|
||||
.pfnUserCallback = &native_callback,
|
||||
.pUserData = this,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Messenger::~Messenger()
|
||||
{
|
||||
if (!m_instance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_instance->destroy_messenger(m_debug_messenger);
|
||||
}
|
||||
|
||||
/*static*/ auto Messenger::native_callback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT type,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
|
||||
void *vulkan_user_data
|
||||
) -> VkBool32
|
||||
{
|
||||
try
|
||||
{
|
||||
ensure(vulkan_user_data, "Null vulkan_user_data received in messenger callback");
|
||||
|
||||
auto *messenger = (Messenger *)vulkan_user_data; // NOLINT
|
||||
auto &component = messenger->m_entity.get<MessengerComponent>();
|
||||
component.callback(
|
||||
from_native_severity(severity),
|
||||
from_native_type(type),
|
||||
{
|
||||
.message = callback_data->pMessage,
|
||||
},
|
||||
component.user_data
|
||||
);
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_err("Uncaught exception in messenger callback:");
|
||||
log_err("\twhat: {}", exp.what());
|
||||
}
|
||||
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
[[nodiscard]] /*static*/ auto Messenger::to_native_severity(MessageSeverity severity)
|
||||
-> VkDebugUtilsMessageSeverityFlagsEXT
|
||||
{
|
||||
using enum MessageSeverity;
|
||||
|
||||
const auto value = std::to_underlying(severity);
|
||||
auto flags = VkDebugUtilsMessageSeverityFlagsEXT {};
|
||||
|
||||
if (value & std::to_underlying(error))
|
||||
{
|
||||
flags |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
|
||||
}
|
||||
|
||||
if (value & std::to_underlying(warning))
|
||||
{
|
||||
flags |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
|
||||
}
|
||||
|
||||
if (value & std::to_underlying(info))
|
||||
{
|
||||
flags |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT;
|
||||
}
|
||||
|
||||
if (value & std::to_underlying(verbose))
|
||||
{
|
||||
flags |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
[[nodiscard]] /*static*/ auto Messenger::from_native_severity(
|
||||
VkDebugUtilsMessageSeverityFlagsEXT severity
|
||||
) -> MessageSeverity
|
||||
{
|
||||
using enum MessageSeverity;
|
||||
|
||||
auto flags = std::underlying_type_t<MessageSeverity> {};
|
||||
|
||||
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
|
||||
{
|
||||
flags &= std::to_underlying(error);
|
||||
}
|
||||
|
||||
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
|
||||
{
|
||||
flags &= std::to_underlying(warning);
|
||||
}
|
||||
|
||||
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT)
|
||||
{
|
||||
flags &= std::to_underlying(info);
|
||||
}
|
||||
|
||||
if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT)
|
||||
{
|
||||
flags &= std::to_underlying(verbose);
|
||||
}
|
||||
|
||||
return static_cast<MessageSeverity>(flags);
|
||||
}
|
||||
|
||||
[[nodiscard]] /*static*/ auto Messenger::to_native_type(MessageType type)
|
||||
-> VkDebugUtilsMessageTypeFlagsEXT
|
||||
{
|
||||
using enum MessageType;
|
||||
|
||||
const auto value = std::to_underlying(type);
|
||||
auto flags = VkDebugUtilsMessageTypeFlagsEXT {};
|
||||
|
||||
if (value & std::to_underlying(general))
|
||||
{
|
||||
flags |= VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT;
|
||||
}
|
||||
|
||||
if (value & std::to_underlying(validation))
|
||||
{
|
||||
flags |= VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
|
||||
}
|
||||
|
||||
if (value & std::to_underlying(performance))
|
||||
{
|
||||
flags |= VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
[[nodiscard]] /* static */ auto Messenger::from_native_type(VkDebugUtilsMessageTypeFlagsEXT type)
|
||||
-> MessageType
|
||||
{
|
||||
using enum MessageType;
|
||||
|
||||
auto flags = std::underlying_type_t<MessageType> {};
|
||||
|
||||
if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT)
|
||||
{
|
||||
flags |= std::to_underlying(general);
|
||||
}
|
||||
|
||||
if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)
|
||||
{
|
||||
flags |= std::to_underlying(validation);
|
||||
}
|
||||
|
||||
if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT)
|
||||
{
|
||||
flags |= std::to_underlying(general);
|
||||
}
|
||||
|
||||
return static_cast<MessageType>(flags);
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
57
modules/renderer/private/backend/vk/messenger.hpp
Normal file
57
modules/renderer/private/backend/vk/messenger.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include <any>
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/backend/vk/context/instance.hpp>
|
||||
#include <renderer/backend/vk/vulkan.hpp>
|
||||
#include <renderer/components/messenger.hpp>
|
||||
#include <renderer/frontend/messenger.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Messenger: public IMessenger
|
||||
{
|
||||
public:
|
||||
Messenger(IInstance *instance, ecs::Entity entity);
|
||||
|
||||
~Messenger() override;
|
||||
|
||||
Messenger(Messenger &&) = default;
|
||||
|
||||
Messenger(const Messenger &) = delete;
|
||||
|
||||
auto operator=(Messenger &&) -> Messenger & = default;
|
||||
|
||||
auto operator=(const Messenger &) const -> Messenger & = delete;
|
||||
|
||||
|
||||
private:
|
||||
static auto native_callback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT type,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
|
||||
void *vulkan_user_data
|
||||
) -> VkBool32;
|
||||
|
||||
[[nodiscard]] static auto to_native_severity(MessageSeverity severity)
|
||||
-> VkDebugUtilsMessageSeverityFlagsEXT;
|
||||
|
||||
[[nodiscard]] static auto from_native_severity(VkDebugUtilsMessageSeverityFlagsEXT severity)
|
||||
-> MessageSeverity;
|
||||
|
||||
[[nodiscard]] static auto to_native_type(MessageType type) -> VkDebugUtilsMessageTypeFlagsEXT;
|
||||
|
||||
[[nodiscard]] static auto from_native_type(VkDebugUtilsMessageTypeFlagsEXT type) -> MessageType;
|
||||
|
||||
memory::NullOnMove<class Instance *> m_instance {};
|
||||
|
||||
ecs::Entity m_entity;
|
||||
|
||||
VkDebugUtilsMessengerEXT m_debug_messenger = VK_NULL_HANDLE;
|
||||
|
||||
MessageSeverity m_severities {};
|
||||
|
||||
MessageType m_types {};
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,6 +1,6 @@
|
|||
#include <renderer/vk/context/surface.hpp>
|
||||
#include <renderer/vk/debug/messenger.hpp>
|
||||
#include <renderer/vk/test_utils.hpp>
|
||||
#include <renderer/backend/vk/context/surface.hpp>
|
||||
#include <renderer/backend/vk/debug/messenger.hpp>
|
||||
#include <renderer/backend/vk/test_utils.hpp>
|
||||
#include <surface/components.hpp>
|
||||
#include <surface/system.hpp>
|
||||
#include <test/expects.hpp>
|
225
modules/renderer/private/backend/vk/renderer/pass.cpp
Normal file
225
modules/renderer/private/backend/vk/renderer/pass.cpp
Normal file
|
@ -0,0 +1,225 @@
|
|||
#include <renderer/backend/vk/context/device.hpp>
|
||||
#include <renderer/backend/vk/context/swapchain.hpp>
|
||||
#include <renderer/backend/vk/renderer/pass.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Pass::Pass(
|
||||
IContext &context,
|
||||
lt::assets::ShaderAsset vertex_shader,
|
||||
lt::assets::ShaderAsset fragment_shader
|
||||
)
|
||||
: m_device(static_cast<Device *>(context.device()))
|
||||
{
|
||||
auto *vertex_module = create_module(
|
||||
vertex_shader.unpack(lt::assets::ShaderAsset::BlobTag::code)
|
||||
);
|
||||
|
||||
auto *fragment_module = create_module(
|
||||
fragment_shader.unpack(lt::assets::ShaderAsset::BlobTag::code)
|
||||
);
|
||||
|
||||
auto shader_stages = std::array<VkPipelineShaderStageCreateInfo, 2> {
|
||||
VkPipelineShaderStageCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.stage = VK_SHADER_STAGE_VERTEX_BIT,
|
||||
.module = vertex_module,
|
||||
.pName = "main",
|
||||
},
|
||||
VkPipelineShaderStageCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.module = fragment_module,
|
||||
.pName = "main",
|
||||
},
|
||||
};
|
||||
|
||||
auto dynamic_states = std::array<VkDynamicState, 2> {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
};
|
||||
|
||||
auto dynamic_state = VkPipelineDynamicStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size()),
|
||||
.pDynamicStates = dynamic_states.data(),
|
||||
};
|
||||
|
||||
auto vertex_input = VkPipelineVertexInputStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
||||
};
|
||||
|
||||
auto input_assembly = VkPipelineInputAssemblyStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
.primitiveRestartEnable = VK_FALSE,
|
||||
};
|
||||
|
||||
auto viewport_state = VkPipelineViewportStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.viewportCount = 1u,
|
||||
.scissorCount = 1u,
|
||||
};
|
||||
|
||||
auto rasterization = VkPipelineRasterizationStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||
.depthClampEnable = VK_FALSE,
|
||||
.rasterizerDiscardEnable = VK_FALSE,
|
||||
.polygonMode = VK_POLYGON_MODE_FILL,
|
||||
.cullMode = VK_CULL_MODE_NONE,
|
||||
.frontFace = VK_FRONT_FACE_CLOCKWISE,
|
||||
.lineWidth = 1.0,
|
||||
};
|
||||
|
||||
auto multisampling = VkPipelineMultisampleStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.sampleShadingEnable = VK_FALSE,
|
||||
.minSampleShading = 1.0,
|
||||
.pSampleMask = nullptr,
|
||||
.alphaToCoverageEnable = VK_FALSE,
|
||||
.alphaToOneEnable = VK_FALSE,
|
||||
};
|
||||
|
||||
auto color_blend_attachment = VkPipelineColorBlendAttachmentState {
|
||||
.blendEnable = VK_FALSE,
|
||||
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
|
||||
.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
.colorBlendOp = VK_BLEND_OP_ADD,
|
||||
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
|
||||
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
.alphaBlendOp = VK_BLEND_OP_ADD,
|
||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
|
||||
| VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
||||
};
|
||||
|
||||
auto color_blend = VkPipelineColorBlendStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||
.logicOpEnable = VK_FALSE,
|
||||
.logicOp = VK_LOGIC_OP_COPY,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = &color_blend_attachment,
|
||||
.blendConstants = { 0.0f, 0.0, 0.0, 0.0 },
|
||||
};
|
||||
|
||||
m_layout = m_device->create_pipeline_layout(
|
||||
VkPipelineLayoutCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.setLayoutCount = 0u,
|
||||
.pSetLayouts = nullptr,
|
||||
.pushConstantRangeCount = 0u,
|
||||
.pPushConstantRanges = nullptr,
|
||||
}
|
||||
);
|
||||
|
||||
auto attachment_description = VkAttachmentDescription {
|
||||
.format = static_cast<Swapchain *>(context.swapchain())->get_format(),
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
};
|
||||
|
||||
auto color_attachment_ref = VkAttachmentReference {
|
||||
.attachment = 0,
|
||||
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
};
|
||||
|
||||
auto subpass_description = VkSubpassDescription {
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.colorAttachmentCount = 1u,
|
||||
.pColorAttachments = &color_attachment_ref,
|
||||
};
|
||||
|
||||
auto pass_dependency = VkSubpassDependency {
|
||||
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.dstSubpass = 0u,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
.srcAccessMask = 0u,
|
||||
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
};
|
||||
|
||||
m_pass = m_device->create_pass(
|
||||
VkRenderPassCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||
.attachmentCount = 1u,
|
||||
.pAttachments = &attachment_description,
|
||||
.subpassCount = 1u,
|
||||
.pSubpasses = &subpass_description,
|
||||
.dependencyCount = 1u,
|
||||
.pDependencies = &pass_dependency,
|
||||
}
|
||||
);
|
||||
|
||||
m_pipeline = m_device->create_graphics_pipeline(
|
||||
VkGraphicsPipelineCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.stageCount = static_cast<uint32_t>(shader_stages.size()),
|
||||
.pStages = shader_stages.data(),
|
||||
.pVertexInputState = &vertex_input,
|
||||
.pInputAssemblyState = &input_assembly,
|
||||
.pViewportState = &viewport_state,
|
||||
.pRasterizationState = &rasterization,
|
||||
.pMultisampleState = &multisampling,
|
||||
.pDepthStencilState = nullptr,
|
||||
.pColorBlendState = &color_blend,
|
||||
.pDynamicState = &dynamic_state,
|
||||
.layout = m_layout,
|
||||
.renderPass = m_pass,
|
||||
.subpass = 0u,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = -1,
|
||||
}
|
||||
);
|
||||
|
||||
m_framebuffers = static_cast<Swapchain *>(context.swapchain())
|
||||
->create_framebuffers_for_pass(m_pass);
|
||||
|
||||
|
||||
m_device->destroy_shader_module(vertex_module);
|
||||
m_device->destroy_shader_module(fragment_module);
|
||||
}
|
||||
|
||||
Pass::~Pass()
|
||||
{
|
||||
if (!m_device)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_device->wait_idle();
|
||||
m_device->destroy_framebuffers(m_framebuffers);
|
||||
m_device->destroy_pipeline(m_pipeline);
|
||||
m_device->destroy_pass(m_pass);
|
||||
m_device->destroy_pipeline_layout(m_layout);
|
||||
}
|
||||
|
||||
void Pass::replace_swapchain(const ISwapchain &swapchain)
|
||||
{
|
||||
if (!m_device)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_device->wait_idle();
|
||||
m_device->destroy_framebuffers(m_framebuffers);
|
||||
m_framebuffers = static_cast<const Swapchain &>(swapchain).create_framebuffers_for_pass(m_pass);
|
||||
}
|
||||
|
||||
auto Pass::create_module(lt::assets::Blob blob) -> VkShaderModule
|
||||
{
|
||||
return m_device->create_shader_module(
|
||||
VkShaderModuleCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||
.codeSize = blob.size(),
|
||||
.pCode = reinterpret_cast<const uint32_t *>(blob.data()) // NOLINT
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
} // namespace lt::renderer::vk
|
61
modules/renderer/private/backend/vk/renderer/pass.hpp
Normal file
61
modules/renderer/private/backend/vk/renderer/pass.hpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/shader.hpp>
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/backend/vk/utils.hpp>
|
||||
#include <renderer/frontend/context/context.hpp>
|
||||
#include <renderer/frontend/renderer/pass.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Pass: public IPass
|
||||
{
|
||||
public:
|
||||
Pass(
|
||||
IContext &context,
|
||||
lt::assets::ShaderAsset vertex_shader,
|
||||
lt::assets::ShaderAsset fragment_shader
|
||||
);
|
||||
|
||||
~Pass() override;
|
||||
|
||||
Pass(Pass &&) = default;
|
||||
|
||||
Pass(const Pass &) = delete;
|
||||
|
||||
auto operator=(Pass &&) -> Pass & = default;
|
||||
|
||||
auto operator=(const Pass &) -> Pass & = delete;
|
||||
|
||||
void replace_swapchain(const ISwapchain &swapchain);
|
||||
|
||||
[[nodiscard]] auto get_pass() -> VkRenderPass
|
||||
{
|
||||
return m_pass;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_pipeline() -> VkPipeline
|
||||
{
|
||||
return m_pipeline;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_framebuffers() -> std::vector<VkFramebuffer> &
|
||||
{
|
||||
return m_framebuffers;
|
||||
}
|
||||
|
||||
private:
|
||||
auto create_module(lt::assets::Blob blob) -> VkShaderModule;
|
||||
|
||||
memory::NullOnMove<class Device *> m_device {};
|
||||
|
||||
VkRenderPass m_pass = VK_NULL_HANDLE;
|
||||
|
||||
VkPipeline m_pipeline = VK_NULL_HANDLE;
|
||||
|
||||
VkPipelineLayout m_layout = VK_NULL_HANDLE;
|
||||
|
||||
std::vector<VkFramebuffer> m_framebuffers;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
191
modules/renderer/private/backend/vk/renderer/renderer.cpp
Normal file
191
modules/renderer/private/backend/vk/renderer/renderer.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
#include <renderer/backend/vk/context/swapchain.hpp>
|
||||
#include <renderer/backend/vk/renderer/renderer.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Renderer::Renderer(IContext &context, uint32_t max_frames_in_flight)
|
||||
: m_device(static_cast<Device *>(context.device()))
|
||||
, m_swapchain(static_cast<Swapchain *>(context.swapchain()))
|
||||
, m_resolution(m_swapchain->get_resolution())
|
||||
, m_max_frames_in_flight(max_frames_in_flight)
|
||||
{
|
||||
ensure(m_device, "Failed to initialize renderer: null device");
|
||||
ensure(m_swapchain, "Failed to initialize renderer: null swapchain");
|
||||
|
||||
// TODO(Light): HARDCODED PASS!!!
|
||||
m_pass = create_ref<vk::Pass>(
|
||||
context,
|
||||
assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" },
|
||||
assets::ShaderAsset { "./data/test_assets/triangle.frag.asset" }
|
||||
);
|
||||
|
||||
m_pool = m_device->create_command_pool(
|
||||
VkCommandPoolCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||
.queueFamilyIndex = m_device->get_family_indices()[0],
|
||||
}
|
||||
);
|
||||
|
||||
m_cmds.resize(m_max_frames_in_flight);
|
||||
m_cmds = m_device->allocate_command_buffers(
|
||||
VkCommandBufferAllocateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = m_pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = static_cast<uint32_t>(m_cmds.size()),
|
||||
}
|
||||
);
|
||||
|
||||
m_aquire_image_semaphores = m_device->create_semaphores(m_max_frames_in_flight);
|
||||
m_frame_fences = m_device->create_fences(
|
||||
VkFenceCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
},
|
||||
m_max_frames_in_flight
|
||||
);
|
||||
for (auto idx = 0u;
|
||||
auto [semaphore, fence] : std::views::zip(m_aquire_image_semaphores, m_frame_fences))
|
||||
{
|
||||
m_device->name(semaphore, "acquire image semaphore {}", idx);
|
||||
m_device->name(fence, "frame fence {}", idx);
|
||||
}
|
||||
|
||||
m_submit_semaphores = m_device->create_semaphores(m_swapchain->get_image_count());
|
||||
for (auto idx = 0u; auto &semaphore : m_submit_semaphores)
|
||||
{
|
||||
m_device->name(semaphore, "submit semaphore {}", idx);
|
||||
}
|
||||
};
|
||||
|
||||
Renderer::~Renderer()
|
||||
{
|
||||
if (!m_device)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_device->wait_idle();
|
||||
m_device->destroy_semaphores(m_aquire_image_semaphores);
|
||||
m_device->destroy_semaphores(m_submit_semaphores);
|
||||
m_device->destroy_fences(m_frame_fences);
|
||||
m_device->destroy_command_pool(m_pool);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Renderer::draw(uint32_t frame_idx) -> DrawResult
|
||||
{
|
||||
ensure(
|
||||
frame_idx < m_max_frames_in_flight,
|
||||
"Failed to draw: frame_idx >= max_frames_in_flight ({} >= {})",
|
||||
frame_idx,
|
||||
m_max_frames_in_flight
|
||||
);
|
||||
|
||||
auto &frame_fence = m_frame_fences[frame_idx];
|
||||
auto &aquire_semaphore = m_aquire_image_semaphores[frame_idx];
|
||||
auto &cmd = m_cmds[frame_idx];
|
||||
|
||||
m_device->wait_for_fence(frame_fence);
|
||||
|
||||
auto image_idx = m_device->acquire_image(m_swapchain->vk(), aquire_semaphore);
|
||||
if (!image_idx.has_value())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
m_device->reset_fence(frame_fence);
|
||||
vk_reset_command_buffer(cmd, {});
|
||||
record_cmd(cmd, *image_idx);
|
||||
|
||||
auto wait_stage = VkPipelineStageFlags { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
|
||||
auto &submit_semaphore = m_submit_semaphores[*image_idx];
|
||||
m_device->submit(
|
||||
VkSubmitInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.waitSemaphoreCount = 1u,
|
||||
.pWaitSemaphores = &aquire_semaphore,
|
||||
.pWaitDstStageMask = &wait_stage,
|
||||
.commandBufferCount = 1u,
|
||||
.pCommandBuffers = &cmd,
|
||||
.signalSemaphoreCount = 1u,
|
||||
.pSignalSemaphores = &submit_semaphore,
|
||||
},
|
||||
frame_fence
|
||||
);
|
||||
|
||||
// TODO(Light): handle result
|
||||
auto result = VkResult {};
|
||||
m_device->present(
|
||||
VkPresentInfoKHR {
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
.waitSemaphoreCount = 1u,
|
||||
.pWaitSemaphores = &submit_semaphore,
|
||||
.swapchainCount = 1u,
|
||||
.pSwapchains = m_swapchain->vk_ptr(),
|
||||
.pImageIndices = &image_idx.value(),
|
||||
.pResults = &result,
|
||||
}
|
||||
);
|
||||
return DrawResult::success;
|
||||
}
|
||||
|
||||
void Renderer::replace_swapchain(ISwapchain *swapchain)
|
||||
{
|
||||
m_device->wait_idle();
|
||||
m_swapchain = static_cast<Swapchain *>(swapchain);
|
||||
m_resolution = m_swapchain->get_resolution();
|
||||
}
|
||||
|
||||
void Renderer::record_cmd(VkCommandBuffer cmd, uint32_t image_idx)
|
||||
{
|
||||
auto cmd_begin_info = VkCommandBufferBeginInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = {},
|
||||
.pInheritanceInfo = nullptr,
|
||||
};
|
||||
|
||||
vkc(vk_begin_command_buffer(cmd, &cmd_begin_info));
|
||||
|
||||
auto clear_value = VkClearValue {
|
||||
.color = {
|
||||
0.93,
|
||||
0.93,
|
||||
0.93,
|
||||
1.0,
|
||||
},
|
||||
};
|
||||
|
||||
auto pass_begin_info = VkRenderPassBeginInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.renderPass = m_pass->get_pass(),
|
||||
.framebuffer = m_pass->get_framebuffers()[image_idx],
|
||||
.renderArea = { .offset = {}, .extent = m_resolution },
|
||||
.clearValueCount = 1u,
|
||||
.pClearValues = &clear_value
|
||||
};
|
||||
vk_cmd_begin_render_pass(cmd, &pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vk_cmd_bind_pipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pass->get_pipeline());
|
||||
|
||||
auto viewport = VkViewport {
|
||||
.x = 0.0f,
|
||||
.y = 0.0f,
|
||||
.width = static_cast<float>(m_resolution.width),
|
||||
.height = static_cast<float>(m_resolution.height),
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f,
|
||||
};
|
||||
vk_cmd_set_viewport(cmd, 0, 1, &viewport);
|
||||
|
||||
auto scissor = VkRect2D {
|
||||
.offset = { 0u, 0u },
|
||||
.extent = m_resolution,
|
||||
};
|
||||
vk_cmd_set_scissors(cmd, 0, 1, &scissor);
|
||||
|
||||
vk_cmd_draw(cmd, 3, 1, 0, 0);
|
||||
vk_cmd_end_render_pass(cmd);
|
||||
vkc(vk_end_command_buffer(cmd));
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
57
modules/renderer/private/backend/vk/renderer/renderer.hpp
Normal file
57
modules/renderer/private/backend/vk/renderer/renderer.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#pragma once
|
||||
|
||||
#include <ranges>
|
||||
#include <renderer/backend/vk/context/context.hpp>
|
||||
#include <renderer/backend/vk/context/device.hpp>
|
||||
#include <renderer/backend/vk/renderer/pass.hpp>
|
||||
#include <renderer/backend/vk/utils.hpp>
|
||||
#include <renderer/frontend/context/context.hpp>
|
||||
#include <renderer/frontend/renderer/pass.hpp>
|
||||
#include <renderer/frontend/renderer/renderer.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Renderer: public IRenderer
|
||||
{
|
||||
public:
|
||||
Renderer(IContext &context, uint32_t max_frames_in_flight);
|
||||
|
||||
~Renderer() override;
|
||||
|
||||
Renderer(Renderer &&) = default;
|
||||
|
||||
Renderer(const Renderer &) = delete;
|
||||
|
||||
auto operator=(Renderer &&) -> Renderer & = default;
|
||||
|
||||
auto operator=(const Renderer &) -> Renderer & = delete;
|
||||
|
||||
[[nodiscard]] DrawResult draw(uint32_t frame_idx) override;
|
||||
|
||||
void replace_swapchain(ISwapchain *swapchain) override;
|
||||
|
||||
private:
|
||||
void record_cmd(VkCommandBuffer cmd, uint32_t image_idx);
|
||||
|
||||
memory::NullOnMove<class Device *> m_device {};
|
||||
|
||||
class Swapchain *m_swapchain {};
|
||||
|
||||
Ref<class Pass> m_pass;
|
||||
|
||||
VkCommandPool m_pool = VK_NULL_HANDLE;
|
||||
|
||||
std::vector<VkCommandBuffer> m_cmds;
|
||||
|
||||
std::vector<VkFence> m_frame_fences;
|
||||
|
||||
std::vector<VkSemaphore> m_aquire_image_semaphores;
|
||||
|
||||
std::vector<VkSemaphore> m_submit_semaphores;
|
||||
|
||||
VkExtent2D m_resolution;
|
||||
|
||||
uint32_t m_max_frames_in_flight {};
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,9 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <ranges>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/context/surface.hpp>
|
||||
#include <renderer/vk/debug/messenger.hpp>
|
||||
#include <renderer/backend/vk/context/context.hpp>
|
||||
#include <renderer/backend/vk/context/surface.hpp>
|
||||
#include <renderer/backend/vk/debug/messenger.hpp>
|
||||
#include <surface/components.hpp>
|
||||
#include <surface/system.hpp>
|
||||
#include <test/test.hpp>
|
20
modules/renderer/private/backend/vk/utils.hpp
Normal file
20
modules/renderer/private/backend/vk/utils.hpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <renderer/backend/vk/vulkan.hpp>
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
inline void vkc(VkResult result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
throw std::runtime_error { std::format(
|
||||
"Vulkan call failed with result: {}({})",
|
||||
string_VkResult(result),
|
||||
std::to_underlying(result)
|
||||
) };
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -8,15 +8,6 @@
|
|||
namespace lt::renderer::vk {
|
||||
|
||||
// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
// global functions
|
||||
extern PFN_vkGetInstanceProcAddr vk_get_instance_proc_address;
|
||||
extern PFN_vkCreateInstance vk_create_instance;
|
||||
extern PFN_vkEnumerateInstanceExtensionProperties vk_enumerate_instance_extension_properties;
|
||||
extern PFN_vkEnumerateInstanceLayerProperties vk_enumerate_instance_layer_properties;
|
||||
|
||||
// instance functions
|
||||
extern PFN_vkDestroyInstance vk_destroy_instance;
|
||||
extern PFN_vkEnumeratePhysicalDevices vk_enumerate_physical_devices;
|
||||
extern PFN_vkGetPhysicalDeviceProperties vk_get_physical_device_properties;
|
||||
extern PFN_vkGetPhysicalDeviceQueueFamilyProperties vk_get_physical_device_queue_family_properties;
|
||||
extern PFN_vkCreateDevice vk_create_device;
|
||||
|
@ -42,8 +33,6 @@ extern PFN_vkSubmitDebugUtilsMessageEXT vk_submit_debug_message;
|
|||
extern PFN_vkGetPhysicalDeviceSurfaceSupportKHR vk_get_physical_device_surface_support;
|
||||
extern PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vk_get_physical_device_surface_capabilities;
|
||||
extern PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vk_get_physical_device_surface_formats;
|
||||
extern PFN_vkCreateXlibSurfaceKHR vk_create_xlib_surface_khr;
|
||||
extern PFN_vkDestroySurfaceKHR vk_destroy_surface_khr;
|
||||
|
||||
// device functions
|
||||
extern PFN_vkGetDeviceQueue vk_get_device_queue;
|
16
modules/renderer/private/frontend/context/context.cpp
Normal file
16
modules/renderer/private/frontend/context/context.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include <renderer/api.hpp>
|
||||
#include <renderer/backend/vk/context/context.hpp>
|
||||
#include <renderer/frontend/context/context.hpp>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
auto IContext::create(API target_api, const ecs::Entity &surface_entity) -> Scope<IContext>
|
||||
{
|
||||
switch (target_api)
|
||||
{
|
||||
case API::Vulkan: return create_scope<vk::Context>(surface_entity);
|
||||
default: throw std::runtime_error { "Invalid API" };
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt::renderer
|
44
modules/renderer/private/frontend/context/context.hpp
Normal file
44
modules/renderer/private/frontend/context/context.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <ecs/entity.hpp>
|
||||
#include <renderer/api.hpp>
|
||||
#include <renderer/frontend/context/device.hpp>
|
||||
#include <renderer/frontend/context/gpu.hpp>
|
||||
#include <renderer/frontend/context/instance.hpp>
|
||||
#include <renderer/frontend/context/surface.hpp>
|
||||
#include <renderer/frontend/context/swapchain.hpp>
|
||||
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
class IContext
|
||||
{
|
||||
public:
|
||||
static auto create(API target_api, const ecs::Entity &surface_entity) -> Scope<IContext>;
|
||||
IContext() = default;
|
||||
|
||||
virtual ~IContext() = default;
|
||||
|
||||
IContext(IContext &&) = default;
|
||||
|
||||
IContext(const IContext &) = delete;
|
||||
|
||||
auto operator=(IContext &&) -> IContext & = default;
|
||||
|
||||
auto operator=(const IContext &) -> IContext & = delete;
|
||||
|
||||
virtual void recreate_swapchain() = 0;
|
||||
|
||||
[[nodiscard]] virtual auto instance() const -> IInstance * = 0;
|
||||
|
||||
[[nodiscard]] virtual auto surface() const -> ISurface * = 0;
|
||||
|
||||
[[nodiscard]] virtual auto gpu() const -> IGpu * = 0;
|
||||
|
||||
[[nodiscard]] virtual auto device() const -> IDevice * = 0;
|
||||
|
||||
[[nodiscard]] virtual auto swapchain() const -> ISwapchain * = 0;
|
||||
};
|
||||
|
||||
|
||||
} // namespace lt::renderer
|
54
modules/renderer/private/frontend/context/context.test.cpp
Normal file
54
modules/renderer/private/frontend/context/context.test.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include <ranges>
|
||||
#include <renderer/interface/context.hpp>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/context/surface.hpp>
|
||||
#include <renderer/vk/debug/messenger.hpp>
|
||||
#include <surface/components.hpp>
|
||||
#include <surface/system.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
||||
using lt::renderer::API;
|
||||
using lt::renderer::IContext;
|
||||
using ::lt::test::Case;
|
||||
using ::lt::test::expect_eq;
|
||||
using ::lt::test::expect_false;
|
||||
using ::lt::test::expect_not_nullptr;
|
||||
using ::lt::test::expect_throw;
|
||||
using ::lt::test::expect_true;
|
||||
using ::lt::test::Suite;
|
||||
using ::std::ignore;
|
||||
|
||||
namespace constants {
|
||||
|
||||
constexpr auto resolution = lt::math::uvec2 { 800u, 600u };
|
||||
|
||||
}
|
||||
|
||||
|
||||
class Fixture
|
||||
{
|
||||
public:
|
||||
Fixture()
|
||||
: m_registry(lt::create_ref<lt::ecs::Registry>())
|
||||
, m_surface_system(lt::create_scope<lt::surface::System>(m_registry))
|
||||
, m_surface_entity(m_registry, m_registry->create_entity())
|
||||
{
|
||||
}
|
||||
|
||||
auto get_surface_entity() -> lt::ecs::Entity
|
||||
{
|
||||
return m_surface_entity;
|
||||
}
|
||||
|
||||
private:
|
||||
lt::Ref<lt::ecs::Registry> m_registry;
|
||||
lt::Scope<lt::surface::System> m_surface_system;
|
||||
lt::ecs::Entity m_surface_entity;
|
||||
};
|
||||
|
||||
Suite raii = "context_raii"_suite = [] {
|
||||
Case { "Happy path won't throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
IContext::create(API::Vulkan, fixture.get_surface_entity());
|
||||
};
|
||||
};
|
21
modules/renderer/private/frontend/context/device.hpp
Normal file
21
modules/renderer/private/frontend/context/device.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
class IDevice
|
||||
{
|
||||
public:
|
||||
IDevice() = default;
|
||||
|
||||
virtual ~IDevice() = default;
|
||||
|
||||
IDevice(IDevice &&) = default;
|
||||
|
||||
IDevice(const IDevice &) = delete;
|
||||
|
||||
auto operator=(IDevice &&) -> IDevice & = default;
|
||||
|
||||
auto operator=(const IDevice &) -> IDevice & = delete;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
21
modules/renderer/private/frontend/context/gpu.hpp
Normal file
21
modules/renderer/private/frontend/context/gpu.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
class IGpu
|
||||
{
|
||||
public:
|
||||
IGpu() = default;
|
||||
|
||||
virtual ~IGpu() = default;
|
||||
|
||||
IGpu(IGpu &&) = default;
|
||||
|
||||
IGpu(const IGpu &) = delete;
|
||||
|
||||
auto operator=(IGpu &&) -> IGpu & = default;
|
||||
|
||||
auto operator=(const IGpu &) -> IGpu & = delete;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
21
modules/renderer/private/frontend/context/instance.hpp
Normal file
21
modules/renderer/private/frontend/context/instance.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
class IInstance
|
||||
{
|
||||
public:
|
||||
IInstance() = default;
|
||||
|
||||
virtual ~IInstance() = default;
|
||||
|
||||
IInstance(IInstance &&) = default;
|
||||
|
||||
IInstance(const IInstance &) = delete;
|
||||
|
||||
auto operator=(IInstance &&) -> IInstance & = default;
|
||||
|
||||
auto operator=(const IInstance &) -> IInstance & = delete;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
21
modules/renderer/private/frontend/context/surface.hpp
Normal file
21
modules/renderer/private/frontend/context/surface.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
class ISurface
|
||||
{
|
||||
public:
|
||||
ISurface() = default;
|
||||
|
||||
virtual ~ISurface() = default;
|
||||
|
||||
ISurface(ISurface &&) = default;
|
||||
|
||||
ISurface(const ISurface &) = delete;
|
||||
|
||||
auto operator=(ISurface &&) -> ISurface & = default;
|
||||
|
||||
auto operator=(const ISurface &) -> ISurface & = delete;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
21
modules/renderer/private/frontend/context/swapchain.hpp
Normal file
21
modules/renderer/private/frontend/context/swapchain.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
class ISwapchain
|
||||
{
|
||||
public:
|
||||
ISwapchain() = default;
|
||||
|
||||
virtual ~ISwapchain() = default;
|
||||
|
||||
ISwapchain(ISwapchain &&) = default;
|
||||
|
||||
ISwapchain(const ISwapchain &) = delete;
|
||||
|
||||
auto operator=(ISwapchain &&) -> ISwapchain & = default;
|
||||
|
||||
auto operator=(const ISwapchain &) -> ISwapchain & = delete;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
20
modules/renderer/private/frontend/messenger.cpp
Normal file
20
modules/renderer/private/frontend/messenger.cpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
#include <renderer/backend/vk/messenger.hpp>
|
||||
#include <renderer/frontend/messenger.hpp>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
[[nodiscard]] /* static */ auto IMessenger::create(
|
||||
API target_api,
|
||||
IInstance *instance,
|
||||
ecs::Entity entity
|
||||
) -> Scope<IMessenger>
|
||||
{
|
||||
switch (target_api)
|
||||
{
|
||||
case API::Vulkan: return create_scope<vk::Messenger>(instance, std::move(entity));
|
||||
|
||||
case API::Metal:
|
||||
case API::DirectX: throw std::runtime_error { "Invalid API" };
|
||||
}
|
||||
}
|
||||
} // namespace lt::renderer
|
27
modules/renderer/private/frontend/messenger.hpp
Normal file
27
modules/renderer/private/frontend/messenger.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <ecs/entity.hpp>
|
||||
#include <renderer/api.hpp>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
class IMessenger
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] static auto create(API target_api, class IInstance *instance, ecs::Entity entity)
|
||||
-> Scope<IMessenger>;
|
||||
|
||||
IMessenger() = default;
|
||||
|
||||
virtual ~IMessenger() = default;
|
||||
|
||||
IMessenger(IMessenger &&) = default;
|
||||
|
||||
IMessenger(const IMessenger &) = delete;
|
||||
|
||||
auto operator=(IMessenger &&) -> IMessenger & = default;
|
||||
|
||||
auto operator=(const IMessenger &) -> IMessenger & = delete;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
25
modules/renderer/private/frontend/renderer/pass.hpp
Normal file
25
modules/renderer/private/frontend/renderer/pass.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <renderer/frontend/context/swapchain.hpp>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
class IPass
|
||||
{
|
||||
public:
|
||||
IPass() = default;
|
||||
|
||||
virtual ~IPass() = default;
|
||||
|
||||
IPass(IPass &&) = default;
|
||||
|
||||
IPass(const IPass &) = delete;
|
||||
|
||||
auto operator=(IPass &&) -> IPass & = default;
|
||||
|
||||
auto operator=(const IPass &) -> IPass & = delete;
|
||||
|
||||
void replace_swapchain(const ISwapchain &swapchain);
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
17
modules/renderer/private/frontend/renderer/renderer.cpp
Normal file
17
modules/renderer/private/frontend/renderer/renderer.cpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#include <renderer/api.hpp>
|
||||
#include <renderer/backend/vk/renderer/renderer.hpp>
|
||||
#include <renderer/frontend/renderer/renderer.hpp>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
auto IRenderer::create(API target_api, IContext &context, uint32_t max_frames_in_flight)
|
||||
-> Scope<IRenderer>
|
||||
{
|
||||
switch (target_api)
|
||||
{
|
||||
case API::Vulkan: return create_scope<vk::Renderer>(context, max_frames_in_flight);
|
||||
default: throw std::runtime_error { "Invalid API" };
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt::renderer
|
37
modules/renderer/private/frontend/renderer/renderer.hpp
Normal file
37
modules/renderer/private/frontend/renderer/renderer.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <renderer/api.hpp>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
class IRenderer
|
||||
{
|
||||
public:
|
||||
enum class DrawResult : uint8_t
|
||||
{
|
||||
success = 0,
|
||||
invalid_swapchain,
|
||||
error,
|
||||
};
|
||||
|
||||
static auto create(API target_api, class IContext &context, uint32_t max_frames_in_flight)
|
||||
-> Scope<IRenderer>;
|
||||
|
||||
IRenderer() = default;
|
||||
|
||||
virtual ~IRenderer() = default;
|
||||
|
||||
IRenderer(IRenderer &&) = default;
|
||||
|
||||
IRenderer(const IRenderer &) = delete;
|
||||
|
||||
auto operator=(IRenderer &&) -> IRenderer & = default;
|
||||
|
||||
auto operator=(const IRenderer &) -> IRenderer & = delete;
|
||||
|
||||
[[nodiscard]] virtual auto draw(uint32_t frame_idx) -> DrawResult = 0;
|
||||
|
||||
virtual void replace_swapchain(class ISwapchain *swapchain) = 0;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
|
@ -1,92 +1,28 @@
|
|||
#include <renderer/components/messenger.hpp>
|
||||
#include <renderer/frontend/context/context.hpp>
|
||||
#include <renderer/frontend/messenger.hpp>
|
||||
#include <renderer/frontend/renderer/pass.hpp>
|
||||
#include <renderer/frontend/renderer/renderer.hpp>
|
||||
#include <renderer/system.hpp>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/debug/messenger.hpp>
|
||||
#include <renderer/vk/renderer/pass.hpp>
|
||||
#include <renderer/vk/renderer/renderer.hpp>
|
||||
|
||||
using ::lt::assets::ShaderAsset;
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
class ValidationObserver
|
||||
{
|
||||
using Messenger = lt::renderer::vk::Messenger;
|
||||
using enum Messenger::Type;
|
||||
using enum Messenger::Severity;
|
||||
|
||||
public:
|
||||
ValidationObserver()
|
||||
: m_messenger(
|
||||
Messenger::CreateInfo {
|
||||
.severity = static_cast<Messenger::Severity>(warning | error),
|
||||
.type = lt::renderer::vk::Messenger::all_type,
|
||||
.callback = &callback,
|
||||
.user_data = &m_had_any_messages,
|
||||
}
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
~ValidationObserver()
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto had_any_messages() const -> bool
|
||||
{
|
||||
return m_had_any_messages;
|
||||
}
|
||||
|
||||
private:
|
||||
static void callback(
|
||||
Messenger::Severity message_severity,
|
||||
Messenger::Type message_type,
|
||||
Messenger::CallbackData_T vulkan_data,
|
||||
void *user_data
|
||||
)
|
||||
{
|
||||
std::ignore = message_severity;
|
||||
std::ignore = message_type;
|
||||
for (auto idx = 0; idx < vulkan_data->objectCount; ++idx)
|
||||
{
|
||||
auto object = vulkan_data->pObjects[idx];
|
||||
std::println(
|
||||
"0x{:x}({}) = {}",
|
||||
object.objectHandle,
|
||||
string_VkObjectType(object.objectType),
|
||||
object.pObjectName ? object.pObjectName : "unnamed"
|
||||
);
|
||||
}
|
||||
|
||||
std::println("Validation message: {}", vulkan_data->pMessage);
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
|
||||
*(bool *)user_data = true;
|
||||
}
|
||||
|
||||
Messenger m_messenger;
|
||||
bool m_had_any_messages = false;
|
||||
};
|
||||
} // namespace lt::renderer::vk
|
||||
|
||||
#include <surface/components.hpp>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
System::System(CreateInfo info)
|
||||
: m_registry(std::move(info.registry))
|
||||
, m_stats(info.system_stats)
|
||||
, m_context(create_scope<vk::Context>(info.surface_entity))
|
||||
: m_api(info.config.target_api)
|
||||
, m_registry(std::move(info.registry))
|
||||
, m_context(IContext::create(m_api, info.surface_entity))
|
||||
, m_surface_entity(info.surface_entity)
|
||||
, m_max_frames_in_flight(info.config.max_frames_in_flight)
|
||||
{
|
||||
m_validation_observer = new vk::ValidationObserver();
|
||||
// ensure(m_stats, "Failed to initialize system: null stats");
|
||||
ensure(m_registry, "Failed to initialize renderer system: null registry");
|
||||
|
||||
m_pass = create_ref<vk::Pass>(
|
||||
*m_context,
|
||||
ShaderAsset { "./data/test_assets/triangle.vert.asset" },
|
||||
ShaderAsset { "./data/test_assets/triangle.frag.asset" }
|
||||
);
|
||||
m_renderer = IRenderer::create(m_api, *m_context, info.config.max_frames_in_flight);
|
||||
|
||||
m_renderer = create_scope<vk::Renderer>(*m_context, m_pass);
|
||||
// WIP(Light): attach debug messenger on messenger component construction
|
||||
m_registry->connect_on_construct<renderer::MessengerComponent>([](ecs::Registry ®istry,
|
||||
ecs::EntityId entity) {});
|
||||
}
|
||||
|
||||
System::~System()
|
||||
|
@ -103,25 +39,27 @@ void System::on_unregister()
|
|||
|
||||
void System::tick(app::TickInfo tick)
|
||||
{
|
||||
std::ignore = tick;
|
||||
|
||||
for (const auto &event : m_surface_entity.get<surface::SurfaceComponent>().peek_events())
|
||||
{
|
||||
if (std::holds_alternative<surface::ResizedEvent>(event))
|
||||
{
|
||||
m_context->recreate_swapchain();
|
||||
m_renderer->replace_swapchain(m_context->swapchain());
|
||||
m_pass->replace_swapchain(m_context->swapchain());
|
||||
// m_pass->replace_swapchain(m_context->swapchain());
|
||||
}
|
||||
}
|
||||
|
||||
if (m_renderer->draw(m_frame_idx))
|
||||
if (m_renderer->draw(m_frame_idx) != IRenderer::DrawResult::success)
|
||||
{
|
||||
m_context->recreate_swapchain();
|
||||
m_renderer->replace_swapchain(m_context->swapchain());
|
||||
m_pass->replace_swapchain(m_context->swapchain());
|
||||
m_renderer->draw(m_frame_idx); // don't drop the frame
|
||||
// m_pass->replace_swapchain(m_context->swapchain());
|
||||
std::ignore = m_renderer->draw(m_frame_idx); // drop the frame if failed twice
|
||||
}
|
||||
|
||||
m_frame_idx = (m_frame_idx + 1) % vk::Renderer::max_frames_in_flight;
|
||||
m_frame_idx = (m_frame_idx + 1) % m_max_frames_in_flight;
|
||||
}
|
||||
|
||||
} // namespace lt::renderer
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Context::Context(const ecs::Entity &surface_entity)
|
||||
: m_surface(surface_entity)
|
||||
, m_device(m_surface)
|
||||
, m_swapchain(m_device, m_surface)
|
||||
{
|
||||
ensure(m_surface.vk(), "Failed to create vulkan context: null surface");
|
||||
ensure(m_device.vk(), "Failed to create vulkan context: null device");
|
||||
ensure(m_swapchain.vk(), "Failed to create vulkan context: null swapchain");
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,52 +0,0 @@
|
|||
#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);
|
||||
|
||||
[[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;
|
||||
}
|
||||
|
||||
void recreate_swapchain()
|
||||
{
|
||||
m_swapchain.destroy();
|
||||
m_swapchain = Swapchain { m_device, m_surface };
|
||||
}
|
||||
|
||||
private:
|
||||
Surface m_surface;
|
||||
|
||||
Device m_device;
|
||||
|
||||
Swapchain m_swapchain;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,175 +0,0 @@
|
|||
#include <renderer/vk/context/device.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
#include <renderer/vk/context/surface.hpp>
|
||||
#include <renderer/vk/debug/validation.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Device::Device(const Surface &surface)
|
||||
{
|
||||
ensure(surface.vk(), "Failed to initialize vk::Device: null vulkan surface");
|
||||
|
||||
initialize_physical_device();
|
||||
initialize_queue_indices(surface);
|
||||
initialize_logical_device();
|
||||
Instance::load_device_functions(m_device);
|
||||
|
||||
vk_get_device_queue(m_device, m_graphics_queue_family_index, 0, &m_graphics_queue);
|
||||
vk_get_device_queue(m_device, m_present_queue_family_index, 0, &m_present_queue);
|
||||
|
||||
if (m_present_queue.get() == m_graphics_queue.get())
|
||||
{
|
||||
set_object_name(m_device, m_present_queue.get(), "unified queue");
|
||||
}
|
||||
else
|
||||
{
|
||||
set_object_name(m_device, m_graphics_queue.get(), "graphics queue");
|
||||
set_object_name(m_device, m_present_queue.get(), "present queue");
|
||||
}
|
||||
}
|
||||
|
||||
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_infos = std::vector<VkDeviceQueueCreateInfo> {};
|
||||
auto queue_families = std::set { m_graphics_queue_family_index, m_present_queue_family_index };
|
||||
|
||||
for (auto queue_family : queue_families)
|
||||
{
|
||||
queue_infos.emplace_back(
|
||||
VkDeviceQueueCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
||||
.queueFamilyIndex = 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 = static_cast<uint32_t>(queue_infos.size()),
|
||||
.pQueueCreateInfos = queue_infos.data(),
|
||||
.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_indices(const Surface &surface)
|
||||
{
|
||||
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
|
|
@ -1,71 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/vk/vulkan.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Device
|
||||
{
|
||||
public:
|
||||
Device(const class 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 };
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_graphics_queue() const -> VkQueue
|
||||
{
|
||||
return m_graphics_queue;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_present_queue() const -> VkQueue
|
||||
{
|
||||
return m_present_queue;
|
||||
}
|
||||
|
||||
private:
|
||||
void initialize_physical_device();
|
||||
|
||||
void initialize_logical_device();
|
||||
|
||||
void initialize_queue_indices(const class 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_graphics_queue = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkQueue> m_present_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,126 +0,0 @@
|
|||
#include <ranges>
|
||||
#include <renderer/vk/context/device.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
#include <renderer/vk/context/surface.hpp>
|
||||
#include <renderer/vk/context/swapchain.hpp>
|
||||
#include <renderer/vk/debug/validation.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Swapchain::Swapchain(const Device &device, const Surface &surface): m_device(device.vk())
|
||||
{
|
||||
static auto idx = 0u;
|
||||
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();
|
||||
m_format = surface_format.format;
|
||||
|
||||
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 = capabilities.currentExtent,
|
||||
.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_KHR, // TODO(Light): parameterize
|
||||
.clipped = VK_TRUE,
|
||||
.oldSwapchain = nullptr,
|
||||
};
|
||||
m_resolution = capabilities.currentExtent;
|
||||
|
||||
vkc(vk_create_swapchain_khr(device.vk(), &create_info, nullptr, &m_swapchain));
|
||||
vkc(vk_device_wait_idle(device.vk()));
|
||||
set_object_name(device.vk(), m_swapchain.get(), "swapchain {}", idx++);
|
||||
|
||||
auto image_count = uint32_t { 0u };
|
||||
vk_get_swapchain_images_khr(device.vk(), m_swapchain, &image_count, nullptr);
|
||||
|
||||
m_images.resize(image_count);
|
||||
m_image_views.resize(image_count);
|
||||
vk_get_swapchain_images_khr(device.vk(), m_swapchain, &image_count, m_images.data());
|
||||
|
||||
for (auto [image, view] : std::views::zip(m_images, m_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()
|
||||
{
|
||||
destroy();
|
||||
}
|
||||
|
||||
[[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
|
|
@ -1,108 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/vk/debug/validation.hpp>
|
||||
#include <renderer/vk/vulkan.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Swapchain
|
||||
{
|
||||
public:
|
||||
Swapchain(const class Device &device, const class Surface &surface);
|
||||
|
||||
~Swapchain();
|
||||
|
||||
Swapchain(Swapchain &&) = default;
|
||||
|
||||
Swapchain(const Swapchain &) = delete;
|
||||
|
||||
auto operator=(Swapchain &&) -> Swapchain & = default;
|
||||
|
||||
auto operator=(const Swapchain &) const -> Swapchain & = delete;
|
||||
|
||||
void destroy()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_device)
|
||||
{
|
||||
vkc(vk_device_wait_idle(m_device));
|
||||
for (auto &view : m_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 vk() const -> VkSwapchainKHR
|
||||
{
|
||||
return m_swapchain;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_resolution() const -> VkExtent2D
|
||||
{
|
||||
return m_resolution;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_format() const -> VkFormat
|
||||
{
|
||||
return m_format;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_image_count() const -> size_t
|
||||
{
|
||||
return m_images.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto create_framebuffers_for_pass(VkRenderPass pass) const
|
||||
-> std::vector<VkFramebuffer>
|
||||
{
|
||||
auto framebuffers = std::vector<VkFramebuffer>(m_image_views.size());
|
||||
|
||||
for (auto idx = 0u; auto &framebuffer : framebuffers)
|
||||
{
|
||||
auto info = VkFramebufferCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
.renderPass = pass,
|
||||
.attachmentCount = 1u,
|
||||
.pAttachments = &m_image_views[idx++],
|
||||
.width = m_resolution.width,
|
||||
.height = m_resolution.height,
|
||||
.layers = 1u
|
||||
};
|
||||
|
||||
vkc(vk_create_frame_buffer(m_device, &info, nullptr, &framebuffer));
|
||||
}
|
||||
|
||||
return framebuffers;
|
||||
}
|
||||
|
||||
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_images;
|
||||
|
||||
std::vector<VkImageView> m_image_views;
|
||||
|
||||
VkExtent2D m_resolution;
|
||||
|
||||
VkFormat m_format;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,143 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory/pointer_types/null_on_move.hpp>
|
||||
#include <renderer/vk/context/instance.hpp>
|
||||
#include <renderer/vk/vulkan.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Messenger
|
||||
{
|
||||
public:
|
||||
// NOLINTNEXTLINE(performance-enum-size)
|
||||
enum Severity : decltype(std::to_underlying(
|
||||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_FLAG_BITS_MAX_ENUM_EXT
|
||||
))
|
||||
{
|
||||
verbose = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT,
|
||||
info = VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT,
|
||||
warning = VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT,
|
||||
error = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
|
||||
|
||||
all_severity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(performance-enum-size)
|
||||
enum Type : decltype(std::to_underlying(VK_DEBUG_UTILS_MESSAGE_TYPE_FLAG_BITS_MAX_ENUM_EXT))
|
||||
{
|
||||
general = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT,
|
||||
validation = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT,
|
||||
performance = VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
|
||||
|
||||
// address_binding = VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT,
|
||||
|
||||
/** @note: does not include address binding yet. */
|
||||
all_type = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
|
||||
};
|
||||
|
||||
using CallbackData_T = const VkDebugUtilsMessengerCallbackDataEXT *;
|
||||
|
||||
using Callback_T = std::function<void(
|
||||
Severity message_severity,
|
||||
Type message_type,
|
||||
CallbackData_T vulkan_data,
|
||||
void *user_data
|
||||
)>;
|
||||
|
||||
struct CreateInfo
|
||||
{
|
||||
Severity severity;
|
||||
|
||||
Type type;
|
||||
|
||||
Callback_T callback;
|
||||
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
explicit Messenger(CreateInfo info)
|
||||
: m_callback(std::move(info.callback))
|
||||
, m_user_data(info.user_data)
|
||||
{
|
||||
ensure(m_callback, "Failed to initialize vk::Messenger: null callback");
|
||||
ensure(info.severity != Severity {}, "Failed to initialize vk::Messenger: null severity");
|
||||
ensure(info.type != Type {}, "Failed to initialize vk::Messenger: null type");
|
||||
|
||||
// Instance may not be initialized yet
|
||||
// Making it get initialized inside a call to vk_create_debug_messenger
|
||||
// Which would invoke a nullptr...
|
||||
if (!vk_create_debug_messenger)
|
||||
{
|
||||
Instance::get();
|
||||
}
|
||||
|
||||
const auto vulkan_info = VkDebugUtilsMessengerCreateInfoEXT {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
||||
.messageSeverity = info.severity,
|
||||
.messageType = info.type,
|
||||
.pfnUserCallback = &validation_layers_callback,
|
||||
.pUserData = this,
|
||||
};
|
||||
|
||||
ensure(
|
||||
!vk_create_debug_messenger(Instance::get(), &vulkan_info, nullptr, &m_debug_messenger),
|
||||
"Failed to create vulkan debug utils messenger"
|
||||
);
|
||||
}
|
||||
|
||||
~Messenger()
|
||||
{
|
||||
vk_destroy_debug_messenger(Instance::get(), m_debug_messenger, nullptr);
|
||||
}
|
||||
|
||||
Messenger(Messenger &&) = default;
|
||||
|
||||
Messenger(const Messenger &) = delete;
|
||||
|
||||
auto operator=(Messenger &&) -> Messenger & = default;
|
||||
|
||||
auto operator=(const Messenger &) const -> Messenger & = delete;
|
||||
|
||||
|
||||
private:
|
||||
static auto validation_layers_callback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT const message_severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT const message_type,
|
||||
VkDebugUtilsMessengerCallbackDataEXT const *const callback_data,
|
||||
void *const vulkan_user_data
|
||||
) -> VkBool32
|
||||
{
|
||||
auto *messenger = (Messenger *)vulkan_user_data; // NOLINT
|
||||
ensure(messenger, "Null vulkan_user_data received in messenger callback");
|
||||
|
||||
messenger->validation_layers_callback_impl(message_severity, message_type, callback_data);
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
void validation_layers_callback_impl(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT message_type,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT *callback_data
|
||||
)
|
||||
{
|
||||
m_callback(
|
||||
static_cast<Severity>(message_severity),
|
||||
static_cast<Type>(message_type),
|
||||
callback_data,
|
||||
m_user_data
|
||||
);
|
||||
}
|
||||
|
||||
memory::NullOnMove<VkDebugUtilsMessengerEXT> m_debug_messenger = VK_NULL_HANDLE;
|
||||
|
||||
Callback_T m_callback;
|
||||
|
||||
void *m_user_data;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,66 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <renderer/vk/vulkan.hpp>
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
inline void vkc(VkResult result)
|
||||
{
|
||||
if (result)
|
||||
{
|
||||
throw std::runtime_error { std::format(
|
||||
"Vulkan call failed with result: {}({})",
|
||||
string_VkResult(result),
|
||||
std::to_underlying(result)
|
||||
) };
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline auto get_object_type(T object) -> VkObjectType
|
||||
{
|
||||
if constexpr (std::is_same_v<T, VkQueue>)
|
||||
{
|
||||
return VK_OBJECT_TYPE_QUEUE;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, VkFence>)
|
||||
{
|
||||
return VK_OBJECT_TYPE_FENCE;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, VkSemaphore>)
|
||||
{
|
||||
return VK_OBJECT_TYPE_SEMAPHORE;
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, VkSwapchainKHR>)
|
||||
{
|
||||
return VK_OBJECT_TYPE_SWAPCHAIN_KHR;
|
||||
}
|
||||
|
||||
static_assert("invalid type");
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
inline void set_object_name(
|
||||
VkDevice device,
|
||||
T object,
|
||||
std::format_string<Args...> fmt,
|
||||
Args &&...args
|
||||
)
|
||||
{
|
||||
const auto name = std::format(fmt, std::forward<Args>(args)...);
|
||||
auto info = VkDebugUtilsObjectNameInfoEXT {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.objectType = get_object_type(object),
|
||||
.objectHandle = (uint64_t)(object),
|
||||
.pObjectName = name.c_str(),
|
||||
};
|
||||
|
||||
vk_set_debug_object_name(device, &info);
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,18 +0,0 @@
|
|||
#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
|
|
@ -1,36 +0,0 @@
|
|||
#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
|
|
@ -1,59 +0,0 @@
|
|||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/pipeline.hpp>
|
||||
#include <renderer/vk/test_utils.hpp>
|
||||
#include <surface/system.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
||||
using namespace lt;
|
||||
|
||||
using renderer::vk::Context;
|
||||
using renderer::vk::Pipeline;
|
||||
|
||||
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 = constants::resolution,
|
||||
});
|
||||
|
||||
m_context = create_ref<Context>(*m_surface_entity);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto context() -> Ref<Context>
|
||||
{
|
||||
return m_context;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto device() -> VkDevice
|
||||
{
|
||||
return m_context->device().vk();
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<ecs::Registry> m_registry;
|
||||
|
||||
Ref<surface::System> m_surface_system;
|
||||
|
||||
Scope<ecs::Entity> m_surface_entity;
|
||||
|
||||
Ref<Context> m_context;
|
||||
};
|
||||
|
||||
Suite raii = "raii"_suite = [] {
|
||||
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,281 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/shader.hpp>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/debug/validation.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Pass
|
||||
{
|
||||
public:
|
||||
Pass(
|
||||
Context &context,
|
||||
lt::assets::ShaderAsset vertex_shader,
|
||||
lt::assets::ShaderAsset fragment_shader
|
||||
)
|
||||
: m_device(context.device().vk())
|
||||
{
|
||||
// auto fragment_blob = vertex_shader.unpack(lt::assets::ShaderAsset::BlobTag::code);
|
||||
|
||||
auto *vertex_module = create_module(
|
||||
vertex_shader.unpack(lt::assets::ShaderAsset::BlobTag::code)
|
||||
);
|
||||
|
||||
auto *fragment_module = create_module(
|
||||
fragment_shader.unpack(lt::assets::ShaderAsset::BlobTag::code)
|
||||
);
|
||||
|
||||
auto shader_stages = std::array<VkPipelineShaderStageCreateInfo, 2> {
|
||||
VkPipelineShaderStageCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.stage = VK_SHADER_STAGE_VERTEX_BIT,
|
||||
.module = vertex_module,
|
||||
.pName = "main",
|
||||
},
|
||||
VkPipelineShaderStageCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
||||
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
|
||||
.module = fragment_module,
|
||||
.pName = "main",
|
||||
},
|
||||
};
|
||||
|
||||
auto dynamic_states = std::array<VkDynamicState, 2> {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
};
|
||||
|
||||
auto dynamic_state = VkPipelineDynamicStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
|
||||
.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size()),
|
||||
.pDynamicStates = dynamic_states.data(),
|
||||
};
|
||||
|
||||
auto vertex_input = VkPipelineVertexInputStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
|
||||
};
|
||||
|
||||
auto input_assembly = VkPipelineInputAssemblyStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
|
||||
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
|
||||
.primitiveRestartEnable = VK_FALSE,
|
||||
};
|
||||
|
||||
auto viewport_state = VkPipelineViewportStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.viewportCount = 1u,
|
||||
.scissorCount = 1u,
|
||||
};
|
||||
|
||||
auto rasterization = VkPipelineRasterizationStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
|
||||
.depthClampEnable = VK_FALSE,
|
||||
.rasterizerDiscardEnable = VK_FALSE,
|
||||
.polygonMode = VK_POLYGON_MODE_FILL,
|
||||
.cullMode = VK_CULL_MODE_NONE,
|
||||
.frontFace = VK_FRONT_FACE_CLOCKWISE,
|
||||
.lineWidth = 1.0,
|
||||
};
|
||||
|
||||
auto multisampling = VkPipelineMultisampleStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
|
||||
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.sampleShadingEnable = VK_FALSE,
|
||||
.minSampleShading = 1.0,
|
||||
.pSampleMask = nullptr,
|
||||
.alphaToCoverageEnable = VK_FALSE,
|
||||
.alphaToOneEnable = VK_FALSE,
|
||||
};
|
||||
|
||||
auto color_blend_attachment = VkPipelineColorBlendAttachmentState {
|
||||
.blendEnable = VK_FALSE,
|
||||
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
|
||||
.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
.colorBlendOp = VK_BLEND_OP_ADD,
|
||||
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
|
||||
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
.alphaBlendOp = VK_BLEND_OP_ADD,
|
||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
|
||||
| VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
||||
};
|
||||
|
||||
auto color_blend = VkPipelineColorBlendStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
|
||||
.logicOpEnable = VK_FALSE,
|
||||
.logicOp = VK_LOGIC_OP_COPY,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = &color_blend_attachment,
|
||||
.blendConstants = { 0.0f, 0.0, 0.0, 0.0 },
|
||||
};
|
||||
|
||||
auto layout_info = VkPipelineLayoutCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
|
||||
.setLayoutCount = 0u,
|
||||
.pSetLayouts = nullptr,
|
||||
.pushConstantRangeCount = 0u,
|
||||
.pPushConstantRanges = nullptr,
|
||||
};
|
||||
|
||||
vkc(vk_create_pipeline_layout(m_device, &layout_info, nullptr, &m_layout));
|
||||
|
||||
auto attachment_description = VkAttachmentDescription {
|
||||
.format = context.swapchain().get_format(),
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
|
||||
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
|
||||
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
};
|
||||
|
||||
auto color_attachment_ref = VkAttachmentReference {
|
||||
.attachment = 0,
|
||||
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
};
|
||||
|
||||
auto subpass_description = VkSubpassDescription {
|
||||
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
.colorAttachmentCount = 1u,
|
||||
.pColorAttachments = &color_attachment_ref,
|
||||
};
|
||||
|
||||
auto pass_dependency = VkSubpassDependency {
|
||||
.srcSubpass = VK_SUBPASS_EXTERNAL,
|
||||
.dstSubpass = 0u,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
.srcAccessMask = 0u,
|
||||
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
|
||||
};
|
||||
|
||||
auto renderpass_info = VkRenderPassCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
|
||||
.attachmentCount = 1u,
|
||||
.pAttachments = &attachment_description,
|
||||
.subpassCount = 1u,
|
||||
.pSubpasses = &subpass_description,
|
||||
.dependencyCount = 1u,
|
||||
.pDependencies = &pass_dependency,
|
||||
};
|
||||
|
||||
vkc(vk_create_render_pass(m_device, &renderpass_info, nullptr, &m_pass));
|
||||
|
||||
auto pipeline_info = VkGraphicsPipelineCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
||||
.stageCount = static_cast<uint32_t>(shader_stages.size()),
|
||||
.pStages = shader_stages.data(),
|
||||
.pVertexInputState = &vertex_input,
|
||||
.pInputAssemblyState = &input_assembly,
|
||||
.pViewportState = &viewport_state,
|
||||
.pRasterizationState = &rasterization,
|
||||
.pMultisampleState = &multisampling,
|
||||
.pDepthStencilState = nullptr,
|
||||
.pColorBlendState = &color_blend,
|
||||
.pDynamicState = &dynamic_state,
|
||||
.layout = m_layout,
|
||||
.renderPass = m_pass,
|
||||
.subpass = 0u,
|
||||
.basePipelineHandle = VK_NULL_HANDLE,
|
||||
.basePipelineIndex = -1,
|
||||
};
|
||||
|
||||
vkc(vk_create_graphics_pipelines(
|
||||
m_device,
|
||||
VK_NULL_HANDLE,
|
||||
1u,
|
||||
&pipeline_info,
|
||||
nullptr,
|
||||
&m_pipeline
|
||||
));
|
||||
|
||||
vk_destroy_shader_module(m_device, vertex_module, nullptr);
|
||||
vk_destroy_shader_module(m_device, fragment_module, nullptr);
|
||||
|
||||
m_framebuffers = context.swapchain().create_framebuffers_for_pass(m_pass);
|
||||
}
|
||||
|
||||
~Pass()
|
||||
{
|
||||
if (!m_device)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &framebuffer : m_framebuffers)
|
||||
{
|
||||
vk_destroy_frame_buffer(m_device, framebuffer, nullptr);
|
||||
}
|
||||
|
||||
vk_destroy_pipeline(m_device, m_pipeline, nullptr);
|
||||
vk_destroy_render_pass(m_device, m_pass, nullptr);
|
||||
vk_destroy_pipeline_layout(m_device, m_layout, nullptr);
|
||||
}
|
||||
|
||||
Pass(Pass &&) = default;
|
||||
|
||||
Pass(const Pass &) = delete;
|
||||
|
||||
auto operator=(Pass &&) -> Pass & = default;
|
||||
|
||||
auto operator=(const Pass &) -> Pass & = delete;
|
||||
|
||||
void replace_swapchain(const Swapchain &swapchain)
|
||||
{
|
||||
if (!m_device)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vk_device_wait_idle(m_device);
|
||||
for (auto &framebuffer : m_framebuffers)
|
||||
{
|
||||
vk_destroy_frame_buffer(m_device, framebuffer, nullptr);
|
||||
}
|
||||
|
||||
m_framebuffers = swapchain.create_framebuffers_for_pass(m_pass);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_pass() -> VkRenderPass
|
||||
{
|
||||
return m_pass;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_pipeline() -> VkPipeline
|
||||
{
|
||||
return m_pipeline;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_framebuffers() -> std::vector<VkFramebuffer> &
|
||||
{
|
||||
return m_framebuffers;
|
||||
}
|
||||
|
||||
private:
|
||||
auto create_module(lt::assets::Blob blob) -> VkShaderModule
|
||||
{
|
||||
auto info = VkShaderModuleCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||
.codeSize = blob.size(),
|
||||
.pCode = reinterpret_cast<const uint32_t *>(blob.data()) // NOLINT
|
||||
};
|
||||
|
||||
auto *module = VkShaderModule { VK_NULL_HANDLE };
|
||||
vkc(vk_create_shader_module(m_device, &info, nullptr, &module));
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
memory::NullOnMove<VkDevice> m_device = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkPipeline> m_pipeline = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkRenderPass> m_pass = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkPipelineLayout> m_layout = VK_NULL_HANDLE;
|
||||
|
||||
std::vector<VkFramebuffer> m_framebuffers;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
|
@ -1,355 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <ranges>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/debug/validation.hpp>
|
||||
#include <renderer/vk/renderer/pass.hpp>
|
||||
#include <time/timer.hpp>
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
static constexpr auto max_frames_in_flight = uint32_t { 3u };
|
||||
|
||||
Renderer(Context &context, Ref<Pass> pass)
|
||||
: m_device(context.device().vk())
|
||||
, m_graphics_queue(context.device().get_graphics_queue())
|
||||
, m_present_queue(context.device().get_present_queue())
|
||||
, m_swapchain(context.swapchain().vk())
|
||||
, m_pass(std::move(pass))
|
||||
, m_resolution(context.swapchain().get_resolution())
|
||||
{
|
||||
ensure(m_device, "Failed to initialize renderer: null device");
|
||||
ensure(m_graphics_queue, "Failed to initialize renderer: null graphics queue");
|
||||
ensure(m_present_queue, "Failed to initialize renderer: null present queue");
|
||||
ensure(m_swapchain, "Failed to initialize renderer: null swapchain");
|
||||
|
||||
auto pool_info = VkCommandPoolCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||
.queueFamilyIndex = context.device().get_family_indices()[0],
|
||||
|
||||
};
|
||||
vkc(vk_create_command_pool(m_device, &pool_info, nullptr, &m_pool));
|
||||
|
||||
auto cmd_info = VkCommandBufferAllocateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = m_pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = static_cast<uint32_t>(m_cmds.size()),
|
||||
};
|
||||
vkc(vk_allocate_command_buffers(m_device, &cmd_info, &m_cmds[0]));
|
||||
|
||||
auto semaphore_info = VkSemaphoreCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
};
|
||||
|
||||
auto fence_info = VkFenceCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
};
|
||||
|
||||
for (auto idx : std::views::iota(0u, max_frames_in_flight))
|
||||
{
|
||||
vkc(vk_create_semaphore(
|
||||
m_device,
|
||||
&semaphore_info,
|
||||
nullptr,
|
||||
&m_aquire_image_semaphores[idx]
|
||||
));
|
||||
|
||||
vkc(vk_create_fence(m_device, &fence_info, nullptr, &m_in_flight_fences[idx]));
|
||||
|
||||
set_object_name(
|
||||
m_device,
|
||||
m_aquire_image_semaphores[idx].get(),
|
||||
"aquire semaphore {}",
|
||||
idx
|
||||
);
|
||||
|
||||
set_object_name(m_device, m_in_flight_fences[idx].get(), "frame fence {}", idx);
|
||||
|
||||
{
|
||||
const auto name = std::format("frame fence {}", idx);
|
||||
auto debug_info = VkDebugUtilsObjectNameInfoEXT {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.objectType = VK_OBJECT_TYPE_FENCE,
|
||||
.objectHandle = reinterpret_cast<uint64_t>(
|
||||
static_cast<VkFence_T *>(m_in_flight_fences[idx].get())
|
||||
),
|
||||
.pObjectName = name.c_str(),
|
||||
};
|
||||
vk_set_debug_object_name(m_device, &debug_info);
|
||||
}
|
||||
}
|
||||
|
||||
m_submit_semaphores.resize(context.swapchain().get_image_count());
|
||||
for (auto idx = 0; auto &semaphore : m_submit_semaphores)
|
||||
{
|
||||
vkc(vk_create_semaphore(m_device, &semaphore_info, nullptr, &semaphore));
|
||||
set_object_name(m_device, semaphore.get(), "submit semaphore {}", idx++);
|
||||
}
|
||||
};
|
||||
|
||||
~Renderer()
|
||||
{
|
||||
if (!m_device)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vkc(vk_device_wait_idle(m_device));
|
||||
|
||||
for (auto [semaphore, fence] :
|
||||
std::views::zip(m_aquire_image_semaphores, m_in_flight_fences))
|
||||
{
|
||||
vk_destroy_semaphore(m_device, semaphore, nullptr);
|
||||
vk_destroy_fence(m_device, fence, nullptr);
|
||||
}
|
||||
|
||||
|
||||
for (auto &semaphore : m_submit_semaphores)
|
||||
{
|
||||
vk_destroy_semaphore(m_device, semaphore, nullptr);
|
||||
}
|
||||
|
||||
vk_destroy_command_pool(m_device, m_pool, nullptr);
|
||||
}
|
||||
|
||||
Renderer(Renderer &&) = default;
|
||||
|
||||
Renderer(const Renderer &) = delete;
|
||||
|
||||
auto operator=(Renderer &&) -> Renderer & = default;
|
||||
|
||||
auto operator=(const Renderer &) -> Renderer & = delete;
|
||||
|
||||
auto draw(uint32_t frame_idx) -> bool
|
||||
{
|
||||
ensure(
|
||||
frame_idx < max_frames_in_flight,
|
||||
"Failed to draw: frame_idx >= max_frames_in_flight"
|
||||
);
|
||||
|
||||
auto &flight_fence = m_in_flight_fences[frame_idx];
|
||||
auto &aquire_semaphore = m_aquire_image_semaphores[frame_idx];
|
||||
auto &cmd = m_cmds[frame_idx];
|
||||
|
||||
try
|
||||
{
|
||||
vkc(vk_wait_for_fences(
|
||||
m_device,
|
||||
1u,
|
||||
&flight_fence,
|
||||
VK_TRUE,
|
||||
std::numeric_limits<uint64_t>::max()
|
||||
));
|
||||
|
||||
auto image_idx = uint32_t {};
|
||||
auto result = vk_acquire_next_image_khr(
|
||||
m_device,
|
||||
m_swapchain,
|
||||
1000000ul,
|
||||
aquire_semaphore,
|
||||
VK_NULL_HANDLE,
|
||||
&image_idx
|
||||
);
|
||||
if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
vkc(vk_reset_fences(m_device, 1u, &flight_fence));
|
||||
vkc(vk_reset_command_buffer(cmd, {}));
|
||||
record_cmd(cmd, image_idx);
|
||||
|
||||
auto wait_stage = VkPipelineStageFlags {
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
|
||||
};
|
||||
auto &submit_semaphore = m_submit_semaphores[image_idx];
|
||||
auto submit_info = VkSubmitInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.waitSemaphoreCount = 1u,
|
||||
.pWaitSemaphores = &aquire_semaphore,
|
||||
.pWaitDstStageMask = &wait_stage,
|
||||
.commandBufferCount = 1u,
|
||||
.pCommandBuffers = &cmd,
|
||||
.signalSemaphoreCount = 1u,
|
||||
.pSignalSemaphores = &submit_semaphore,
|
||||
};
|
||||
|
||||
vkc(vk_queue_submit(m_graphics_queue, 1u, &submit_info, flight_fence));
|
||||
|
||||
VkResult res;
|
||||
auto present_info = VkPresentInfoKHR {
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
.waitSemaphoreCount = 1u,
|
||||
.pWaitSemaphores = &submit_semaphore,
|
||||
.swapchainCount = 1u,
|
||||
.pSwapchains = &m_swapchain,
|
||||
.pImageIndices = &image_idx,
|
||||
.pResults = &res,
|
||||
};
|
||||
|
||||
vk_queue_present_khr(m_present_queue, &present_info);
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_dbg("EXCEPTION: {}", exp.what());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void replace_swapchain(const Swapchain &swapchain)
|
||||
{
|
||||
vk_device_wait_idle(m_device);
|
||||
|
||||
m_swapchain = swapchain.vk();
|
||||
m_resolution = swapchain.get_resolution();
|
||||
ensure(m_swapchain, "Failed to replace renderer's swapchain: null swapchain");
|
||||
|
||||
for (auto [semaphore, fence] :
|
||||
std::views::zip(m_aquire_image_semaphores, m_in_flight_fences))
|
||||
{
|
||||
vk_destroy_semaphore(m_device, semaphore, nullptr);
|
||||
vk_destroy_fence(m_device, fence, nullptr);
|
||||
}
|
||||
|
||||
for (auto &semaphore : m_submit_semaphores)
|
||||
{
|
||||
vk_destroy_semaphore(m_device, semaphore, nullptr);
|
||||
}
|
||||
|
||||
auto semaphore_info = VkSemaphoreCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
};
|
||||
|
||||
auto fence_info = VkFenceCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
};
|
||||
|
||||
for (auto idx : std::views::iota(0u, max_frames_in_flight))
|
||||
{
|
||||
vkc(vk_create_semaphore(
|
||||
m_device,
|
||||
&semaphore_info,
|
||||
nullptr,
|
||||
&m_aquire_image_semaphores[idx]
|
||||
));
|
||||
|
||||
vkc(vk_create_fence(m_device, &fence_info, nullptr, &m_in_flight_fences[idx]));
|
||||
|
||||
set_object_name(
|
||||
m_device,
|
||||
m_aquire_image_semaphores[idx].get(),
|
||||
"aquire semaphore {}",
|
||||
idx
|
||||
);
|
||||
|
||||
set_object_name(m_device, m_in_flight_fences[idx].get(), "frame fence {}", idx);
|
||||
|
||||
{
|
||||
const auto name = std::format("frame fence {}", idx);
|
||||
auto debug_info = VkDebugUtilsObjectNameInfoEXT {
|
||||
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||||
.objectType = VK_OBJECT_TYPE_FENCE,
|
||||
.objectHandle = reinterpret_cast<uint64_t>(
|
||||
static_cast<VkFence_T *>(m_in_flight_fences[idx].get())
|
||||
),
|
||||
.pObjectName = name.c_str(),
|
||||
};
|
||||
vk_set_debug_object_name(m_device, &debug_info);
|
||||
}
|
||||
}
|
||||
|
||||
m_submit_semaphores.resize(swapchain.get_image_count());
|
||||
for (auto idx = 0; auto &semaphore : m_submit_semaphores)
|
||||
{
|
||||
vkc(vk_create_semaphore(m_device, &semaphore_info, nullptr, &semaphore));
|
||||
set_object_name(m_device, semaphore.get(), "submit semaphore {}", idx++);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void record_cmd(VkCommandBuffer cmd, uint32_t image_idx)
|
||||
{
|
||||
auto cmd_begin_info = VkCommandBufferBeginInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = {},
|
||||
.pInheritanceInfo = nullptr,
|
||||
};
|
||||
|
||||
vkc(vk_begin_command_buffer(cmd, &cmd_begin_info));
|
||||
|
||||
static auto timer = Timer {};
|
||||
|
||||
auto clear_value = VkClearValue {
|
||||
.color = {
|
||||
0.93,
|
||||
0.93,
|
||||
0.93,
|
||||
1.0,
|
||||
},
|
||||
};
|
||||
|
||||
auto pass_begin_info = VkRenderPassBeginInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.renderPass = m_pass->get_pass(),
|
||||
.framebuffer = m_pass->get_framebuffers()[image_idx],
|
||||
.renderArea = { .offset = {}, .extent = m_resolution },
|
||||
.clearValueCount = 1u,
|
||||
.pClearValues = &clear_value
|
||||
};
|
||||
vk_cmd_begin_render_pass(cmd, &pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
vk_cmd_bind_pipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pass->get_pipeline());
|
||||
|
||||
auto viewport = VkViewport {
|
||||
.x = 0.0f,
|
||||
.y = 0.0f,
|
||||
.width = static_cast<float>(m_resolution.width),
|
||||
.height = static_cast<float>(m_resolution.height),
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f,
|
||||
};
|
||||
vk_cmd_set_viewport(cmd, 0, 1, &viewport);
|
||||
|
||||
auto scissor = VkRect2D {
|
||||
.offset = { 0u, 0u },
|
||||
.extent = m_resolution,
|
||||
};
|
||||
vk_cmd_set_scissors(cmd, 0, 1, &scissor);
|
||||
|
||||
vk_cmd_draw(cmd, 3, 1, 0, 0);
|
||||
vk_cmd_end_render_pass(cmd);
|
||||
vkc(vk_end_command_buffer(cmd));
|
||||
}
|
||||
|
||||
|
||||
memory::NullOnMove<VkDevice> m_device = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkCommandPool> m_pool = VK_NULL_HANDLE;
|
||||
|
||||
std::array<memory::NullOnMove<VkCommandBuffer>, max_frames_in_flight> m_cmds {};
|
||||
|
||||
std::array<memory::NullOnMove<VkSemaphore>, max_frames_in_flight> m_aquire_image_semaphores {};
|
||||
|
||||
std::vector<memory::NullOnMove<VkSemaphore>> m_submit_semaphores;
|
||||
|
||||
std::array<memory::NullOnMove<VkFence>, max_frames_in_flight> m_in_flight_fences {};
|
||||
|
||||
memory::NullOnMove<VkSwapchainKHR> m_swapchain = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkQueue> m_graphics_queue = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkQueue> m_present_queue = VK_NULL_HANDLE;
|
||||
|
||||
Ref<Pass> m_pass;
|
||||
|
||||
VkExtent2D m_resolution;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
12
modules/renderer/public/api.hpp
Normal file
12
modules/renderer/public/api.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
enum class API : uint8_t
|
||||
{
|
||||
Vulkan,
|
||||
DirectX,
|
||||
Metal,
|
||||
};
|
||||
|
||||
}
|
52
modules/renderer/public/components/messenger.hpp
Normal file
52
modules/renderer/public/components/messenger.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <any>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
enum class MessageSeverity : uint8_t
|
||||
{
|
||||
none = 0u,
|
||||
|
||||
verbose = bit(0u),
|
||||
info = bit(1u),
|
||||
warning = bit(2u),
|
||||
error = bit(3u),
|
||||
|
||||
all = verbose | info | warning | error,
|
||||
};
|
||||
|
||||
enum class MessageType : uint8_t
|
||||
{
|
||||
none = 0u,
|
||||
general = bit(0u),
|
||||
validation = bit(1u),
|
||||
performance = bit(2u),
|
||||
|
||||
all = general | validation | performance,
|
||||
};
|
||||
|
||||
struct MessengerCallbackData
|
||||
{
|
||||
std::string message;
|
||||
};
|
||||
|
||||
using Callback_T = std::function<void(
|
||||
MessageSeverity message_severity,
|
||||
MessageType message_type,
|
||||
MessengerCallbackData data,
|
||||
std::any user_data
|
||||
)>;
|
||||
|
||||
struct MessengerComponent
|
||||
{
|
||||
MessageSeverity severities;
|
||||
|
||||
MessageType types;
|
||||
|
||||
Callback_T callback;
|
||||
|
||||
std::any user_data;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
|
@ -2,8 +2,4 @@
|
|||
|
||||
namespace lt::renderer {
|
||||
|
||||
struct SolidColor
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
||||
|
|
|
@ -2,26 +2,28 @@
|
|||
|
||||
#include <app/system.hpp>
|
||||
#include <ecs/entity.hpp>
|
||||
#include <ecs/registry.hpp>
|
||||
#include <renderer/api.hpp>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
namespace vk {
|
||||
class Context;
|
||||
class Renderer;
|
||||
class Pass;
|
||||
class ValidationObserver;
|
||||
} // namespace vk
|
||||
|
||||
class System: public app::ISystem
|
||||
{
|
||||
public:
|
||||
struct Configuration
|
||||
{
|
||||
API target_api;
|
||||
|
||||
uint32_t max_frames_in_flight;
|
||||
};
|
||||
|
||||
struct CreateInfo
|
||||
{
|
||||
Configuration config;
|
||||
|
||||
Ref<ecs::Registry> registry;
|
||||
|
||||
ecs::Entity surface_entity;
|
||||
|
||||
Ref<app::SystemStats> system_stats;
|
||||
};
|
||||
|
||||
System(CreateInfo info);
|
||||
|
@ -40,38 +42,31 @@ public:
|
|||
|
||||
void on_unregister() override;
|
||||
|
||||
|
||||
void tick(app::TickInfo tick) override;
|
||||
|
||||
|
||||
[[nodiscard]] auto get_stats() const -> const app::SystemStats &
|
||||
{
|
||||
return *m_stats;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_last_tick_result() const -> const app::TickResult & override
|
||||
{
|
||||
return m_last_tick_result;
|
||||
}
|
||||
|
||||
private:
|
||||
class vk::ValidationObserver *m_validation_observer;
|
||||
API m_api;
|
||||
|
||||
Ref<ecs::Registry> m_registry;
|
||||
|
||||
ecs::Entity m_surface_entity;
|
||||
|
||||
Ref<app::SystemStats> m_stats;
|
||||
Scope<class IContext> m_context;
|
||||
|
||||
Scope<class vk::Context> m_context;
|
||||
Scope<class IRenderer> m_renderer;
|
||||
|
||||
Ref<class vk::Pass> m_pass;
|
||||
|
||||
Scope<class vk::Renderer> m_renderer;
|
||||
std::vector<Scope<class IMessenger>> m_messengers;
|
||||
|
||||
app::TickResult m_last_tick_result {};
|
||||
|
||||
uint32_t m_frame_idx {};
|
||||
|
||||
uint32_t m_max_frames_in_flight {};
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
enum class Severity : uint8_t
|
||||
{
|
||||
off,
|
||||
|
||||
very_verbose,
|
||||
verbose,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
critical,
|
||||
};
|
||||
|
||||
class Validation
|
||||
{
|
||||
public:
|
||||
void push_diagnosis();
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
Loading…
Add table
Reference in a new issue