444 lines
14 KiB
C++
444 lines
14 KiB
C++
/**
|
|
* @file frame_sync.cpp
|
|
* @brief MIRAI 框架帧同步机制实现
|
|
* @author MIRAI Team
|
|
* @version 0.1.0
|
|
*/
|
|
|
|
#include "frame_sync.hpp"
|
|
#include "logger.hpp"
|
|
|
|
namespace mirai {
|
|
|
|
// ================================================================================================
|
|
// frame_sync 实现
|
|
// ================================================================================================
|
|
|
|
frame_sync::frame_sync(std::shared_ptr<vulkan_device> device,
|
|
std::shared_ptr<swapchain> swap_chain,
|
|
const frame_sync_config& config)
|
|
: device_(std::move(device))
|
|
, swapchain_(std::move(swap_chain))
|
|
, frames_in_flight_(config.frames_in_flight)
|
|
, fence_timeout_(config.fence_timeout)
|
|
{
|
|
// 验证参数
|
|
if (frames_in_flight_ < 1) {
|
|
frames_in_flight_ = 1;
|
|
}
|
|
if (frames_in_flight_ > 3) {
|
|
frames_in_flight_ = 3;
|
|
}
|
|
|
|
// 创建同步对象和命令缓冲
|
|
if (!create_sync_objects()) {
|
|
MIRAI_LOG_ERROR("Failed to create sync objects");
|
|
return;
|
|
}
|
|
|
|
if (!create_command_buffers()) {
|
|
MIRAI_LOG_ERROR("Failed to create command buffers");
|
|
destroy_sync_objects();
|
|
return;
|
|
}
|
|
|
|
// 初始化图像飞行中栅栏
|
|
u32 image_count = swapchain_ ? swapchain_->get_image_count() : 0;
|
|
images_in_flight_.resize(image_count, nullptr);
|
|
|
|
MIRAI_LOG_DEBUG("Frame sync created with {} frames in flight", frames_in_flight_);
|
|
}
|
|
|
|
frame_sync::~frame_sync() {
|
|
if (device_ && device_->is_valid()) {
|
|
// 等待设备空闲
|
|
device_->wait_idle();
|
|
|
|
// 销毁资源
|
|
destroy_command_buffers();
|
|
destroy_sync_objects();
|
|
}
|
|
}
|
|
|
|
std::pair<frame_begin_result, u32> frame_sync::begin_frame() {
|
|
if (!is_valid() || !swapchain_) {
|
|
return {frame_begin_result::error, 0};
|
|
}
|
|
|
|
const auto& resources = frame_resources_[current_frame_];
|
|
|
|
// 等待当前帧的栅栏
|
|
vk::Result result = device_->get_device().waitForFences(resources.in_flight_fence, vk::True, fence_timeout_);
|
|
|
|
if (result == vk::Result::eTimeout) {
|
|
MIRAI_LOG_WARN("Frame fence wait timed out");
|
|
return {frame_begin_result::timeout, 0};
|
|
} else if (result == vk::Result::eErrorDeviceLost) {
|
|
MIRAI_LOG_ERROR("Device lost while waiting for frame fence");
|
|
return {frame_begin_result::device_lost, 0};
|
|
} else if (result != vk::Result::eSuccess) {
|
|
MIRAI_LOG_ERROR("Failed to wait for frame fence: {}", vk::to_string(result));
|
|
return {frame_begin_result::error, 0};
|
|
}
|
|
|
|
// 获取下一个交换链图像
|
|
auto [acquire_result, image_index] = swapchain_->acquire_next_image(
|
|
resources.image_available_semaphore);
|
|
|
|
if (acquire_result == acquire_result::out_of_date) {
|
|
return {frame_begin_result::swapchain_out_of_date, 0};
|
|
} else if (acquire_result != acquire_result::success &&
|
|
acquire_result != acquire_result::suboptimal) {
|
|
MIRAI_LOG_ERROR("Failed to acquire swapchain image");
|
|
return {frame_begin_result::error, 0};
|
|
}
|
|
|
|
current_image_index_ = image_index;
|
|
|
|
// 检查图像是否仍在使用中
|
|
if (images_in_flight_[image_index]) {
|
|
device_->get_device().waitForFences(images_in_flight_[image_index], vk::True, fence_timeout_);
|
|
}
|
|
|
|
// 标记该图像为当前帧正在使用
|
|
images_in_flight_[image_index] = resources.in_flight_fence;
|
|
|
|
// 重置栅栏
|
|
device_->get_device().resetFences(resources.in_flight_fence);
|
|
|
|
// 调用帧开始回调
|
|
if (on_frame_begin_) {
|
|
on_frame_begin_(current_frame_, current_image_index_);
|
|
}
|
|
|
|
return {frame_begin_result::success, current_image_index_};
|
|
}
|
|
|
|
frame_end_result frame_sync::end_frame(vk::CommandBuffer cmd_buffer) {
|
|
if (!is_valid() || !swapchain_) {
|
|
return frame_end_result::error;
|
|
}
|
|
|
|
const auto& resources = frame_resources_[current_frame_];
|
|
|
|
// 提交命令缓冲
|
|
vk::SubmitInfo submit_info{};
|
|
|
|
vk::Semaphore wait_semaphores[] = {resources.image_available_semaphore};
|
|
vk::PipelineStageFlags wait_stages[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput};
|
|
submit_info.setWaitSemaphores(wait_semaphores)
|
|
.setWaitDstStageMask(wait_stages)
|
|
.setCommandBuffers(cmd_buffer);
|
|
|
|
vk::Semaphore signal_semaphores[] = {resources.render_finished_semaphore};
|
|
submit_info.setSignalSemaphores(signal_semaphores);
|
|
|
|
vk::Result submit_result = device_->get_graphics_queue().submit(submit_info, resources.in_flight_fence);
|
|
if (submit_result != vk::Result::eSuccess) {
|
|
MIRAI_LOG_ERROR("Failed to submit command buffer: {}", vk::to_string(submit_result));
|
|
return frame_end_result::error;
|
|
}
|
|
|
|
// 呈现图像
|
|
auto present_result = swapchain_->present(current_image_index_,
|
|
resources.render_finished_semaphore);
|
|
|
|
// 调用帧结束回调
|
|
if (on_frame_end_) {
|
|
on_frame_end_(current_frame_);
|
|
}
|
|
|
|
// 推进到下一帧
|
|
advance_frame();
|
|
|
|
// 检查呈现结果
|
|
if (present_result == present_result::out_of_date) {
|
|
return frame_end_result::swapchain_out_of_date;
|
|
} else if (present_result == present_result::suboptimal) {
|
|
return frame_end_result::swapchain_suboptimal;
|
|
} else if (present_result != present_result::success) {
|
|
return frame_end_result::error;
|
|
}
|
|
|
|
return frame_end_result::success;
|
|
}
|
|
|
|
frame_end_result frame_sync::end_frame() {
|
|
return end_frame(frame_resources_[current_frame_].command_buffer);
|
|
}
|
|
|
|
bool frame_sync::wait_for_current_frame() {
|
|
if (!is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
const auto& resources = frame_resources_[current_frame_];
|
|
vk::Result result = device_->get_device().waitForFences(resources.in_flight_fence,
|
|
vk::True, fence_timeout_);
|
|
|
|
if (result != vk::Result::eSuccess) {
|
|
MIRAI_LOG_ERROR("Failed to wait for current frame: {}", vk::to_string(result));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool frame_sync::wait_for_all_frames() {
|
|
if (!is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
// 收集所有栅栏
|
|
std::vector<vk::Fence> fences;
|
|
fences.reserve(frames_in_flight_);
|
|
for (const auto& resources : frame_resources_) {
|
|
if (resources.in_flight_fence) {
|
|
fences.push_back(resources.in_flight_fence);
|
|
}
|
|
}
|
|
|
|
if (fences.empty()) {
|
|
return true;
|
|
}
|
|
|
|
vk::Result result = device_->get_device().waitForFences(fences, vk::True, fence_timeout_);
|
|
|
|
if (result != vk::Result::eSuccess) {
|
|
MIRAI_LOG_ERROR("Failed to wait for all frames: {}", vk::to_string(result));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool frame_sync::recreate(std::shared_ptr<swapchain> new_swapchain) {
|
|
if (!device_ || !device_->is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
// 等待所有帧完成
|
|
wait_for_all_frames();
|
|
|
|
// 更新交换链
|
|
swapchain_ = std::move(new_swapchain);
|
|
|
|
// 重新初始化图像飞行中栅栏
|
|
u32 image_count = swapchain_ ? swapchain_->get_image_count() : 0;
|
|
images_in_flight_.clear();
|
|
images_in_flight_.resize(image_count, nullptr);
|
|
|
|
// 重置当前帧索引
|
|
current_frame_ = 0;
|
|
current_image_index_ = 0;
|
|
|
|
MIRAI_LOG_DEBUG("Frame sync recreated for new swapchain");
|
|
return true;
|
|
}
|
|
|
|
void frame_sync::on_created() {
|
|
MIRAI_LOG_DEBUG("Frame sync created");
|
|
}
|
|
|
|
void frame_sync::on_destroying() {
|
|
MIRAI_LOG_DEBUG("Frame sync destroying");
|
|
}
|
|
|
|
bool frame_sync::create_sync_objects() {
|
|
if (!device_ || !device_->is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
frame_resources_.resize(frames_in_flight_);
|
|
|
|
vk::SemaphoreCreateInfo semaphore_info{};
|
|
|
|
vk::FenceCreateInfo fence_info{};
|
|
fence_info.flags = vk::FenceCreateFlagBits::eSignaled; // 初始状态为已信号
|
|
|
|
for (u32 i = 0; i < frames_in_flight_; ++i) {
|
|
auto& resources = frame_resources_[i];
|
|
|
|
// 创建图像可用信号量
|
|
auto image_sem_result = device_->get_device().createSemaphore(semaphore_info);
|
|
if (image_sem_result.result != vk::Result::eSuccess) {
|
|
MIRAI_LOG_ERROR("Failed to create image available semaphore: {}",
|
|
vk::to_string(image_sem_result.result));
|
|
return false;
|
|
}
|
|
resources.image_available_semaphore = image_sem_result.value;
|
|
|
|
// 创建渲染完成信号量
|
|
auto render_sem_result = device_->get_device().createSemaphore(semaphore_info);
|
|
if (render_sem_result.result != vk::Result::eSuccess) {
|
|
MIRAI_LOG_ERROR("Failed to create render finished semaphore: {}",
|
|
vk::to_string(render_sem_result.result));
|
|
return false;
|
|
}
|
|
resources.render_finished_semaphore = render_sem_result.value;
|
|
|
|
// 创建飞行中栅栏
|
|
auto fence_result = device_->get_device().createFence(fence_info);
|
|
if (fence_result.result != vk::Result::eSuccess) {
|
|
MIRAI_LOG_ERROR("Failed to create in flight fence: {}",
|
|
vk::to_string(fence_result.result));
|
|
return false;
|
|
}
|
|
resources.in_flight_fence = fence_result.value;
|
|
}
|
|
|
|
MIRAI_LOG_DEBUG("Created {} sets of sync objects", frames_in_flight_);
|
|
return true;
|
|
}
|
|
|
|
bool frame_sync::create_command_buffers() {
|
|
if (!device_ || !device_->is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
// 创建命令池
|
|
vk::CommandPoolCreateInfo pool_info{};
|
|
pool_info.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
|
|
if (device_->get_queue_family_indices().graphics_family.has_value()) {
|
|
pool_info.queueFamilyIndex = device_->get_queue_family_indices().graphics_family.value();
|
|
} else {
|
|
MIRAI_LOG_ERROR("No graphics queue family found");
|
|
return false;
|
|
}
|
|
|
|
auto pool_result = device_->get_device().createCommandPool(pool_info);
|
|
if (pool_result.result != vk::Result::eSuccess) {
|
|
MIRAI_LOG_ERROR("Failed to create command pool: {}", vk::to_string(pool_result.result));
|
|
return false;
|
|
}
|
|
command_pool_ = pool_result.value;
|
|
|
|
// 分配命令缓冲
|
|
vk::CommandBufferAllocateInfo alloc_info{};
|
|
alloc_info.commandPool = command_pool_;
|
|
alloc_info.level = vk::CommandBufferLevel::ePrimary;
|
|
alloc_info.commandBufferCount = frames_in_flight_;
|
|
|
|
auto alloc_result = device_->get_device().allocateCommandBuffers(alloc_info);
|
|
if (alloc_result.result != vk::Result::eSuccess) {
|
|
MIRAI_LOG_ERROR("Failed to allocate command buffers: {}", vk::to_string(alloc_result.result));
|
|
return false;
|
|
}
|
|
|
|
auto buffers = alloc_result.value;
|
|
for (u32 i = 0; i < frames_in_flight_; ++i) {
|
|
frame_resources_[i].command_buffer = buffers[i];
|
|
}
|
|
|
|
MIRAI_LOG_DEBUG("Created command pool and {} command buffers", frames_in_flight_);
|
|
return true;
|
|
}
|
|
|
|
void frame_sync::destroy_sync_objects() {
|
|
if (!device_ || !device_->is_valid()) {
|
|
return;
|
|
}
|
|
|
|
for (auto& resources : frame_resources_) {
|
|
if (resources.image_available_semaphore) {
|
|
device_->get_device().destroySemaphore(resources.image_available_semaphore);
|
|
resources.image_available_semaphore = nullptr;
|
|
}
|
|
if (resources.render_finished_semaphore) {
|
|
device_->get_device().destroySemaphore(resources.render_finished_semaphore);
|
|
resources.render_finished_semaphore = nullptr;
|
|
}
|
|
if (resources.in_flight_fence) {
|
|
device_->get_device().destroyFence(resources.in_flight_fence);
|
|
resources.in_flight_fence = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void frame_sync::destroy_command_buffers() {
|
|
if (!device_ || !device_->is_valid()) {
|
|
return;
|
|
}
|
|
|
|
// 命令缓冲在命令池销毁时自动释放
|
|
if (command_pool_) {
|
|
device_->get_device().destroyCommandPool(command_pool_);
|
|
command_pool_ = nullptr;
|
|
}
|
|
|
|
for (auto& resources : frame_resources_) {
|
|
resources.command_buffer = nullptr;
|
|
}
|
|
}
|
|
|
|
// ================================================================================================
|
|
// frame_time_tracker 实现
|
|
// ================================================================================================
|
|
|
|
frame_time_tracker::frame_time_tracker(u32 sample_count)
|
|
: sample_count_(sample_count)
|
|
{
|
|
frame_time_samples_.resize(sample_count, 0.0);
|
|
}
|
|
|
|
void frame_time_tracker::begin_frame() {
|
|
frame_start_time_ = std::chrono::high_resolution_clock::now();
|
|
}
|
|
|
|
void frame_time_tracker::end_frame() {
|
|
auto end_time = std::chrono::high_resolution_clock::now();
|
|
auto duration = std::chrono::duration<f64, std::milli>(end_time - frame_start_time_);
|
|
current_frame_time_ms_ = duration.count();
|
|
|
|
// 更新样本
|
|
frame_time_samples_[current_sample_index_] = current_frame_time_ms_;
|
|
current_sample_index_ = (current_sample_index_ + 1) % sample_count_;
|
|
|
|
if (!samples_filled_ && current_sample_index_ == 0) {
|
|
samples_filled_ = true;
|
|
}
|
|
|
|
// 更新统计
|
|
++frame_count_;
|
|
total_time_s_ += current_frame_time_ms_ / 1000.0;
|
|
}
|
|
|
|
frame_timing frame_time_tracker::get_timing() const {
|
|
frame_timing timing;
|
|
timing.cpu_frame_time_ms = current_frame_time_ms_;
|
|
timing.gpu_frame_time_ms = 0.0; // GPU 时间需要使用 GPU 查询获取
|
|
timing.fps = current_frame_time_ms_ > 0.0 ? 1000.0 / current_frame_time_ms_ : 0.0;
|
|
timing.frame_count = frame_count_;
|
|
timing.total_time_s = total_time_s_;
|
|
return timing;
|
|
}
|
|
|
|
f64 frame_time_tracker::get_average_frame_time_ms() const {
|
|
u32 count = samples_filled_ ? sample_count_ : current_sample_index_;
|
|
if (count == 0) {
|
|
return 0.0;
|
|
}
|
|
|
|
f64 sum = 0.0;
|
|
for (u32 i = 0; i < count; ++i) {
|
|
sum += frame_time_samples_[i];
|
|
}
|
|
|
|
return sum / static_cast<f64>(count);
|
|
}
|
|
|
|
f64 frame_time_tracker::get_average_fps() const {
|
|
f64 avg_time = get_average_frame_time_ms();
|
|
return avg_time > 0.0 ? 1000.0 / avg_time : 0.0;
|
|
}
|
|
|
|
void frame_time_tracker::reset() {
|
|
std::fill(frame_time_samples_.begin(), frame_time_samples_.end(), 0.0);
|
|
current_sample_index_ = 0;
|
|
samples_filled_ = false;
|
|
current_frame_time_ms_ = 0.0;
|
|
frame_count_ = 0;
|
|
total_time_s_ = 0.0;
|
|
}
|
|
|
|
} // namespace mirai
|