mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
173 lines
9.6 KiB
C++
173 lines
9.6 KiB
C++
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
#pragma once
|
|
|
|
#include "../VkIncludes.h"
|
|
#include <Includes.h>
|
|
|
|
namespace MobileGL::MG_Backend::DirectVulkan {
|
|
class FrameContext {
|
|
public:
|
|
// Notified immediately after a frame command buffer begins recording
|
|
// (before any render pass has been begun); every BeginCommandRecording
|
|
// caller funnels through this single seam. Implemented by the renderer
|
|
// to prepare per-frame timer-query pools (vkCmdResetQueryPool must be
|
|
// recorded outside a render pass).
|
|
class IRecordingObserver {
|
|
public:
|
|
virtual ~IRecordingObserver() = default;
|
|
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 = kAcquireWaitStages;
|
|
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
|
VkSemaphore signalSemaphore = VK_NULL_HANDLE;
|
|
// [0] = pre-pass command buffer (when recorded), then the frame
|
|
// command buffer; submitInfo.pCommandBuffers points here.
|
|
VkCommandBuffer commandBuffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
|
|
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
};
|
|
|
|
struct PresentInfoPacket {
|
|
VkSemaphore waitSemaphore = VK_NULL_HANDLE;
|
|
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
|
Uint32 imageIndex = 0;
|
|
VkPresentInfoKHR presentInfo{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
|
};
|
|
|
|
// A command buffer submitted mid-frame (FlushPendingCommands), tagged
|
|
// with the submit-tracker index it was submitted under so it can be
|
|
// freed as soon as that submission is observed complete - without
|
|
// waiting for the slot's fence to be waited again (present-less flush
|
|
// loops never wait it).
|
|
struct RetiredCommandBuffer {
|
|
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
|
Uint64 submitIndex = 0;
|
|
};
|
|
|
|
struct FrameData {
|
|
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
|
|
// Pre-pass work stream: out-of-pass commands (deferred clear
|
|
// materialization, sampled-layout transitions) for resources the
|
|
// frame's recording has not touched yet. Submitted immediately
|
|
// BEFORE commandBuffer in the same vkQueueSubmit, so recording
|
|
// into it never has to split the frame's active render pass.
|
|
VkCommandBuffer preCommandBuffer = VK_NULL_HANDLE;
|
|
VkSemaphore imageAvailableSemaphore = VK_NULL_HANDLE;
|
|
VkFence imageInFlightFence = VK_NULL_HANDLE;
|
|
Bool isCommandRecording = false;
|
|
Bool hasCommandBufferRecorded = false;
|
|
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).
|
|
Vector<RetiredCommandBuffer> retiredCommandBuffers;
|
|
// Submit-tracker index of this slot's most recent queue submission
|
|
// (written by the renderer at submit time).
|
|
Uint64 lastSubmitIndex = 0;
|
|
};
|
|
|
|
VkResult Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount);
|
|
void Destroy(VkDevice device, VkCommandPool commandPool);
|
|
|
|
// Lifecycle functions
|
|
FrameData& GetCurrent();
|
|
const FrameData& GetCurrent() const;
|
|
Bool IsCommandRecording() const;
|
|
void AdvanceToNext();
|
|
VkCommandBuffer& BeginCommandRecording(VkCommandBufferUsageFlags flags = 0,
|
|
const VkCommandBufferInheritanceInfo* pInheritanceInfo = nullptr);
|
|
void EndCommandRecording();
|
|
// Lazily opens the pre-pass work stream (see FrameData::preCommandBuffer).
|
|
VkCommandBuffer BeginPreCommandRecording();
|
|
// Closes the pre stream if open, marking it for submission ahead of the
|
|
// frame command buffer. Safe to call when it never opened.
|
|
void EndPreCommandRecordingIfOpen();
|
|
// Drops an in-progress or recorded-but-unsubmitted pre stream (dropped
|
|
// frame recordings, swapchain recreation).
|
|
void AbandonPreCommandRecording();
|
|
VkResult InitializeSwapchainSemaphores(VkDevice device, Uint32 swapchainImageCount);
|
|
void DestroySwapchainSemaphores(VkDevice device);
|
|
Bool TransitionToPresent(VkImage image, VkImageLayout oldLayout,
|
|
VkImageLayout presentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
|
SubmitInfoPacket GetSubmitInfo(Bool shouldSubmitCommandBuffer, Uint32 swapchainImageIndex) const;
|
|
PresentInfoPacket GetPresentInfo(VkSwapchainKHR swapchain, Uint32 imageIndex) const;
|
|
// Acquires into the current slot's semaphore WITHOUT the slot-fence wait: for a surface
|
|
// brought up mid-frame, whose slot fence this frame has already waited and reset. The
|
|
// slot's semaphore must be unsignaled with no pending signal (consumed).
|
|
VkResult AcquireNextImageInCurrentSlot(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex);
|
|
// The per-swapchain-image render-finished semaphores belong to one swapchain; a renderer
|
|
// that parks a surface parks them with it.
|
|
void ExchangeSwapchainSemaphores(Vector<VkSemaphore>& other) { std::swap(m_swapchainImageRenderFinishedSemaphores, other); }
|
|
VkResult WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex,
|
|
Uint64 timeout = UINT64_MAX, VkFence acquireFence = VK_NULL_HANDLE);
|
|
|
|
// Parks the current (already ended and submitted) command buffer on the
|
|
// slot's retired list and installs a freshly allocated one, so recording
|
|
// can restart while the submitted buffer is still executing. Retired
|
|
// buffers are freed after the slot's fence is next waited, or as soon
|
|
// as their submission is observed complete.
|
|
VkResult RetireCurrentCommandBuffer(Bool retirePreCommandBuffer = false);
|
|
|
|
// Frees every retired command buffer whose tagged submission index is
|
|
// known complete. Driven by the renderer's submit tracker on completion
|
|
// events (fence waits and non-blocking polls), so present-less flush
|
|
// loops reclaim their buffers without any extra wait.
|
|
void FreeRetiredCommandBuffersCompletedUpTo(Uint64 completedSubmitIndex);
|
|
// Frees every slot's retired command buffers. Only valid when the
|
|
// caller has proven every queue submission complete.
|
|
void FreeAllRetiredCommandBuffers();
|
|
|
|
Uint32 GetCurrentFrameIndex() const;
|
|
Uint32 GetFrameCount() const;
|
|
|
|
// Observer may be null (no notifications). Not owned.
|
|
void SetRecordingObserver(IRecordingObserver* observer);
|
|
|
|
private:
|
|
void AssertValidFrameIndex(Uint32 frameIndex) const;
|
|
void AssertValidSwapchainImageIndex(Uint32 imageIndex) const;
|
|
|
|
VkResult CreateSyncObjectsForFrame(VkDevice device, Uint32 frameIndex,
|
|
const VkSemaphoreCreateInfo& semaphoreInfo,
|
|
const VkFenceCreateInfo& fenceInfo);
|
|
void DestroySyncObjectsForFrame(VkDevice device, Uint32 frameIndex);
|
|
void FreeRetiredCommandBuffers(FrameData& frame);
|
|
|
|
Vector<FrameData> m_frames;
|
|
Vector<VkSemaphore> m_swapchainImageRenderFinishedSemaphores;
|
|
Uint32 currentFrameIndex = 0;
|
|
IRecordingObserver* m_recordingObserver = nullptr;
|
|
// Stored at Initialize for retired-command-buffer management.
|
|
VkDevice m_device = VK_NULL_HANDLE;
|
|
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
|
// Completed command buffers kept for the next retirement instead of being freed and
|
|
// reallocated: the pool resets a buffer implicitly at its next vkBeginCommandBuffer.
|
|
Vector<VkCommandBuffer> m_spareCommandBuffers;
|
|
VkResult TakeCommandBuffer(VkCommandBuffer& out);
|
|
void RecycleCommandBuffer(VkCommandBuffer commandBuffer);
|
|
};
|
|
} // namespace MobileGL::MG_Backend::DirectVulkan
|