mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Magma: submit a frame's off-screen work ahead of the swapchain acquire wait.
This commit is contained in:
@@ -302,7 +302,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
auto& frame = GetCurrent();
|
||||
const VkResult result = vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, frame.imageAvailableSemaphore,
|
||||
VK_NULL_HANDLE, &outImageIndex);
|
||||
if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) frame.imageAvailableSemaphoreConsumed = false;
|
||||
if (result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) {
|
||||
frame.imageAvailableSemaphoreConsumed = false;
|
||||
frame.acquiredImageReferenced = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -333,6 +336,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
frame.imageAvailableSemaphoreConsumed = false;
|
||||
frame.acquiredImageReferenced = false;
|
||||
const VkResult resetResult = vkResetFences(device, 1, &frame.imageInFlightFence);
|
||||
// Hand the acquire's own code back so the caller can schedule a rebuild.
|
||||
return resetResult == VK_SUCCESS ? result : resetResult;
|
||||
@@ -476,6 +480,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
frame.hasCommandBufferRecorded = false;
|
||||
frame.isCommandRecording = false;
|
||||
frame.imageAvailableSemaphoreConsumed = false;
|
||||
frame.acquiredImageReferenced = false;
|
||||
return VK_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -494,5 +499,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
frame.isCommandRecording = false;
|
||||
frame.hasCommandBufferRecorded = false;
|
||||
frame.imageAvailableSemaphoreConsumed = false;
|
||||
frame.acquiredImageReferenced = false;
|
||||
}
|
||||
} // namespace MobileGL::MG_Backend::DirectVulkan
|
||||
|
||||
@@ -25,8 +25,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
virtual void OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) = 0;
|
||||
};
|
||||
|
||||
// The stages the acquire wait blocks. Every first access of an acquired swapchain image is
|
||||
// a layout transition out of PRESENT_SRC_KHR or UNDEFINED whose source scope includes these
|
||||
// stages (TransitionWireImage, TransitionToPresent), and that chain is what orders the
|
||||
// access after the presentation engine's release. Work at other stages, in this and later
|
||||
// submissions, does not wait for the release.
|
||||
static constexpr VkPipelineStageFlags kAcquireWaitStages =
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
|
||||
struct SubmitInfoPacket {
|
||||
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkPipelineStageFlags waitDstStageMask = kAcquireWaitStages;
|
||||
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
||||
VkSemaphore signalSemaphore = VK_NULL_HANDLE;
|
||||
// [0] = pre-pass command buffer (when recorded), then the frame
|
||||
@@ -67,6 +75,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool isPreCommandRecording = false;
|
||||
Bool hasPreCommandBufferRecorded = false;
|
||||
Bool imageAvailableSemaphoreConsumed = false;
|
||||
// The open recording references the acquired swapchain image. Only a submission that
|
||||
// does (or the present's) waits on imageAvailableSemaphore: the frame's work recorded
|
||||
// before its first use of the image goes to the queue without the wait, so it never
|
||||
// queues behind the release of an image the display still scans out.
|
||||
Bool acquiredImageReferenced = false;
|
||||
// Command buffers submitted mid-frame (FlushPendingCommands),
|
||||
// appended in submit order; freed once their submission is known
|
||||
// complete (fence wait or completion poll).
|
||||
|
||||
@@ -3601,6 +3601,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// split server records every draw its client sends into the same frame command buffer, so
|
||||
// the same loading frame grows it the same way there.
|
||||
SplitOversizedRecording();
|
||||
// A draw into the window: the frame's work so far goes out now, ahead of the acquire wait,
|
||||
// while nothing of this draw is recorded yet (its descriptor binds must land in the
|
||||
// recording the draw does). ResolveWireImage would split later, mid-setup.
|
||||
if (const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer(); fbo != nullptr && fbo->IsDefault)
|
||||
SubmitAheadOfAcquiredImage();
|
||||
if (!RewindWireDescriptorSetsIfDue()) return false;
|
||||
// The wire route returns before the monolith branch's draw-gated
|
||||
// sweep. Dead wire texture/renderbuffer records otherwise live
|
||||
@@ -6116,7 +6121,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
m_completedSubmitCounter = m_submitCounter;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::SubmitPendingCommandBuffer(FrameContext::FrameData& frame, VkFence fence, Bool pooledFence) {
|
||||
Bool VulkanRenderer::SubmitPendingCommandBuffer(FrameContext::FrameData& frame, VkFence fence, Bool pooledFence,
|
||||
Bool waitAcquire) {
|
||||
RetireWireDrawPass();
|
||||
// Batched texture uploads must reach the queue before the frame's
|
||||
// commands: the recording being submitted may sample images whose
|
||||
@@ -6124,10 +6130,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (m_textureManager) {
|
||||
m_textureManager->FlushPendingUploads();
|
||||
}
|
||||
VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
||||
VkPipelineStageFlags waitDstStageMask = FrameContext::kAcquireWaitStages;
|
||||
VkSemaphore waitSemaphore = frame.imageAvailableSemaphore;
|
||||
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||
if (!frame.imageAvailableSemaphoreConsumed) {
|
||||
// A recording that never touched the acquired image does not wait for its release: on a
|
||||
// queue that runs submissions in order, that wait would hold this work, and everything
|
||||
// after it, until the display lets go of the image (SubmitAheadOfAcquiredImage).
|
||||
const Bool waitsAcquire =
|
||||
!frame.imageAvailableSemaphoreConsumed && (frame.acquiredImageReferenced || waitAcquire);
|
||||
if (waitsAcquire) {
|
||||
submitInfo.waitSemaphoreCount = 1;
|
||||
submitInfo.pWaitSemaphores = &waitSemaphore;
|
||||
submitInfo.pWaitDstStageMask = &waitDstStageMask;
|
||||
@@ -6161,7 +6172,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
MGLOG_E_ONCE("SubmitPendingCommandBuffer: vkQueueSubmit returned %d", result);
|
||||
return false;
|
||||
}
|
||||
frame.imageAvailableSemaphoreConsumed = true;
|
||||
if (waitsAcquire) frame.imageAvailableSemaphoreConsumed = true;
|
||||
frame.hasCommandBufferRecorded = false;
|
||||
frame.hasPreCommandBufferRecorded = false;
|
||||
RegisterSubmit(fence, pooledFence);
|
||||
@@ -6239,6 +6250,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanRenderer::SubmitAheadOfAcquiredImage() {
|
||||
if (m_frameContext.GetFrameCount() == 0) return;
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
if (frame.imageAvailableSemaphoreConsumed || frame.acquiredImageReferenced) return;
|
||||
if (m_swapchainObject.GetHandle() == VK_NULL_HANDLE || m_presentSuspended) return;
|
||||
// The frame's work so far renders off screen. Submitted together with the window pass it
|
||||
// would wait behind the acquire semaphore, which signals only when the display releases
|
||||
// the image - about every other frame that is the image it has just stopped scanning out,
|
||||
// so the whole frame's GPU work started after the next display commit. Submitted now it
|
||||
// runs while the display still holds the image. A failed flush leaves the work recorded,
|
||||
// and it goes out later with the wait, as before. Inside a draw's preparation nothing is
|
||||
// submitted (the draw may already hold state of the open recording); the image is only
|
||||
// marked, so the submission that carries the use still waits.
|
||||
if (m_wirePreparationDepth == 0 && HasPendingRecordedWork() && FlushPendingCommands())
|
||||
MGLOG_I_ONCE("DirectVulkan: a frame's off-screen work goes to the queue ahead of its window pass; only "
|
||||
"the submission that uses the swapchain image waits for its acquire");
|
||||
frame.acquiredImageReferenced = true;
|
||||
}
|
||||
|
||||
void VulkanRenderer::NoteDeviceLoss(VkResult result, const char* where) {
|
||||
if (result != VK_ERROR_DEVICE_LOST || m_deviceLost) return;
|
||||
m_deviceLost = true;
|
||||
@@ -6555,6 +6585,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(),
|
||||
"Present, acquired image index out of range");
|
||||
// A frame that never drew into the window still presents its image: what it recorded
|
||||
// goes out first, unwaited, and the present's own submission waits for the release.
|
||||
SubmitAheadOfAcquiredImage();
|
||||
// A real presented frame is the canonical aging cadence; mid-frame drains
|
||||
// count against this and only age when presents stop coming.
|
||||
m_drainsSinceLastPresent = 0;
|
||||
@@ -9020,7 +9053,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
if (m_swapchainObject.GetHandle() != VK_NULL_HANDLE && !m_presentSuspended &&
|
||||
!frame.imageAvailableSemaphoreConsumed) {
|
||||
const VkFence fence = AcquirePooledSubmitFence();
|
||||
if (fence == VK_NULL_HANDLE || !SubmitPendingCommandBuffer(frame, fence, /*pooledFence=*/true)) {
|
||||
if (fence == VK_NULL_HANDLE ||
|
||||
!SubmitPendingCommandBuffer(frame, fence, /*pooledFence=*/true, /*waitAcquire=*/true)) {
|
||||
MGLOG_E("DirectVulkan: a parked surface's pending acquire could not be waited out");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,12 +976,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// vkWaitForFences inline before returning.
|
||||
void RegisterSubmit(VkFence fence, Bool pooledFence);
|
||||
// Builds the submit packet for the frame's pending command buffer
|
||||
// (consuming the acquire semaphore on the slot's first submission),
|
||||
// (consuming the acquire semaphore on the first submission that
|
||||
// references the acquired image, or on any when `waitAcquire`),
|
||||
// submits it with `fence`, and registers the submission. On failure
|
||||
// the frame state is left untouched. Shared by the mid-frame flush
|
||||
// and the readback path so the semaphore-consumption invariant lives
|
||||
// in one place.
|
||||
Bool SubmitPendingCommandBuffer(FrameContext::FrameData& frame, VkFence fence, Bool pooledFence);
|
||||
Bool SubmitPendingCommandBuffer(FrameContext::FrameData& frame, VkFence fence, Bool pooledFence,
|
||||
Bool waitAcquire = false);
|
||||
// Called before the frame first references the acquired swapchain image: submits what is
|
||||
// recorded so far WITHOUT the acquire wait, then marks the recording as referencing the
|
||||
// image, so only the window pass waits for the presentation engine's release.
|
||||
void SubmitAheadOfAcquiredImage();
|
||||
// Polls in-flight submission fences (prefix order) and advances the
|
||||
// completed counter past every fence observed signaled.
|
||||
void RefreshCompletedSubmits();
|
||||
|
||||
@@ -133,6 +133,9 @@
|
||||
out.swapchainImageIndex = isWriteTarget ? DefaultFramebufferWriteIndex(true)
|
||||
: DefaultFramebufferReadIndex(true);
|
||||
if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) {
|
||||
// Every use of the acquired image is reached through here, ahead of its commands:
|
||||
// the work recorded before it goes out without the acquire wait.
|
||||
if (out.swapchainImageIndex == m_imageIndexAcquired) SubmitAheadOfAcquiredImage();
|
||||
out.image = m_swapchainObject.GetImage(out.swapchainImageIndex);
|
||||
out.format = m_swapchainObject.GetSurfaceFormat().format;
|
||||
out.layout = m_swapchainObject.GetImageLayout(out.swapchainImageIndex);
|
||||
@@ -201,6 +204,12 @@
|
||||
VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
VkAccessFlags access = 0;
|
||||
GetImageTransitionSourceState(image.layout, stage, access);
|
||||
// A swapchain image leaves PRESENT_SRC_KHR (or UNDEFINED, new) only after it was acquired:
|
||||
// the transition is ordered after the presentation engine's release through the stages
|
||||
// the acquire wait blocks, which an empty (TOP_OF_PIPE) source scope would not chain to.
|
||||
if (image.isDefault && image.aspect == VK_IMAGE_ASPECT_COLOR_BIT &&
|
||||
(image.layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR || image.layout == VK_IMAGE_LAYOUT_UNDEFINED))
|
||||
stage |= FrameContext::kAcquireWaitStages;
|
||||
// One tracked layout per image: move the complete mip chain, never just the surface.
|
||||
VkImageAspectFlags barrierAspect = image.aspect;
|
||||
// Every packed format, D16_UNORM_S8_UINT included (g5-msrbo review round): the layout is
|
||||
@@ -565,6 +574,9 @@
|
||||
void VulkanRenderer::ClearWireFramebuffer(const MG_Pipe::MGPFramebufferState& fbo,
|
||||
const ClearAttachmentPayload& clearRequest, GLint drawbuffer) {
|
||||
if (MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) return;
|
||||
// A clear of the window: the frame's work so far goes out ahead of the acquire wait, before
|
||||
// any of the clear's paths (in-pass, deferred, standalone) records or resolves anything.
|
||||
if (fbo.IsDefault) SubmitAheadOfAcquiredImage();
|
||||
// P15 S0: what the draw pass can take is cleared inside it (ClearWireFramebufferInPass,
|
||||
// WireDraw.inc); only the buffers it hands back take the standalone pass below.
|
||||
// P15: in the open pass when it is this framebuffer's (the in-pass clear) or merely holds
|
||||
|
||||
Reference in New Issue
Block a user