diff --git a/src/graphics/guest_gpu/command_processor/commandProcessor.h b/src/graphics/guest_gpu/command_processor/commandProcessor.h index eb490d6..90280cb 100644 --- a/src/graphics/guest_gpu/command_processor/commandProcessor.h +++ b/src/graphics/guest_gpu/command_processor/commandProcessor.h @@ -162,6 +162,7 @@ public: void Flip(void* dst_gpu_addr, uint32_t value); void FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action, void* dst_gpu_addr, uint32_t value); + void PrepareCpuFlip(); void SynchronizeGpu(); void SynchronizeGpuRange(uint64_t vaddr, uint64_t size); void MemoryBarrier(); diff --git a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp index a964c98..5e5d3f5 100644 --- a/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp +++ b/src/graphics/guest_gpu/command_processor/pm4Handlers.cpp @@ -2494,7 +2494,6 @@ KYTY_CP_OP_PARSER(CpOpFlip) { cp->SetFlip(f); cp->Flip(); - cp->BufferFlush(); return 5; } diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index 930988d..8507e2c 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -15,6 +15,7 @@ #include "graphics/host_gpu/objects/label.h" #include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/renderContext.h" +#include "graphics/presentation/displayBuffer.h" #include "graphics/presentation/videoOut.h" #include "graphics/presentation/window.h" #include "graphics/shader/shader.h" @@ -167,6 +168,7 @@ public: void Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer, uint32_t num_const_dw, int handle, int index, int flip_mode, int64_t flip_arg, bool trigger_agc_interrupt_on_done); + void SubmitFlipPreparation(); void Done(); void WaitForIdle(); bool IsIdle(); @@ -188,6 +190,7 @@ private: CommandProcessor::FlipInfo flip; bool trigger_agc_interrupt_on_done = false; + bool prepare_cpu_flip = false; }; static void ThreadBatchRun(void* data); @@ -266,6 +269,7 @@ public: uint32_t num_const_dw, bool trigger_agc_interrupt_on_done); void SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw, bool trigger_agc_interrupt_on_done); + void SubmitFlipPreparation(); void Done(); void PauseSubmissions(); void ResumeSubmissions(); @@ -335,6 +339,11 @@ void Gpu::SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw, ring->Submit(cmd_buffer, num_dw, trigger_agc_interrupt_on_done); } +void Gpu::SubmitFlipPreparation() { + GpuMutexLock lock(m_mutex); + m_gfx_ring->SubmitFlipPreparation(); +} + void Gpu::Done() { GraphicsRing* gfx_ring = nullptr; CommandProcessor* gfx_cp = nullptr; @@ -746,6 +755,26 @@ void GraphicsRing::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, m_cond_var.Signal(); } +void GraphicsRing::SubmitFlipPreparation() { + EXIT_IF(m_cp == nullptr); + Common::LockGuard lock(m_mutex); + + WindowWaitForGraphicInitialized(); + GraphicsRenderCreateContext(); + if (m_done) { + while (!m_idle) { + m_idle_cond_var.Wait(&m_mutex); + } + m_done = false; + m_cp->Reset(); + } + + auto& batch = m_cmd_batches.emplace_back(); + batch.prepare_cpu_flip = true; + m_idle = false; + m_cond_var.Signal(); +} + void GraphicsRing::Done() { Common::LockGuard lock(m_mutex); if (m_done) { @@ -803,20 +832,22 @@ void GraphicsRing::ThreadBatchRun(void* data) { cp->RunLock(); { cp->BufferInit(); - cp->ResetDeCe(); - cp->SetFlip(buf.flip); cp->SetSubmitId(++seq); - - ring->m_draw_job.Execute( - [cp, buf] { cp->Run(buf.draw_buffer.data, buf.draw_buffer.num_dw); }); - ring->m_constant_job.Execute( - [cp, buf] { cp->Run(buf.const_buffer.data, buf.const_buffer.num_dw); }); - ring->m_draw_job.Wait(); - ring->m_constant_job.Wait(); - - cp->BufferFlush(); - if (buf.trigger_agc_interrupt_on_done) { - GraphicsRenderTriggerEopEvent(0); + if (buf.prepare_cpu_flip) { + cp->PrepareCpuFlip(); + } else { + cp->ResetDeCe(); + cp->SetFlip(buf.flip); + ring->m_draw_job.Execute( + [cp, buf] { cp->Run(buf.draw_buffer.data, buf.draw_buffer.num_dw); }); + ring->m_constant_job.Execute( + [cp, buf] { cp->Run(buf.const_buffer.data, buf.const_buffer.num_dw); }); + ring->m_draw_job.Wait(); + ring->m_constant_job.Wait(); + cp->BufferFlush(); + if (buf.trigger_agc_interrupt_on_done) { + GraphicsRenderTriggerEopEvent(0); + } } } cp->RunUnlock(); @@ -1812,8 +1843,12 @@ void CommandProcessor::Flip() { LOGF("CommandProcessor::Flip()\n"); } - GraphicsRenderWriteAtEndOfPipeOnlyFlip(m_submit_id, CurrentBuffer(), m_flip.handle, - m_flip.index, m_flip.flip_mode, m_flip.flip_arg); + auto* command = CurrentBuffer(); + auto request = GraphicsRenderPrepareDisplayBufferFlip( + command, m_flip.handle, m_flip.index, m_flip.flip_mode, m_flip.flip_arg); + GraphicsRenderWriteAtEndOfPipeOnlyFlip(m_submit_id, command, m_flip.handle, m_flip.index, + m_flip.flip_mode, m_flip.flip_arg, request); + m_scheduler.Flush(); } void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) { @@ -1828,12 +1863,16 @@ void CommandProcessor::Flip(void* dst_gpu_addr, uint32_t value) { reinterpret_cast(dst_gpu_addr), value); } + auto* command = CurrentBuffer(); + auto request = GraphicsRenderPrepareDisplayBufferFlip( + command, m_flip.handle, m_flip.index, m_flip.flip_mode, m_flip.flip_arg); CommandProcessorGuestAccessScope guest_access(CommandProcessorGuestAccess::DirectFence, reinterpret_cast(dst_gpu_addr), sizeof(uint32_t)); GraphicsRenderWriteAtEndOfPipeWithFlip32( - m_submit_id, CurrentBuffer(), static_cast(dst_gpu_addr), value, m_flip.handle, - m_flip.index, m_flip.flip_mode, m_flip.flip_arg); + m_submit_id, command, static_cast(dst_gpu_addr), value, m_flip.handle, + m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request); + m_scheduler.Flush(); } void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action, @@ -1851,16 +1890,36 @@ void CommandProcessor::FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache eop_event_type, cache_action, reinterpret_cast(dst_gpu_addr), value); } - if (eop_event_type == 0x00000004 && cache_action == 0x00000038) { - CommandProcessorGuestAccessScope guest_access(CommandProcessorGuestAccess::DirectFence, - reinterpret_cast(dst_gpu_addr), - sizeof(uint32_t)); - GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBackFlip32( - m_submit_id, CurrentBuffer(), static_cast(dst_gpu_addr), value, - m_flip.handle, m_flip.index, m_flip.flip_mode, m_flip.flip_arg); - } else { + if (eop_event_type != 0x00000004 || cache_action != 0x00000038) { EXIT("unknown event type\n"); } + auto* command = CurrentBuffer(); + auto request = GraphicsRenderPrepareDisplayBufferFlip(command, m_flip.handle, m_flip.index, + m_flip.flip_mode, m_flip.flip_arg); + CommandProcessorGuestAccessScope guest_access(CommandProcessorGuestAccess::DirectFence, + reinterpret_cast(dst_gpu_addr), + sizeof(uint32_t)); + GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBackFlip32( + m_submit_id, command, static_cast(dst_gpu_addr), value, m_flip.handle, + m_flip.index, m_flip.flip_mode, m_flip.flip_arg, request); + m_scheduler.Flush(); +} + +void CommandProcessor::PrepareCpuFlip() { + Common::LockGuard lock(m_mutex); + CheckBuffer(); + if (g_current_run_cp != nullptr) { + EXIT("invalid graphics-thread CPU flip preparation\n"); + } + struct RunScope { + explicit RunScope(CommandProcessor* cp) { g_current_run_cp = cp; } + ~RunScope() { g_current_run_cp = nullptr; } + }; + RunScope run_scope(this); + + auto prepared_id = Presentation::DisplayBufferPrepareNextFlipOnGpu(CurrentBuffer()); + m_scheduler.Flush(); + Presentation::DisplayBufferCompleteFlipFromGpu(prepared_id); } void CommandProcessor::SynchronizeGpu() { @@ -1894,6 +1953,11 @@ void GraphicsRunSubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num g_gpu->SubmitCompute(queue, cmd_buffer, num_dw, trigger_agc_interrupt_on_done); } +void GraphicsRunSubmitFlipPreparation() { + EXIT_IF(g_gpu == nullptr); + g_gpu->SubmitFlipPreparation(); +} + void GraphicsRunWait() { GraphicsRunSubmissionLock lock; } diff --git a/src/graphics/guest_gpu/graphicsRun.h b/src/graphics/guest_gpu/graphicsRun.h index 99682de..f2c0eeb 100644 --- a/src/graphics/guest_gpu/graphicsRun.h +++ b/src/graphics/guest_gpu/graphicsRun.h @@ -19,6 +19,7 @@ void GraphicsRunSubmit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t uint32_t num_const_dw, bool trigger_agc_interrupt_on_done = false); void GraphicsRunSubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw, bool trigger_agc_interrupt_on_done = false); +void GraphicsRunSubmitFlipPreparation(); void GraphicsRunWait(); void GraphicsRunDone(); int GraphicsRunGetFrameNum(); diff --git a/src/graphics/host_gpu/graphicContext.h b/src/graphics/host_gpu/graphicContext.h index 683f18f..baec3a4 100644 --- a/src/graphics/host_gpu/graphicContext.h +++ b/src/graphics/host_gpu/graphicContext.h @@ -29,7 +29,6 @@ struct VulkanSwapchain { std::unique_ptr render_complete_semaphores; uint32_t current_index = 0; uint32_t present_frame = 0; - std::vector> present_command_buffers; }; struct VulkanCommandPool { diff --git a/src/graphics/host_gpu/renderer/context.cpp b/src/graphics/host_gpu/renderer/context.cpp index db093ad..29d677d 100644 --- a/src/graphics/host_gpu/renderer/context.cpp +++ b/src/graphics/host_gpu/renderer/context.cpp @@ -330,7 +330,8 @@ void CommandBuffer::Execute() { queue.mutex->Unlock(); } - m_execute = true; + m_execute = true; + m_fence_waited = false; m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1; if (result != VK_SUCCESS) { @@ -393,7 +394,8 @@ void CommandBuffer::ExecuteWithSemaphore(VkSemaphore signal_semaphore) { queue.mutex->Unlock(); } - m_execute = true; + m_execute = true; + m_fence_waited = false; m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1; if (result != VK_SUCCESS) { @@ -460,7 +462,8 @@ void CommandBuffer::ExecuteWithSemaphore(VkSemaphore wait_semaphore, queue.mutex->Unlock(); } - m_execute = true; + m_execute = true; + m_fence_waited = false; m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1; if (result != VK_SUCCESS) { @@ -474,27 +477,11 @@ void CommandBuffer::ExecuteWithSemaphore(VkSemaphore wait_semaphore, } void CommandBuffer::WaitForFence() { - EXIT_IF(IsInvalid()); - const bool was_executed = m_execute; - if (m_execute) { - auto* device = g_render_ctx->GetGraphicCtx()->device; - - auto result = vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX); - if (result != VK_SUCCESS) { - LOGF("vkWaitForFences failed: %s (%d), wait=WaitForFence queue=%d index=%u " - "submit_seq=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64 - " args=%u,%u,%u,%u,0x%016" PRIx64 "\n", - string_VkResult(result), static_cast(result), m_queue, m_index, m_submit_seq, - m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, - m_debug_arg3, m_debug_arg4); - } - EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS); - - m_execute = false; - } - + WaitForFenceOnly(); if (was_executed) { + m_execute = false; + m_fence_waited = false; RecycleDescriptorsAfterFence(); m_fence_resources.ReleaseAfterFence(); } @@ -505,32 +492,34 @@ void CommandBuffer::WaitForFence() { m_delete_after_fence.clear(); } -void CommandBuffer::WaitForFenceAndReset() { +void CommandBuffer::WaitForFenceOnly() { EXIT_IF(IsInvalid()); + if (!m_execute || m_fence_waited) { + return; + } + auto* device = g_render_ctx->GetGraphicCtx()->device; + auto result = vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX); + if (result != VK_SUCCESS) { + LOGF("vkWaitForFences failed: %s (%d), queue=%d index=%u submit_seq=%" PRIu64 + " debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n", + string_VkResult(result), static_cast(result), m_queue, m_index, m_submit_seq, + m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3, + m_debug_arg4); + } + EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS); + m_fence_waited = true; +} +void CommandBuffer::WaitForFenceAndReset() { const bool was_executed = m_execute; - if (m_execute) { - auto* device = g_render_ctx->GetGraphicCtx()->device; - - auto result = vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX); - if (result != VK_SUCCESS) { - LOGF("vkWaitForFences failed: %s (%d), wait=WaitForFenceAndReset queue=%d index=%u " - "submit_seq=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64 - " args=%u,%u,%u,%u,0x%016" PRIx64 "\n", - string_VkResult(result), static_cast(result), m_queue, m_index, m_submit_seq, - m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, - m_debug_arg3, m_debug_arg4); - } - EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS); - - m_execute = false; - + WaitForFenceOnly(); + if (was_executed) { + m_execute = false; + m_fence_waited = false; vkResetCommandBuffer(m_pool->buffers[m_index], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); } - m_host_stream.Reset(); - if (was_executed) { RecycleDescriptorsAfterFence(); m_fence_resources.ReleaseAfterFence(); diff --git a/src/graphics/host_gpu/renderer/render.h b/src/graphics/host_gpu/renderer/render.h index 853a057..24a85b5 100644 --- a/src/graphics/host_gpu/renderer/render.h +++ b/src/graphics/host_gpu/renderer/render.h @@ -76,6 +76,7 @@ public: void BeginRenderPass(VulkanFramebuffer* framebuffer, RenderColorInfo* colors, uint32_t color_count, RenderDepthInfo* depth) const; void EndRenderPass() const; + void WaitForFenceOnly(); void WaitForFence(); void WaitForFenceAndReset(); void DeleteAfterFence(VulkanBuffer* buffer); @@ -96,6 +97,7 @@ private: uint32_t m_index = static_cast(-1); int m_queue = -1; bool m_execute = false; + bool m_fence_waited = false; uint64_t m_submit_seq = 0; uint32_t m_debug_op = 0; uint64_t m_debug_submit_id = 0; @@ -150,14 +152,18 @@ void GraphicsRenderWriteAtEndOfPipe32(uint64_t submit_id, CommandBuffer* buffer, void GraphicsRenderWriteAtEndOfPipeGds32(uint64_t submit_id, CommandBuffer* buffer, uint32_t* dst_gpu_addr, uint32_t dw_offset, uint32_t dw_num); +uint64_t GraphicsRenderPrepareDisplayBufferFlip(CommandBuffer* buffer, int handle, int index, + int flip_mode, int64_t flip_arg); void GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBackFlip32( uint64_t submit_id, CommandBuffer* buffer, uint32_t* dst_gpu_addr, uint32_t value, int handle, - int index, int flip_mode, int64_t flip_arg); + int index, int flip_mode, int64_t flip_arg, uint64_t request_id); void GraphicsRenderWriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer* buffer, uint32_t* dst_gpu_addr, uint32_t value, int handle, - int index, int flip_mode, int64_t flip_arg); + int index, int flip_mode, int64_t flip_arg, + uint64_t request_id); void GraphicsRenderWriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer* buffer, int handle, - int index, int flip_mode, int64_t flip_arg); + int index, int flip_mode, int64_t flip_arg, + uint64_t request_id); void GraphicsRenderWriteAtEndOfPipeWithWriteBack64(uint64_t submit_id, CommandBuffer* buffer, uint64_t* dst_gpu_addr, uint64_t value); void GraphicsRenderWriteAtEndOfPipeWithWriteBack32(uint64_t submit_id, CommandBuffer* buffer, diff --git a/src/graphics/host_gpu/renderer/sync.cpp b/src/graphics/host_gpu/renderer/sync.cpp index ddc3a93..4fb952b 100644 --- a/src/graphics/host_gpu/renderer/sync.cpp +++ b/src/graphics/host_gpu/renderer/sync.cpp @@ -84,30 +84,12 @@ static void PublishImmediateFence(T* dst, T value) { std::memcpy(dst, &value, sizeof(value)); } -static void SubmitDisplayBufferFlip(const uint64_t* args) { - if (g_render_ctx == nullptr || args == nullptr) { - EXIT("GPU flip submission has invalid state, render_ctx=%p args=%p\n", - static_cast(g_render_ctx), static_cast(args)); - } - const auto handle = static_cast(args[0]); - const auto index = static_cast(args[1]); - const auto flip_mode = static_cast(args[2]); - const auto flip_arg = static_cast(args[3]); - const auto result = - Presentation::DisplayBufferSubmitFlipFromGpu(handle, index, flip_mode, flip_arg); - if (result != 0) { - EXIT("GPU flip submission failed, result=%d handle=%d index=%d mode=%d arg=%" PRId64 "\n", - result, handle, index, flip_mode, flip_arg); - } -} - static bool CompleteDisplayBufferFlip(const uint64_t* args) { if (g_render_ctx == nullptr || args == nullptr) { EXIT("GPU flip completion has invalid state, render_ctx=%p args=%p\n", static_cast(g_render_ctx), static_cast(args)); } - Presentation::DisplayBufferCompleteFlipFromGpu(static_cast(args[0]), - static_cast(args[1])); + Presentation::DisplayBufferCompleteFlipFromGpu(args[0]); return true; } @@ -348,9 +330,28 @@ void GraphicsRenderWriteAtEndOfPipeWithInterrupt32(uint64_t submit_id, CommandBu args); } +uint64_t GraphicsRenderPrepareDisplayBufferFlip(CommandBuffer* buffer, int handle, int index, + int flip_mode, int64_t flip_arg) { + for (;;) { + uint64_t request_id = 0; + const auto result = Presentation::DisplayBufferSubmitFlipFromGpu( + buffer, handle, index, flip_mode, flip_arg, &request_id); + if (result == OK) { + EXIT_IF(request_id == 0); + return request_id; + } + if (result != VideoOut::VIDEO_OUT_ERROR_FLIP_QUEUE_FULL) { + EXIT("GPU flip submission failed, result=%d handle=%d index=%d mode=%d arg=%" PRId64 + "\n", + result, handle, index, flip_mode, flip_arg); + } + Presentation::DisplayBufferWaitForFlipQueueSlot(); + } +} + void GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBackFlip32( uint64_t submit_id, CommandBuffer* buffer, uint32_t* dst_gpu_addr, uint32_t value, int handle, - int index, int flip_mode, int64_t flip_arg) { + int index, int flip_mode, int64_t flip_arg, uint64_t request_id) { EXIT_IF(g_render_ctx == nullptr); EXIT_IF(dst_gpu_addr == nullptr); EXIT_IF(buffer == nullptr); @@ -361,11 +362,7 @@ void GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBackFlip32( static_cast(flip_mode), value, static_cast(flip_arg)); PublishImmediateFence(dst_gpu_addr, value); - uint64_t args[LABEL_ARGS_MAX] = {static_cast(handle), static_cast(index), - static_cast(flip_mode), - static_cast(flip_arg)}; - - SubmitDisplayBufferFlip(args); + const uint64_t args[LABEL_ARGS_MAX] = {request_id}; SubmitLabel32( buffer, nullptr, 0, CompleteDisplayBufferFlip, [](const uint64_t* /*args*/) { @@ -378,7 +375,8 @@ void GraphicsRenderWriteAtEndOfPipeWithInterruptWriteBackFlip32( void GraphicsRenderWriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer* buffer, uint32_t* dst_gpu_addr, uint32_t value, int handle, - int index, int flip_mode, int64_t flip_arg) { + int index, int flip_mode, int64_t flip_arg, + uint64_t request_id) { EXIT_IF(g_render_ctx == nullptr); EXIT_IF(dst_gpu_addr == nullptr); EXIT_IF(buffer == nullptr); @@ -389,16 +387,13 @@ void GraphicsRenderWriteAtEndOfPipeWithFlip32(uint64_t submit_id, CommandBuffer* static_cast(flip_mode), value, static_cast(flip_arg)); PublishImmediateFence(dst_gpu_addr, value); - uint64_t args[LABEL_ARGS_MAX] = {static_cast(handle), static_cast(index), - static_cast(flip_mode), - static_cast(flip_arg)}; - - SubmitDisplayBufferFlip(args); + const uint64_t args[LABEL_ARGS_MAX] = {request_id}; SubmitLabel32(buffer, nullptr, 0, CompleteDisplayBufferFlip, nullptr, args); } void GraphicsRenderWriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer* buffer, int handle, - int index, int flip_mode, int64_t flip_arg) { + int index, int flip_mode, int64_t flip_arg, + uint64_t request_id) { EXIT_IF(g_render_ctx == nullptr); EXIT_IF(buffer == nullptr); EXIT_IF(buffer->IsInvalid()); @@ -407,11 +402,7 @@ void GraphicsRenderWriteAtEndOfPipeOnlyFlip(uint64_t submit_id, CommandBuffer* b static_cast(handle), static_cast(index), static_cast(flip_mode), 0, static_cast(flip_arg)); - uint64_t args[LABEL_ARGS_MAX] = {static_cast(handle), static_cast(index), - static_cast(flip_mode), - static_cast(flip_arg)}; - - SubmitDisplayBufferFlip(args); + const uint64_t args[LABEL_ARGS_MAX] = {request_id}; SubmitLabel32(buffer, nullptr, 0, CompleteDisplayBufferFlip, nullptr, args); } diff --git a/src/graphics/host_gpu/utils.cpp b/src/graphics/host_gpu/utils.cpp index 9c6f726..014756b 100644 --- a/src/graphics/host_gpu/utils.cpp +++ b/src/graphics/host_gpu/utils.cpp @@ -863,17 +863,21 @@ void UtilCopyImageWithBuffer(CommandBuffer* buffer, GraphicContext* ctx, VulkanI final_layout); } -void UtilBlitImage(CommandBuffer* buffer, VulkanImage* src_image, VulkanSwapchain* dst_swapchain) { +void UtilBlitPreparedImage(CommandBuffer* buffer, VulkanImage* src_image, + VulkanSwapchain* dst_swapchain) { auto* vk_buffer = buffer->GetPool()->buffers[buffer->GetIndex()]; + if (src_image->layout != VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { + EXIT("invalid prepared presentation image, image=%p vk_image=%p layout=%d\n", + static_cast(src_image), + src_image != nullptr ? static_cast(src_image->image) : nullptr, + src_image != nullptr ? static_cast(src_image->layout) : -1); + } VulkanImage swapchain_image(VulkanImageType::Unknown); swapchain_image.image = dst_swapchain->swapchain_images[dst_swapchain->current_index]; swapchain_image.layout = VK_IMAGE_LAYOUT_UNDEFINED; - auto src_layout = src_image->layout; - SetImageLayout(vk_buffer, src_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, src_layout, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); SetImageLayout(vk_buffer, &swapchain_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); @@ -902,9 +906,18 @@ void UtilBlitImage(CommandBuffer* buffer, VulkanImage* src_image, VulkanSwapchai vkCmdBlitImage(vk_buffer, src_image->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swapchain_image.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, VK_FILTER_LINEAR); +} - SetImageLayout(vk_buffer, src_image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL); +void UtilClearColorImage(CommandBuffer* buffer, VulkanImage* image, + const VkClearColorValue& color) { + auto* vk_buffer = buffer->GetPool()->buffers[buffer->GetIndex()]; + SetImageLayout(vk_buffer, image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, image->layout, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + const VkImageSubresourceRange range {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + vkCmdClearColorImage(vk_buffer, image->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1, + &range); + SetImageLayout(vk_buffer, image, 0, 1, VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); } void VulkanCreateBuffer(GraphicContext* gctx, uint64_t size, VulkanBuffer* buffer) { diff --git a/src/graphics/host_gpu/utils.h b/src/graphics/host_gpu/utils.h index 53eee2c..2635a5e 100644 --- a/src/graphics/host_gpu/utils.h +++ b/src/graphics/host_gpu/utils.h @@ -90,7 +90,10 @@ void UtilCopyImageWithBuffer(CommandBuffer* buffer, GraphicContext* ctx, VulkanI VkImageAspectFlags src_aspect, VulkanImage* dst_image, VkImageAspectFlags dst_aspect, uint32_t bytes_per_element, uint64_t dst_layout); -void UtilBlitImage(CommandBuffer* buffer, VulkanImage* src_image, VulkanSwapchain* dst_swapchain); +void UtilBlitPreparedImage(CommandBuffer* buffer, VulkanImage* src_image, + VulkanSwapchain* dst_swapchain); +void UtilClearColorImage(CommandBuffer* buffer, VulkanImage* image, + const VkClearColorValue& color); void UtilFillImage(GraphicContext* ctx, VulkanImage* dst_image, const void* src_data, uint64_t size, uint32_t src_pitch, uint64_t dst_layout); void UtilFillImage(GraphicContext* ctx, DepthStencilVulkanImage* dst_image, const void* src_data, diff --git a/src/graphics/presentation/displayBuffer.h b/src/graphics/presentation/displayBuffer.h index b0c7829..b96c6c0 100644 --- a/src/graphics/presentation/displayBuffer.h +++ b/src/graphics/presentation/displayBuffer.h @@ -4,6 +4,7 @@ #include "common/common.h" namespace Libs::Graphics { +class CommandBuffer; struct VideoOutVulkanImage; } // namespace Libs::Graphics @@ -17,8 +18,11 @@ struct DisplayBufferImage { }; DisplayBufferImage DisplayBufferFind(uint64_t addr, bool render_target = false); -int DisplayBufferSubmitFlipFromGpu(int handle, int index, int flip_mode, int64_t flip_arg); -void DisplayBufferCompleteFlipFromGpu(int handle, int index); +int DisplayBufferSubmitFlipFromGpu(Graphics::CommandBuffer* buffer, int handle, int index, + int flip_mode, int64_t flip_arg, uint64_t* request_id); +uint64_t DisplayBufferPrepareNextFlipOnGpu(Graphics::CommandBuffer* buffer); +void DisplayBufferCompleteFlipFromGpu(uint64_t request_id); +void DisplayBufferWaitForFlipQueueSlot(); } // namespace Libs::Presentation diff --git a/src/graphics/presentation/videoOut.cpp b/src/graphics/presentation/videoOut.cpp index 4fdd501..436bb7f 100644 --- a/src/graphics/presentation/videoOut.cpp +++ b/src/graphics/presentation/videoOut.cpp @@ -10,6 +10,7 @@ #include "common/threads.h" #include "common/timer.h" #include "graphics/guest_gpu/gpu_defs.h" +#include "graphics/guest_gpu/graphicsRun.h" #include "graphics/guest_gpu/tile.h" #include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/renderContext.h" @@ -21,6 +22,7 @@ #include "libs/libs.h" #include +#include #include #include @@ -45,6 +47,7 @@ constexpr int VIDEO_OUT_FLIP_MODE_VSYNC_MULTI = 4; constexpr int VIDEO_OUT_BUFFER_INDEX_BLACK = -2; constexpr int VIDEO_OUT_BUFFER_INDEX_BLANK = -1; constexpr int VIDEO_OUT_BUFFER_NUM_MAX = 16; +constexpr size_t VIDEO_OUT_FLIP_QUEUE_CAPACITY = 16; constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX = 4; constexpr uint64_t VIDEO_OUT_OUTPUT_MODE_DEFAULT = 0x0000000000000001ULL; constexpr uint64_t VIDEO_OUT_OUTPUT_MODE_119_88HZ = 0x000000000000000FULL; @@ -160,28 +163,39 @@ public: virtual ~FlipQueue() { KYTY_NOT_IMPLEMENTED; } KYTY_CLASS_NO_COPY(FlipQueue); - bool Submit(VideoOutConfig* cfg, int index, int64_t flip_arg, bool gpu_eop); + bool Reserve(VideoOutConfig* cfg, int index, int64_t flip_arg, bool gpu_eop, + uint64_t* request_id); + void Prepare(uint64_t request_id, Graphics::CommandBuffer* buffer); + uint64_t PrepareNextCpu(Graphics::CommandBuffer* buffer); + void Complete(uint64_t request_id); + void WaitForSubmitSlot(); bool Flip(uint32_t micros); bool HasPending(VideoOutConfig* cfg, int start_index, int count); void GetFlipStatus(VideoOutConfig* cfg, VideoOutFlipStatus* out); - void AllowEopPresent(VideoOutConfig* cfg, int index); void Wait(VideoOutConfig* cfg, int index); private: + enum class RequestState { Reserved, Recording, Ready, Presenting }; + struct Request { + uint64_t id; VideoOutConfig* cfg; int index; int64_t flip_arg; uint64_t submit_ptc; bool gpu_eop; - bool prepared; + RequestState state; + Graphics::PreparedFrame* frame; }; Common::Mutex m_mutex; Common::CondVar m_submit_cond_var; + Common::CondVar m_submit_slot_cond_var; Common::CondVar m_done_cond_var; std::list m_requests; + std::list m_cpu_requests; bool m_processing = false; + uint64_t m_next_request_id = 1; }; class VideoOutContext { @@ -558,52 +572,145 @@ Presentation::DisplayBufferImage VideoOutContext::FindImage(const void* buffer, return ret; } -bool FlipQueue::Submit(VideoOutConfig* cfg, int index, int64_t flip_arg, bool gpu_eop) { +bool FlipQueue::Reserve(VideoOutConfig* cfg, int index, int64_t flip_arg, bool gpu_eop, + uint64_t* request_id) { + EXIT_IF(cfg == nullptr || request_id == nullptr); Common::LockGuard lock(m_mutex); - if (m_requests.size() >= 16) { + if (m_requests.size() + m_cpu_requests.size() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) { return false; } + auto& pending = gpu_eop ? m_requests : m_cpu_requests; Request r {}; + r.id = m_next_request_id++; r.cfg = cfg; r.index = index; r.flip_arg = flip_arg; r.submit_ptc = LibKernel::KernelGetProcessTimeCounter(); r.gpu_eop = gpu_eop; - r.prepared = !gpu_eop; + r.state = RequestState::Reserved; - m_requests.push_back(r); + pending.push_back(r); + *request_id = r.id; - cfg->flip_status.flipPendingNum = static_cast(m_requests.size()); + cfg->flip_status.flipPendingNum = static_cast(m_requests.size() + m_cpu_requests.size()); cfg->flip_status.submitProcessTimeCounter = r.submit_ptc; if (gpu_eop) { cfg->flip_status.gcQueueNum++; } - m_submit_cond_var.Signal(); - return true; } -void FlipQueue::AllowEopPresent(VideoOutConfig* cfg, int index) { - Common::LockGuard lock(m_mutex); - auto request = std::find_if(m_requests.begin(), m_requests.end(), [cfg, index](const auto& r) { - return r.cfg == cfg && r.index == index && r.gpu_eop && !r.prepared; - }); - if (request == m_requests.end()) { - EXIT("completed GPU flip has no queued request\n"); +void FlipQueue::Prepare(uint64_t request_id, Graphics::CommandBuffer* buffer) { + EXIT_IF(buffer == nullptr); + + VideoOutConfig* cfg = nullptr; + int index = 0; + { + Common::LockGuard lock(m_mutex); + auto request = std::find_if(m_requests.begin(), m_requests.end(), + [request_id](const auto& r) { return r.id == request_id; }); + if (request == m_requests.end()) { + auto pending = std::find_if(m_cpu_requests.begin(), m_cpu_requests.end(), + [request_id](const auto& r) { return r.id == request_id; }); + if (pending == m_cpu_requests.end()) { + EXIT("cannot prepare video-out request id=%" PRIu64 "\n", request_id); + } + request = m_requests.insert(m_requests.end(), *pending); + m_cpu_requests.erase(pending); + } + if (request->state != RequestState::Reserved) { + EXIT("cannot prepare video-out request id=%" PRIu64 "\n", request_id); + } + request->state = RequestState::Recording; + cfg = request->cfg; + index = request->index; } - request->prepared = true; + + const bool special = IsSpecialBufferIndex(index); + Graphics::VideoOutVulkanImage* source = nullptr; + uint32_t width = 0; + uint32_t height = 0; + { + Common::LockGuard lock(cfg->mutex); + if (cfg->closing) { + EXIT("cannot prepare flip for a closing video-out, id=%" PRIu64 "\n", request_id); + } + if (special) { + width = cfg->width; + height = cfg->height; + } else { + if (cfg->unregistering[index]) { + EXIT("cannot prepare flip from an unavailable surface, id=%" PRIu64 " index=%d\n", + request_id, index); + } + source = cfg->buffers[index].buffer_vulkan; + if (source == nullptr) { + EXIT("cannot prepare flip without a native surface, id=%" PRIu64 " index=%d\n", + request_id, index); + } + } + } + auto* frame = special ? Graphics::WindowPrepareBlankFrame(buffer, width, height, + index == VIDEO_OUT_BUFFER_INDEX_BLACK) + : Graphics::WindowPrepareFrame(buffer, source); + + Common::LockGuard lock(m_mutex); + auto request = std::find_if(m_requests.begin(), m_requests.end(), + [request_id](const auto& r) { return r.id == request_id; }); + if (request == m_requests.end() || request->state != RequestState::Recording || + request->frame != nullptr) { + EXIT("video-out request changed while recording, id=%" PRIu64 "\n", request_id); + } + request->frame = frame; +} + +uint64_t FlipQueue::PrepareNextCpu(Graphics::CommandBuffer* buffer) { + uint64_t request_id = 0; + { + Common::LockGuard lock(m_mutex); + if (m_cpu_requests.empty()) { + EXIT("CPU flip preparation has no accepted request\n"); + } + request_id = m_cpu_requests.front().id; + } + Prepare(request_id, buffer); + return request_id; +} + +void FlipQueue::Complete(uint64_t request_id) { + Common::LockGuard lock(m_mutex); + auto request = std::find_if(m_requests.begin(), m_requests.end(), + [request_id](const auto& r) { return r.id == request_id; }); + if (request == m_requests.end() || request->state != RequestState::Recording || + request->frame == nullptr) { + EXIT("completed GPU flip has no prepared recording, id=%" PRIu64 "\n", request_id); + } + request->state = RequestState::Ready; m_submit_cond_var.Signal(); } +void FlipQueue::WaitForSubmitSlot() { + Common::LockGuard lock(m_mutex); + while (m_requests.size() + m_cpu_requests.size() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) { + if (m_requests.empty()) { + EXIT("video-out queue is saturated by CPU flips queued behind the current EOP\n"); + } + m_submit_slot_cond_var.Wait(&m_mutex); + } +} + void FlipQueue::Wait(VideoOutConfig* cfg, int index) { Common::LockGuard lock(m_mutex); - while (std::find_if(m_requests.begin(), m_requests.end(), [cfg, index](const auto& r) { - return r.cfg == cfg && r.index == index; - }) != m_requests.end()) { + auto has_request = [this, cfg, index] { + auto matches = [cfg, index](const auto& r) { return r.cfg == cfg && r.index == index; }; + return std::any_of(m_requests.begin(), m_requests.end(), matches) || + std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches); + }; + while (has_request()) { m_done_cond_var.Wait(&m_mutex); } } @@ -613,10 +720,12 @@ bool FlipQueue::HasPending(VideoOutConfig* cfg, int start_index, int count) { EXIT("invalid video-out pending-flip query range\n"); } Common::LockGuard lock(m_mutex); - return std::any_of(m_requests.begin(), m_requests.end(), [&](const auto& request) { + auto matches = [&](const auto& request) { return request.cfg == cfg && request.index >= start_index && request.index < start_index + count; - }); + }; + return std::any_of(m_requests.begin(), m_requests.end(), matches) || + std::any_of(m_cpu_requests.begin(), m_cpu_requests.end(), matches); } bool FlipQueue::Flip(uint32_t micros) { @@ -634,7 +743,7 @@ bool FlipQueue::Flip(uint32_t micros) { if (m_processing) { EXIT("video-out flip queue processing is already active\n"); } - if (m_requests.front().gpu_eop && !m_requests.front().prepared) { + if (m_requests.front().state != RequestState::Ready) { m_mutex.Unlock(); return false; } @@ -647,23 +756,19 @@ bool FlipQueue::Flip(uint32_t micros) { return false; } - Graphics::VideoOutVulkanImage* buffer = nullptr; - r.cfg->mutex.Lock(); - if (!IsSpecialBufferIndex(r.index)) { - buffer = r.cfg->buffers[r.index].buffer_vulkan; - if (buffer == nullptr) { - EXIT("queued video-out flip has no native surface\n"); - } - } - r.cfg->mutex.Unlock(); - if (buffer != nullptr) { - Graphics::g_render_ctx->GetTextureCache()->RefreshVideoOut(buffer); - Graphics::WindowDrawBuffer(buffer); + m_mutex.Lock(); + if (m_requests.empty() || m_requests.front().id != r.id || + m_requests.front().state != RequestState::Ready || !m_processing) { + EXIT("video-out request changed before presentation, id=%" PRIu64 "\n", r.id); } + m_requests.front().state = RequestState::Presenting; + m_mutex.Unlock(); + + Graphics::WindowPresentFrame(r.frame); m_mutex.Lock(); - if (m_requests.empty() || m_requests.front().cfg != r.cfg || - m_requests.front().index != r.index || m_requests.front().submit_ptc != r.submit_ptc) { + if (m_requests.empty() || m_requests.front().id != r.id || + m_requests.front().state != RequestState::Presenting) { EXIT("video-out flip queue changed while processing its front request\n"); } m_requests.pop_front(); @@ -674,13 +779,14 @@ bool FlipQueue::Flip(uint32_t micros) { r.cfg->flip_status.submitProcessTimeCounter = r.submit_ptc; r.cfg->flip_status.flipArg = r.flip_arg; r.cfg->flip_status.currentBuffer = r.index; - r.cfg->flip_status.flipPendingNum = static_cast(m_requests.size()); + r.cfg->flip_status.flipPendingNum = static_cast(m_requests.size() + m_cpu_requests.size()); if (r.gpu_eop && r.cfg->flip_status.gcQueueNum > 0) { r.cfg->flip_status.gcQueueNum--; } m_processing = false; - m_done_cond_var.Signal(); + m_done_cond_var.SignalAll(); + m_submit_slot_cond_var.Signal(); m_mutex.Unlock(); r.cfg->mutex.Lock(); @@ -1448,17 +1554,20 @@ KYTY_SYSV_ABI int VideoOutSubmitFlip(int handle, int index, int flip_mode, int64 if (!IsValidBufferIndex(index)) { return VIDEO_OUT_ERROR_INVALID_INDEX; } - Common::LockGuard lock(ctx->mutex); - if (ctx->closing || (!IsSpecialBufferIndex(index) && ctx->unregistering[index])) { - return VIDEO_OUT_ERROR_INVALID_INDEX; - } - if (!IsSpecialBufferIndex(index) && ctx->buffers[index].buffer_vulkan == nullptr) { - return VIDEO_OUT_ERROR_INVALID_INDEX; - } - - if (!g_video_out_context->GetFlipQueue().Submit(ctx, index, flip_arg, false)) { - return VIDEO_OUT_ERROR_FLIP_QUEUE_FULL; + uint64_t request_id = 0; + { + Common::LockGuard lock(ctx->mutex); + const bool special = IsSpecialBufferIndex(index); + if (ctx->closing || (!special && (ctx->unregistering[index] || + ctx->buffers[index].buffer_vulkan == nullptr))) { + return VIDEO_OUT_ERROR_INVALID_INDEX; + } + if (!g_video_out_context->GetFlipQueue().Reserve(ctx, index, flip_arg, false, + &request_id)) { + return VIDEO_OUT_ERROR_FLIP_QUEUE_FULL; + } } + Graphics::GraphicsRunSubmitFlipPreparation(); return OK; } @@ -1467,8 +1576,10 @@ KYTY_SYSV_ABI int VideoOutSubmitFlip(int handle, int index, int flip_mode, int64 namespace Libs::Presentation { -int DisplayBufferSubmitFlipFromGpu(int handle, int index, int flip_mode, int64_t flip_arg) { - EXIT_IF(VideoOut::g_video_out_context == nullptr || Graphics::g_render_ctx == nullptr); +int DisplayBufferSubmitFlipFromGpu(Graphics::CommandBuffer* buffer, int handle, int index, + int flip_mode, int64_t flip_arg, uint64_t* request_id) { + EXIT_IF(VideoOut::g_video_out_context == nullptr || Graphics::g_render_ctx == nullptr || + buffer == nullptr || request_id == nullptr); auto* ctx = VideoOut::g_video_out_context->Get(handle); if (ctx == nullptr) { @@ -1483,41 +1594,36 @@ int DisplayBufferSubmitFlipFromGpu(int handle, int index, int flip_mode, int64_t if (!VideoOut::IsValidBufferIndex(index)) { return VideoOut::VIDEO_OUT_ERROR_INVALID_INDEX; } - Common::LockGuard lock(ctx->mutex); - if (ctx->closing || (!VideoOut::IsSpecialBufferIndex(index) && ctx->unregistering[index])) { - return VideoOut::VIDEO_OUT_ERROR_INVALID_INDEX; - } - if (!VideoOut::IsSpecialBufferIndex(index) && ctx->buffers[index].buffer_vulkan == nullptr) { - return VideoOut::VIDEO_OUT_ERROR_INVALID_INDEX; - } - if (!VideoOut::g_video_out_context->GetFlipQueue().Submit(ctx, index, flip_arg, true)) { - return VideoOut::VIDEO_OUT_ERROR_FLIP_QUEUE_FULL; + { + Common::LockGuard lock(ctx->mutex); + const bool special = VideoOut::IsSpecialBufferIndex(index); + if (ctx->closing || (!special && (ctx->unregistering[index] || + ctx->buffers[index].buffer_vulkan == nullptr))) { + return VideoOut::VIDEO_OUT_ERROR_INVALID_INDEX; + } + if (!VideoOut::g_video_out_context->GetFlipQueue().Reserve(ctx, index, flip_arg, true, + request_id)) { + return VideoOut::VIDEO_OUT_ERROR_FLIP_QUEUE_FULL; + } } + VideoOut::g_video_out_context->GetFlipQueue().Prepare(*request_id, buffer); return OK; } -void DisplayBufferCompleteFlipFromGpu(int handle, int index) { +uint64_t DisplayBufferPrepareNextFlipOnGpu(Graphics::CommandBuffer* buffer) { EXIT_IF(VideoOut::g_video_out_context == nullptr || Graphics::g_render_ctx == nullptr); + return VideoOut::g_video_out_context->GetFlipQueue().PrepareNextCpu(buffer); +} - auto* ctx = VideoOut::g_video_out_context->Get(handle); - if (ctx == nullptr || !VideoOut::IsValidBufferIndex(index)) { - EXIT("GPU flip completed with invalid handle or index, handle=%d index=%d\n", handle, - index); - } - Common::LockGuard lock(ctx->mutex); - if (ctx->closing || (!VideoOut::IsSpecialBufferIndex(index) && ctx->unregistering[index])) { - EXIT("GPU flip completed for an unavailable surface, handle=%d index=%d\n", handle, index); - } - if (!VideoOut::IsSpecialBufferIndex(index)) { - auto* image = ctx->buffers[index].buffer_vulkan; - if (image == nullptr) { - EXIT("GPU flip completed without a native surface, handle=%d index=%d\n", handle, - index); - } - Graphics::g_render_ctx->GetTextureCache()->RefreshVideoOut(image); - } - VideoOut::g_video_out_context->GetFlipQueue().AllowEopPresent(ctx, index); +void DisplayBufferCompleteFlipFromGpu(uint64_t request_id) { + EXIT_IF(VideoOut::g_video_out_context == nullptr || Graphics::g_render_ctx == nullptr); + VideoOut::g_video_out_context->GetFlipQueue().Complete(request_id); +} + +void DisplayBufferWaitForFlipQueueSlot() { + EXIT_IF(VideoOut::g_video_out_context == nullptr || Graphics::g_render_ctx == nullptr); + VideoOut::g_video_out_context->GetFlipQueue().WaitForSubmitSlot(); } } // namespace Libs::Presentation diff --git a/src/graphics/presentation/window.h b/src/graphics/presentation/window.h index fca1d0b..b3d8807 100644 --- a/src/graphics/presentation/window.h +++ b/src/graphics/presentation/window.h @@ -9,7 +9,9 @@ struct VkSurfaceCapabilitiesKHR; namespace Libs::Graphics { struct GraphicContext; +class CommandBuffer; struct VideoOutVulkanImage; +struct PreparedFrame; VkSurfaceCapabilitiesKHR* VulkanGetSurfaceCapabilities(); @@ -18,7 +20,10 @@ GraphicContext* WindowGetGraphicContext(); void WindowInit(uint32_t width, uint32_t height); void WindowRun(); void WindowWaitForGraphicInitialized(); -void WindowDrawBuffer(VideoOutVulkanImage* image); +PreparedFrame* WindowPrepareFrame(CommandBuffer* buffer, VideoOutVulkanImage* image); +PreparedFrame* WindowPrepareBlankFrame(CommandBuffer* buffer, uint32_t width, uint32_t height, + bool opaque); +void WindowPresentFrame(PreparedFrame* frame); } // namespace Libs::Graphics diff --git a/src/graphics/presentation/window/swapchain.cpp b/src/graphics/presentation/window/swapchain.cpp index 3556fb7..86c58f9 100644 --- a/src/graphics/presentation/window/swapchain.cpp +++ b/src/graphics/presentation/window/swapchain.cpp @@ -28,6 +28,7 @@ #include "common/timer.h" #include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/renderer/render.h" +#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/utils.h" #include "graphics/host_gpu/vma.h" #include "graphics/presentation/renderDoc.h" @@ -38,9 +39,11 @@ #include "loader/systemContent.h" #include +#include #include #include #include +#include #include #include #include @@ -55,6 +58,131 @@ namespace Libs::Graphics { +struct PreparedFrame { + VulkanImage image {VulkanImageType::Unknown}; + std::unique_ptr present_commands; + bool busy = false; +}; + +class PreparedFramePool { +public: + void EnsureCapacity(uint32_t count, VkFormat format) { + if (count == 0 || format == VK_FORMAT_UNDEFINED) { + EXIT("prepared-frame pool requires at least one frame\n"); + } + Common::LockGuard lock(m_mutex); + m_format = format; + while (m_frames.size() < count) { + auto frame = std::make_unique(); + m_free.push_back(frame.get()); + m_frames.push_back(std::move(frame)); + } + } + + VkFormat GetFormat() { + Common::LockGuard lock(m_mutex); + if (m_format == VK_FORMAT_UNDEFINED) { + EXIT("prepared-frame pool has no presentation format\n"); + } + return m_format; + } + + PreparedFrame* Acquire() { + m_mutex.Lock(); + if (m_frames.empty()) { + EXIT("prepared-frame pool was used before swapchain initialization\n"); + } + while (m_free.empty()) { + m_available.Wait(&m_mutex); + } + auto* frame = m_free.front(); + m_free.pop_front(); + if (frame->busy) { + EXIT("prepared-frame pool returned an invalid frame\n"); + } + frame->busy = true; + m_mutex.Unlock(); + + // The producer only waits here. Reset stays on the presentation thread that owns the + // allocating Vulkan command pool. + if (frame->present_commands != nullptr) { + frame->present_commands->WaitForFenceOnly(); + } + + return frame; + } + + void Release(PreparedFrame* frame) { + if (frame == nullptr) { + EXIT("cannot release a null prepared frame\n"); + } + Common::LockGuard lock(m_mutex); + if (!frame->busy) { + EXIT("prepared frame was released twice\n"); + } + frame->busy = false; + m_free.push_back(frame); + m_available.Signal(); + } + +private: + Common::Mutex m_mutex; + Common::CondVar m_available; + std::vector> m_frames; + std::deque m_free; + VkFormat m_format = VK_FORMAT_UNDEFINED; +}; + +static PreparedFramePool* GetPreparedFramePool() { + static auto* pool = new PreparedFramePool; + return pool; +} + +static void ConfigurePreparedFrame(PreparedFrame* frame, VkExtent2D extent, VkFormat format) { + EXIT_IF(frame == nullptr); + EXIT_IF(g_window_ctx == nullptr); + if (extent.width == 0 || extent.height == 0 || format == VK_FORMAT_UNDEFINED) { + EXIT("unsupported prepared frame, extent=%ux%u format=%d\n", extent.width, extent.height, + static_cast(format)); + } + + auto* ctx = &g_window_ctx->graphic_ctx; + auto& dst = frame->image; + const bool compatible = dst.image != nullptr && dst.extent.width == extent.width && + dst.extent.height == extent.height && dst.format == format; + if (compatible) { + return; + } + if (dst.image != nullptr) { + VulkanDeleteImage(ctx, &dst, &dst.memory); + dst = VulkanImage(VulkanImageType::Unknown); + } + + dst.extent = extent; + dst.format = format; + dst.layers = 1; + dst.mip_levels = 1; + dst.layout = VK_IMAGE_LAYOUT_UNDEFINED; + dst.memory.property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + + VkImageCreateInfo create {}; + create.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + create.imageType = VK_IMAGE_TYPE_2D; + create.extent = {dst.extent.width, dst.extent.height, 1}; + create.mipLevels = 1; + create.arrayLayers = 1; + create.format = dst.format; + create.tiling = VK_IMAGE_TILING_OPTIMAL; + create.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + create.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + create.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + create.samples = VK_SAMPLE_COUNT_1_BIT; + if (!VulkanCreateImage(ctx, &create, &dst, &dst.memory)) { + EXIT("failed to allocate prepared presentation image, extent=%ux%u format=%d\n", + dst.extent.width, dst.extent.height, static_cast(dst.format)); + } +} + VulkanSwapchain::~VulkanSwapchain() = default; [[maybe_unused]] static VkSwapchainKHR VulkanCreateSwapchainInternal( @@ -189,6 +317,7 @@ VulkanSwapchain* VulkanCreateSwapchain(GraphicContext* ctx, uint32_t image_count &s->render_complete_semaphores[i]); EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS); } + GetPreparedFramePool()->EnsureCapacity(s->swapchain_images_count, s->swapchain_format); return swapchain_owner.release(); } @@ -201,8 +330,6 @@ static void VulkanDeleteSwapchain(GraphicContext* ctx, VulkanSwapchain* s) { VulkanDeviceWaitIdle(ctx); - s->present_command_buffers.clear(); - if (s->image_acquired_semaphores != nullptr) { for (uint32_t i = 0; i < s->swapchain_images_count; i++) { if (s->image_acquired_semaphores[i] != nullptr) { @@ -256,31 +383,56 @@ static void VulkanRecreateSwapchain() { g_window_ctx->swapchain = VulkanCreateSwapchain(&g_window_ctx->graphic_ctx, 2); } -static CommandBuffer* WindowGetPresentCommandBuffer(VulkanSwapchain* swapchain, - uint32_t present_frame) { - EXIT_IF(swapchain == nullptr); - EXIT_IF(swapchain->swapchain_images_count == 0); - EXIT_IF(present_frame >= swapchain->swapchain_images_count); - - if (swapchain->present_command_buffers.empty()) { - swapchain->present_command_buffers.resize(swapchain->swapchain_images_count); +static void ValidatePreparedCommand(CommandBuffer* buffer) { + if (buffer == nullptr || buffer->IsInvalid() || buffer->GetQueue() != GraphicContext::QUEUE_GFX) { + EXIT("prepared frames must be recorded on the graphics queue\n"); } - EXIT_IF(swapchain->present_command_buffers.size() != swapchain->swapchain_images_count); - - auto& buffer = swapchain->present_command_buffers[present_frame]; - if (buffer == nullptr) { - buffer = std::make_unique(GraphicContext::QUEUE_GFX); - } - - return buffer.get(); + EXIT_IF(g_render_ctx == nullptr); } -void WindowDrawBuffer(VideoOutVulkanImage* image) { +PreparedFrame* WindowPrepareFrame(CommandBuffer* buffer, VideoOutVulkanImage* image) { + KYTY_PROFILER_FUNCTION(); + ValidatePreparedCommand(buffer); + if (image->format == VK_FORMAT_UNDEFINED) { + EXIT("unsupported presentation source, image=%p\n", static_cast(image)); + } + + auto* frame = GetPreparedFramePool()->Acquire(); + Common::LockGuard render_lock(g_render_ctx->GetMutex()); + g_render_ctx->GetTextureCache()->RefreshVideoOut(image); + ConfigurePreparedFrame(frame, image->extent, image->format); + const std::array copies {ImageImageCopy { + .src_image = image, .width = image->extent.width, .height = image->extent.height}}; + UtilImageToImage(buffer, copies, &frame->image, + static_cast(VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)); + return frame; +} + +PreparedFrame* WindowPrepareBlankFrame(CommandBuffer* buffer, uint32_t width, uint32_t height, + bool opaque) { + KYTY_PROFILER_FUNCTION(); + ValidatePreparedCommand(buffer); + auto* pool = GetPreparedFramePool(); + auto format = pool->GetFormat(); + auto* frame = pool->Acquire(); + Common::LockGuard render_lock(g_render_ctx->GetMutex()); + ConfigurePreparedFrame(frame, {width, height}, format); + const VkClearColorValue clear {{0.0f, 0.0f, 0.0f, opaque ? 1.0f : 0.0f}}; + UtilClearColorImage(buffer, &frame->image, clear); + return frame; +} + +void WindowPresentFrame(PreparedFrame* frame) { KYTY_PROFILER_FUNCTION(); - EXIT_IF(image == nullptr); + EXIT_IF(frame == nullptr); EXIT_IF(g_window_ctx == nullptr); EXIT_IF(g_window_ctx->swapchain == nullptr); + struct ReleaseScope { + PreparedFrame* frame; + ~ReleaseScope() { GetPreparedFramePool()->Release(frame); } + }; + ReleaseScope release {frame}; if (g_window_ctx->window_hidden) { WindowUpdateIcon(); @@ -296,9 +448,6 @@ void WindowDrawBuffer(VideoOutVulkanImage* image) { const auto present_frame = swapchain->present_frame; EXIT_IF(present_frame >= swapchain->swapchain_images_count); - auto& buffer = *WindowGetPresentCommandBuffer(swapchain, present_frame); - buffer.WaitForFenceAndReset(); - swapchain->current_index = static_cast(-1); auto result = vkAcquireNextImageKHR( @@ -315,14 +464,17 @@ void WindowDrawBuffer(VideoOutVulkanImage* image) { default: EXIT("vkAcquireNextImageKHR failed: %s\n", string_VkResult(result)); } EXIT_NOT_IMPLEMENTED(swapchain->current_index == static_cast(-1)); - - EXIT_NOT_IMPLEMENTED(buffer.IsInvalid()); + if (frame->present_commands == nullptr) { + frame->present_commands = std::make_unique(GraphicContext::QUEUE_GFX); + } + frame->present_commands->WaitForFenceAndReset(); + auto& buffer = *frame->present_commands; auto* vk_buffer = buffer.GetPool()->buffers[buffer.GetIndex()]; buffer.Begin(); - UtilBlitImage(&buffer, image, swapchain); + UtilBlitPreparedImage(&buffer, &frame->image, swapchain); VkImageMemoryBarrier pre_present_barrier {}; pre_present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;