light/modules/renderer/vk/renderer.cppm
light7734 1df42cf30d
Some checks reported errors
continuous-integration/drone/push Build was killed
wip: convert from include style to module import style :D
2025-11-09 17:16:57 +03:30

466 lines
13 KiB
C++

#pragma once
#include <memory/reference.hpp>
#include <ranges>
#include <renderer/backend/vk/context/device.hpp>
#include <renderer/backend/vk/data/buffer.hpp>
#include <renderer/backend/vk/renderer/pass.hpp>
#include <renderer/backend/vk/utils.hpp>
#include <renderer/frontend/data/buffer.hpp>
#include <renderer/frontend/renderer/pass.hpp>
#include <renderer/frontend/renderer/renderer.hpp>
namespace lt::renderer::vk {
class Renderer: public IRenderer
{
public:
Renderer(
class IGpu *gpu,
class IDevice *device,
class ISwapchain *swapchain,
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]] auto frame(uint32_t frame_idx, std::function<void()> submit_scene)
-> Result override;
void replace_swapchain(ISwapchain *swapchain) override;
void set_frame_constants(FrameConstants constants) override
{
m_frame_constants = constants;
}
void submit_sprite(
const components::Sprite &sprite,
const math::components::Transform &transform
) override;
private:
void record_cmd(VkCommandBuffer cmd, uint32_t image_idx);
void map_buffers(uint32_t frame_idx);
void flush_buffers(VkCommandBuffer cmd);
memory::NullOnMove<class Device *> m_device {};
class Swapchain *m_swapchain {};
memory::Ref<class Pass> m_pass;
VkCommandPool m_pool = VK_NULL_HANDLE;
VkCommandPool m_transient_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 {};
FrameConstants m_frame_constants;
Buffer m_vertex_buffer;
Buffer m_staging_buffer;
size_t m_staging_offset;
std::span<std::byte> m_staging_map;
std::span<components::Sprite::Vertex> m_sprite_vertex_map;
size_t m_current_sprite_idx;
};
} // namespace lt::renderer::vk
module :private;
#include <memory/reference.hpp>
#include <renderer/backend/vk/context/swapchain.hpp>
#include <renderer/backend/vk/renderer/renderer.hpp>
namespace lt::renderer::vk {
Renderer::Renderer(IGpu *gpu, IDevice *device, ISwapchain *swapchain, uint32_t max_frames_in_flight)
: m_device(static_cast<Device *>(device))
, m_swapchain(static_cast<Swapchain *>(swapchain))
, m_resolution(m_swapchain->get_resolution())
, m_max_frames_in_flight(max_frames_in_flight)
, m_staging_offset()
, m_vertex_buffer(
device,
gpu,
IBuffer::CreateInfo {
.usage = IBuffer::Usage::vertex,
.size = 1'000'000,
.debug_name = "vertex buffer",
}
)
, m_staging_buffer(
device,
gpu,
IBuffer::CreateInfo {
.usage = IBuffer::Usage::staging,
.size = 1'000'000,
.debug_name = "staging buffer",
}
)
{
ensure(m_device, "Failed to initialize renderer: null device");
ensure(m_swapchain, "Failed to initialize renderer: null swapchain");
// TODO(Light): HARDCODED PASS!!!
m_pass = memory::create_ref<vk::Pass>(
m_device,
m_swapchain,
assets::ShaderAsset { "./data/test_assets/sprite.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_transient_pool = m_device->create_command_pool(
VkCommandPoolCreateInfo {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_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);
m_device->destroy_command_pool(m_transient_pool);
}
[[nodiscard]] auto Renderer::frame(uint32_t frame_idx, std::function<void()> submit_scene) -> Result
{
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 Result::invalid_swapchain;
}
m_device->reset_fence(frame_fence);
map_buffers(frame_idx);
submit_scene();
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 Result::success;
}
void Renderer::replace_swapchain(ISwapchain *swapchain)
{
m_device->wait_idle();
m_swapchain = static_cast<Swapchain *>(swapchain);
m_resolution = m_swapchain->get_resolution();
m_pass->replace_swapchain(*swapchain);
}
void Renderer::map_buffers(uint32_t frame_idx)
{
using components::Sprite;
m_current_sprite_idx = 0;
m_staging_map = m_staging_buffer.map();
auto frame_segment_size = m_staging_map.size() / m_max_frames_in_flight;
m_staging_offset = frame_segment_size * frame_idx;
m_staging_map = m_staging_map.subspan(m_staging_offset, frame_segment_size);
m_sprite_vertex_map = std::span<Sprite::Vertex>(
std::bit_cast<Sprite::Vertex *>(m_staging_map.data()),
m_staging_map.size() / sizeof(Sprite::Vertex)
);
}
void Renderer::flush_buffers(VkCommandBuffer cmd)
{
m_staging_map = {};
m_sprite_vertex_map = {};
m_staging_buffer.unmap();
const auto buffer_copy_info = VkBufferCopy {
.srcOffset = m_staging_offset,
.dstOffset = m_staging_offset,
.size = m_current_sprite_idx * sizeof(components::Sprite::Vertex),
};
vk_cmd_copy_buffer(cmd, m_staging_buffer.vk(), m_vertex_buffer.vk(), 1u, &buffer_copy_info);
}
void Renderer::record_cmd(VkCommandBuffer cmd, uint32_t image_idx)
{
const auto cmd_begin_info = VkCommandBufferBeginInfo {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = {},
.pInheritanceInfo = nullptr,
};
const auto begin_frame_barrier = VkImageMemoryBarrier {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = {},
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.image = m_swapchain->get_image(image_idx),
.subresourceRange = VkImageSubresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0u,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0u,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
const auto end_frame_barrier = VkImageMemoryBarrier {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.dstAccessMask = {},
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.image = m_swapchain->get_image(image_idx),
.subresourceRange = VkImageSubresourceRange{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0u,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0u,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
const auto scissor = VkRect2D {
.offset = { .x = 0u, .y = 0u },
.extent = m_resolution,
};
const 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,
};
const auto color_attachment_info = VkRenderingAttachmentInfoKHR {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = m_swapchain->get_image_view(image_idx),
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.resolveMode = VK_RESOLVE_MODE_NONE,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.clearValue = VkClearValue { .color = { 0.93, 0.93, 0.93, 1.0 } },
};
const auto rendering_info = VkRenderingInfoKHR {
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR,
.renderArea = scissor,
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &color_attachment_info,
};
vk_reset_command_buffer(cmd, {});
vkc(vk_begin_command_buffer(cmd, &cmd_begin_info));
flush_buffers(cmd);
vk_cmd_push_constants(
cmd,
m_pass->get_layout(),
VK_SHADER_STAGE_VERTEX_BIT,
0u,
sizeof(FrameConstants),
&m_frame_constants
);
vk_cmd_pipeline_barrier(
cmd,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
0,
0,
nullptr,
0,
nullptr,
1,
&begin_frame_barrier
);
vk_cmd_begin_rendering(cmd, &rendering_info);
vk_cmd_bind_pipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pass->get_pipeline());
vk_cmd_set_viewport(cmd, 0, 1, &viewport);
vk_cmd_set_scissors(cmd, 0, 1, &scissor);
vk_cmd_draw(cmd, m_current_sprite_idx, 1, 0, 0);
vk_cmd_end_rendering(cmd);
vk_cmd_pipeline_barrier(
cmd,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
0,
0,
nullptr,
0,
nullptr,
1,
&end_frame_barrier
);
vkc(vk_end_command_buffer(cmd));
}
void Renderer::submit_sprite(
const components::Sprite &sprite,
const math::components::Transform &transform
)
{
using components::Sprite;
const auto &[x, y, z] = transform.translation;
const auto &[width, height, _] = transform.scale;
m_sprite_vertex_map[m_current_sprite_idx++] = components::Sprite::Vertex {
.position = { x, y + height, z },
.color = sprite.color,
};
m_sprite_vertex_map[m_current_sprite_idx++] = components::Sprite::Vertex {
.position = { x + width, y + height, z },
.color = sprite.color,
};
m_sprite_vertex_map[m_current_sprite_idx++] = components::Sprite::Vertex {
.position = { x + width, y, z },
.color = sprite.color,
};
m_sprite_vertex_map[m_current_sprite_idx++] = components::Sprite::Vertex {
.position = { x + width, y, z },
.color = sprite.color,
};
m_sprite_vertex_map[m_current_sprite_idx++] = components::Sprite::Vertex {
.position = { x, y, z },
.color = sprite.color,
};
m_sprite_vertex_map[m_current_sprite_idx++] = components::Sprite::Vertex {
.position = { x, y + height, z },
.color = sprite.color,
};
}
} // namespace lt::renderer::vk