feat(renderer): swapchain recreation, bug fixes & other stuff
This commit is contained in:
parent
1ce8aed8a2
commit
ef2f728cd6
16 changed files with 499 additions and 160 deletions
|
@ -1,7 +1,97 @@
|
|||
#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
|
||||
|
||||
|
||||
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_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 = create_scope<vk::Renderer>(*m_context, m_pass);
|
||||
}
|
||||
|
||||
System::~System()
|
||||
{
|
||||
}
|
||||
|
||||
void System::on_register()
|
||||
{
|
||||
}
|
||||
|
@ -12,6 +102,15 @@ void System::on_unregister()
|
|||
|
||||
void System::tick(app::TickInfo tick)
|
||||
{
|
||||
if (!m_renderer->draw(m_frame_idx))
|
||||
{
|
||||
m_context->recreate_swapchain();
|
||||
m_renderer->replace_swapchain(m_context->swapchain());
|
||||
m_pass->replace_swapchain(m_context->swapchain());
|
||||
m_renderer->draw(m_frame_idx);
|
||||
}
|
||||
|
||||
m_frame_idx = (m_frame_idx + 1) % vk::Renderer::max_frames_in_flight;
|
||||
}
|
||||
|
||||
} // namespace lt::renderer
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#include <ranges>
|
||||
#include <renderer/system.hpp>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/renderer/renderer.hpp>
|
||||
#include <surface/components.hpp>
|
||||
#include <surface/system.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@ Context::Context(const ecs::Entity &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
|
||||
|
|
|
@ -35,6 +35,12 @@ public:
|
|||
return m_swapchain;
|
||||
}
|
||||
|
||||
void recreate_swapchain()
|
||||
{
|
||||
m_swapchain.destroy();
|
||||
m_swapchain = Swapchain { m_device, m_surface };
|
||||
}
|
||||
|
||||
private:
|
||||
Surface m_surface;
|
||||
|
||||
|
|
|
@ -16,6 +16,16 @@ Device::Device(const Surface &surface)
|
|||
|
||||
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()
|
||||
|
|
|
@ -147,7 +147,7 @@ void Instance::initialize_instance()
|
|||
const auto setting_thread_safety = VkBool32 { VK_TRUE };
|
||||
const auto *setting_debug_action = "";
|
||||
const auto setting_enable_message_limit = VkBool32 { VK_TRUE };
|
||||
const auto setting_duplicate_message_limit = uint32_t { 3u };
|
||||
const auto setting_duplicate_message_limit = uint32_t { UINT32_MAX };
|
||||
auto setting_report_flags = std::array<const char *, 5> {
|
||||
"info", "warn", "perf", "error", "verbose",
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
Surface::Surface(const ecs::Entity &surface_entity)
|
||||
Surface::Surface(ecs::Entity surface_entity): m_surface_entity(surface_entity)
|
||||
{
|
||||
const auto &component = surface_entity.get<surface::SurfaceComponent>();
|
||||
|
||||
|
@ -18,12 +18,6 @@ Surface::Surface(const ecs::Entity &surface_entity)
|
|||
|
||||
auto *instance = Instance::get();
|
||||
auto result = vk_create_xlib_surface_khr(instance, &create_info, nullptr, &m_surface);
|
||||
|
||||
const auto &[width, height] = component.get_resolution();
|
||||
m_framebuffer_size = {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
}
|
||||
|
||||
Surface::~Surface()
|
||||
|
@ -34,4 +28,15 @@ Surface::~Surface()
|
|||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Surface::get_framebuffer_size() const -> VkExtent2D
|
||||
{
|
||||
const auto &[width, height] = //
|
||||
m_surface_entity.get<surface::SurfaceComponent>().get_resolution();
|
||||
|
||||
return {
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vk
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace lt::renderer::vk {
|
|||
class Surface
|
||||
{
|
||||
public:
|
||||
Surface(const ecs::Entity &surface_entity);
|
||||
Surface(ecs::Entity surface_entity);
|
||||
|
||||
~Surface();
|
||||
|
||||
|
@ -26,15 +26,12 @@ public:
|
|||
return m_surface;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_framebuffer_size() const -> VkExtent2D
|
||||
{
|
||||
return m_framebuffer_size;
|
||||
}
|
||||
[[nodiscard]] auto get_framebuffer_size() const -> VkExtent2D;
|
||||
|
||||
private:
|
||||
memory::NullOnMove<VkSurfaceKHR> m_surface = VK_NULL_HANDLE;
|
||||
|
||||
VkExtent2D m_framebuffer_size {};
|
||||
ecs::Entity m_surface_entity;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
||||
|
|
|
@ -11,6 +11,7 @@ Swapchain::Swapchain(const Device &device, const Surface &surface)
|
|||
: m_device(device.vk())
|
||||
, m_resolution(surface.get_framebuffer_size())
|
||||
{
|
||||
static auto idx = 0u;
|
||||
auto *physical_device = device.physical();
|
||||
|
||||
auto capabilities = VkSurfaceCapabilitiesKHR {};
|
||||
|
@ -55,15 +56,16 @@ Swapchain::Swapchain(const Device &device, const Surface &surface)
|
|||
|
||||
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_swapchain_images.resize(image_count);
|
||||
m_swapchain_image_views.resize(image_count);
|
||||
vk_get_swapchain_images_khr(device.vk(), m_swapchain, &image_count, m_swapchain_images.data());
|
||||
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_swapchain_images, m_swapchain_image_views))
|
||||
for (auto [image, view] : std::views::zip(m_images, m_image_views))
|
||||
{
|
||||
auto create_info = VkImageViewCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
|
@ -87,29 +89,11 @@ Swapchain::Swapchain(const Device &device, const Surface &surface)
|
|||
|
||||
vkc(vk_create_image_view(device.vk(), &create_info, nullptr, &view));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Swapchain::~Swapchain()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_device)
|
||||
{
|
||||
vkc(vk_device_wait_idle(m_device));
|
||||
for (auto &view : m_swapchain_image_views)
|
||||
{
|
||||
vk_destroy_image_view(m_device, view, nullptr);
|
||||
}
|
||||
|
||||
vk_destroy_swapchain_khr(m_device, m_swapchain, nullptr);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_err("Failed to destroy swapchain:");
|
||||
log_err("\twhat: {}", exp.what());
|
||||
}
|
||||
destroy();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Swapchain::get_optimal_image_count(
|
||||
|
|
|
@ -21,6 +21,28 @@ public:
|
|||
|
||||
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;
|
||||
|
@ -36,10 +58,15 @@ public:
|
|||
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_swapchain_image_views.size());
|
||||
auto framebuffers = std::vector<VkFramebuffer>(m_image_views.size());
|
||||
|
||||
for (auto idx = 0u; auto &framebuffer : framebuffers)
|
||||
{
|
||||
|
@ -47,7 +74,7 @@ public:
|
|||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
.renderPass = pass,
|
||||
.attachmentCount = 1u,
|
||||
.pAttachments = &m_swapchain_image_views[idx++],
|
||||
.pAttachments = &m_image_views[idx++],
|
||||
.width = m_resolution.width,
|
||||
.height = m_resolution.height,
|
||||
.layers = 1u
|
||||
|
@ -69,9 +96,9 @@ private:
|
|||
|
||||
memory::NullOnMove<VkSwapchainKHR> m_swapchain = VK_NULL_HANDLE;
|
||||
|
||||
std::vector<VkImage> m_swapchain_images;
|
||||
std::vector<VkImage> m_images;
|
||||
|
||||
std::vector<VkImageView> m_swapchain_image_views;
|
||||
std::vector<VkImageView> m_image_views;
|
||||
|
||||
VkExtent2D m_resolution;
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <renderer/vk/vulkan.hpp>
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
|
@ -8,10 +10,57 @@ inline void vkc(VkResult result)
|
|||
{
|
||||
if (result)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format("Vulkan call failed with result: {}", std::to_underlying(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
|
||||
|
|
|
@ -62,20 +62,6 @@ public:
|
|||
.primitiveRestartEnable = VK_FALSE,
|
||||
};
|
||||
|
||||
auto viewport = VkViewport {
|
||||
.x = 0u,
|
||||
.y = 0u,
|
||||
.width = static_cast<float>(context.swapchain().get_resolution().width),
|
||||
.height = static_cast<float>(context.swapchain().get_resolution().height),
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 0.0f,
|
||||
};
|
||||
|
||||
auto scissor = VkRect2D {
|
||||
.offset = { 0u, 0u },
|
||||
.extent = context.swapchain().get_resolution(),
|
||||
};
|
||||
|
||||
auto viewport_state = VkPipelineViewportStateCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
||||
.viewportCount = 1u,
|
||||
|
@ -103,8 +89,6 @@ public:
|
|||
};
|
||||
|
||||
auto color_blend_attachment = VkPipelineColorBlendAttachmentState {
|
||||
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
|
||||
| VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
|
||||
.blendEnable = VK_FALSE,
|
||||
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
|
||||
.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO,
|
||||
|
@ -112,6 +96,8 @@ public:
|
|||
.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 {
|
||||
|
@ -235,6 +221,22 @@ public:
|
|||
|
||||
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;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <ranges>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
#include <renderer/vk/debug/validation.hpp>
|
||||
#include <renderer/vk/renderer/pass.hpp>
|
||||
|
@ -10,6 +11,8 @@ 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())
|
||||
|
@ -18,6 +21,11 @@ public:
|
|||
, 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,
|
||||
|
@ -30,9 +38,9 @@ public:
|
|||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = m_pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1u,
|
||||
.commandBufferCount = static_cast<uint32_t>(m_cmds.size()),
|
||||
};
|
||||
vkc(vk_allocate_command_buffers(m_device, &cmd_info, &m_cmd));
|
||||
vkc(vk_allocate_command_buffers(m_device, &cmd_info, &m_cmds[0]));
|
||||
|
||||
auto semaphore_info = VkSemaphoreCreateInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
|
@ -43,11 +51,168 @@ public:
|
|||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||
};
|
||||
|
||||
vkc(vk_create_semaphore(m_device, &semaphore_info, nullptr, &m_image_available_semaphore));
|
||||
vkc(vk_create_semaphore(m_device, &semaphore_info, nullptr, &m_render_finished_semaphore));
|
||||
vkc(vk_create_fence(m_device, &fence_info, nullptr, &m_in_flight_fence));
|
||||
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,
|
||||
UINT64_MAX,
|
||||
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));
|
||||
|
||||
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 = nullptr,
|
||||
};
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
private:
|
||||
void record_cmd(VkCommandBuffer cmd, uint32_t image_idx)
|
||||
{
|
||||
auto cmd_begin_info = VkCommandBufferBeginInfo {
|
||||
|
@ -101,93 +266,18 @@ public:
|
|||
vkc(vk_end_command_buffer(cmd));
|
||||
}
|
||||
|
||||
void draw()
|
||||
{
|
||||
try
|
||||
{
|
||||
vkc(vk_wait_for_fences(m_device, 1u, &m_in_flight_fence, VK_TRUE, UINT64_MAX));
|
||||
vkc(vk_reset_fences(m_device, 1u, &m_in_flight_fence));
|
||||
|
||||
auto image_idx = uint32_t {};
|
||||
vkc(vk_acquire_next_image_khr(
|
||||
m_device,
|
||||
m_swapchain,
|
||||
UINT64_MAX,
|
||||
m_image_available_semaphore,
|
||||
VK_NULL_HANDLE,
|
||||
&image_idx
|
||||
));
|
||||
|
||||
vkc(vk_reset_command_buffer(m_cmd, {}));
|
||||
record_cmd(m_cmd, image_idx);
|
||||
|
||||
auto wait_stage = VkPipelineStageFlags {
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
|
||||
};
|
||||
auto submit_info = VkSubmitInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.waitSemaphoreCount = 1u,
|
||||
.pWaitSemaphores = &m_image_available_semaphore,
|
||||
.pWaitDstStageMask = &wait_stage,
|
||||
.commandBufferCount = 1u,
|
||||
.pCommandBuffers = &m_cmd,
|
||||
.signalSemaphoreCount = 1u,
|
||||
.pSignalSemaphores = &m_render_finished_semaphore,
|
||||
};
|
||||
|
||||
vkc(vk_queue_submit(m_graphics_queue, 1u, &submit_info, m_in_flight_fence));
|
||||
|
||||
auto present_info = VkPresentInfoKHR {
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
.waitSemaphoreCount = 1u,
|
||||
.pWaitSemaphores = &m_render_finished_semaphore,
|
||||
.swapchainCount = 1u,
|
||||
.pSwapchains = &m_swapchain,
|
||||
.pImageIndices = &image_idx,
|
||||
.pResults = nullptr,
|
||||
};
|
||||
|
||||
vk_queue_present_khr(m_present_queue, &present_info);
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log_dbg("EXCEPTION: {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
~Renderer()
|
||||
{
|
||||
if (!m_device)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vk_destroy_semaphore(m_device, m_render_finished_semaphore, nullptr);
|
||||
vk_destroy_semaphore(m_device, m_image_available_semaphore, nullptr);
|
||||
vk_destroy_fence(m_device, m_in_flight_fence, 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;
|
||||
|
||||
private:
|
||||
memory::NullOnMove<VkDevice> m_device = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkCommandPool> m_pool = VK_NULL_HANDLE;
|
||||
|
||||
memory::NullOnMove<VkCommandBuffer> m_cmd = VK_NULL_HANDLE;
|
||||
std::array<memory::NullOnMove<VkCommandBuffer>, max_frames_in_flight> m_cmds {};
|
||||
|
||||
memory::NullOnMove<VkSemaphore> m_image_available_semaphore = VK_NULL_HANDLE;
|
||||
std::array<memory::NullOnMove<VkSemaphore>, max_frames_in_flight> m_aquire_image_semaphores {};
|
||||
|
||||
memory::NullOnMove<VkSemaphore> m_render_finished_semaphore = VK_NULL_HANDLE;
|
||||
std::vector<memory::NullOnMove<VkSemaphore>> m_submit_semaphores;
|
||||
|
||||
memory::NullOnMove<VkFence> m_in_flight_fence = VK_NULL_HANDLE;
|
||||
std::array<memory::NullOnMove<VkFence>, max_frames_in_flight> m_in_flight_fences {};
|
||||
|
||||
memory::NullOnMove<VkSwapchainKHR> m_swapchain = VK_NULL_HANDLE;
|
||||
|
||||
|
|
|
@ -10,18 +10,62 @@ Suite raii = "renderer_raii"_suite = [] {
|
|||
auto observer = ValidationObserver {};
|
||||
auto [context, _] = create_context();
|
||||
|
||||
std::ignore = Renderer(
|
||||
context,
|
||||
lt::create_ref<Pass>(
|
||||
context,
|
||||
ShaderAsset { "./data/test_assets/triangle.vert.asset" },
|
||||
ShaderAsset { "./data/test_assets/triangle.frag.asset" }
|
||||
)
|
||||
);
|
||||
|
||||
expect_false(observer.had_any_messages());
|
||||
};
|
||||
};
|
||||
|
||||
Suite draw = "renderer_draw"_suite = [] {
|
||||
Case { "renderer draw" } = [] {
|
||||
auto observer = ValidationObserver {};
|
||||
auto [context, _] = create_context();
|
||||
|
||||
auto renderer = Renderer(
|
||||
context,
|
||||
lt::create_ref<Pass>(
|
||||
context,
|
||||
ShaderAsset { "./data/test_assets/triangle.vert.asset" },
|
||||
ShaderAsset { "./data/test_assets/triangle.frag.asset" }
|
||||
)
|
||||
);
|
||||
|
||||
for (auto frame_idx : std::views::iota(0u, 30u))
|
||||
{
|
||||
expect_true(renderer.draw(frame_idx % Renderer::max_frames_in_flight));
|
||||
}
|
||||
expect_false(observer.had_any_messages());
|
||||
};
|
||||
|
||||
Case { "post swapchain replacement renderer draw" } = [] {
|
||||
auto observer = ValidationObserver {};
|
||||
auto [context, _] = create_context();
|
||||
auto pass = lt::create_ref<Pass>(
|
||||
context,
|
||||
ShaderAsset { "./data/test_assets/triangle.vert.asset" },
|
||||
ShaderAsset { "./data/test_assets/triangle.frag.asset" }
|
||||
);
|
||||
|
||||
auto renderer = Renderer { context, pass };
|
||||
|
||||
auto renderer = Renderer(context, pass);
|
||||
|
||||
for (;;)
|
||||
for (auto frame_idx : std::views::iota(0u, 15u))
|
||||
{
|
||||
renderer.draw();
|
||||
expect_true(renderer.draw(frame_idx % Renderer::max_frames_in_flight));
|
||||
}
|
||||
|
||||
context.recreate_swapchain();
|
||||
renderer.replace_swapchain(context.swapchain());
|
||||
pass->replace_swapchain(context.swapchain());
|
||||
for (auto frame_idx : std::views::iota(0u, 15u))
|
||||
{
|
||||
expect_true(renderer.draw(frame_idx % Renderer::max_frames_in_flight));
|
||||
}
|
||||
|
||||
expect_false(observer.had_any_messages());
|
||||
|
|
|
@ -57,6 +57,16 @@ private:
|
|||
{
|
||||
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);
|
||||
|
||||
|
|
|
@ -2,11 +2,17 @@
|
|||
|
||||
#include <app/system.hpp>
|
||||
#include <ecs/entity.hpp>
|
||||
#include <renderer/vk/context/context.hpp>
|
||||
|
||||
namespace lt::renderer {
|
||||
|
||||
class System: app::ISystem
|
||||
namespace vk {
|
||||
class Context;
|
||||
class Renderer;
|
||||
class Pass;
|
||||
class ValidationObserver;
|
||||
} // namespace vk
|
||||
|
||||
class System: public app::ISystem
|
||||
{
|
||||
public:
|
||||
struct CreateInfo
|
||||
|
@ -18,16 +24,9 @@ public:
|
|||
Ref<app::SystemStats> system_stats;
|
||||
};
|
||||
|
||||
[[nodiscard]] System(CreateInfo info)
|
||||
: m_registry(std::move(info.registry))
|
||||
, m_stats(info.system_stats)
|
||||
, m_context(info.surface_entity)
|
||||
{
|
||||
ensure(m_stats, "Failed to initialize system: null stats");
|
||||
ensure(m_registry, "Failed to initialize renderer system: null registry");
|
||||
}
|
||||
System(CreateInfo info);
|
||||
|
||||
~System() override = default;
|
||||
~System() override;
|
||||
|
||||
System(System &&) = default;
|
||||
|
||||
|
@ -41,8 +40,10 @@ public:
|
|||
|
||||
void on_unregister() override;
|
||||
|
||||
|
||||
void tick(app::TickInfo tick) override;
|
||||
|
||||
|
||||
[[nodiscard]] auto get_stats() const -> const app::SystemStats &
|
||||
{
|
||||
return *m_stats;
|
||||
|
@ -54,13 +55,22 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
class vk::ValidationObserver *m_validation_observer;
|
||||
|
||||
Ref<ecs::Registry> m_registry;
|
||||
|
||||
Ref<app::SystemStats> m_stats;
|
||||
|
||||
vk::Context m_context;
|
||||
Scope<class vk::Context> m_context;
|
||||
|
||||
Ref<class vk::Pass> m_pass;
|
||||
|
||||
Scope<class vk::Renderer> m_renderer;
|
||||
|
||||
|
||||
app::TickResult m_last_tick_result {};
|
||||
|
||||
uint32_t m_frame_idx {};
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
||||
|
|
Loading…
Add table
Reference in a new issue