mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[MG_Backend, MG_Remote] (P7): draft B4 aggregate waits and staged extent reset
This commit is contained in:
@@ -307,8 +307,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
// The slot's fence has been waited: every command buffer this slot
|
// Under disaggregation VulkanRenderer waits every registered fence
|
||||||
// submitted (including mid-frame flushes) has finished executing.
|
// through this slot's last submit before calling here. That aggregate
|
||||||
|
// proof covers mid-frame pooled-fence flushes too; the slot fence alone
|
||||||
|
// would not. Initialize's first acquire has no prior submissions.
|
||||||
FreeRetiredCommandBuffers(frame);
|
FreeRetiredCommandBuffers(frame);
|
||||||
|
|
||||||
result = vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
result = vkAcquireNextImageKHR(device, swapchain, timeout, frame.imageAvailableSemaphore, acquireFence,
|
||||||
|
|||||||
@@ -808,8 +808,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// Nothing bumps the slice epoch for those other resources, so freeing the buffer
|
// Nothing bumps the slice epoch for those other resources, so freeing the buffer
|
||||||
// here left the streamed memo handing a destroyed VkBuffer to vkCmdBindIndexBuffer
|
// here left the streamed memo handing a destroyed VkBuffer to vkCmdBindIndexBuffer
|
||||||
// (llvmpipe then faulted inside the draw; the Create/Flywheel indirect retrace died
|
// (llvmpipe then faulted inside the draw; the Create/Flywheel indirect retrace died
|
||||||
// exactly this way). Mid-frame drains do not advance m_frameSerial, so they must not
|
// exactly this way). Most mid-frame drains do not advance m_frameSerial; every
|
||||||
// free arena storage either: the arena's own ResetFrame/BeginFrame is the point where
|
// eighth does, but only through BeginFrame after the arena/memo boundary work.
|
||||||
|
// The drain's per-resource sweep must not free arena storage: ResetFrame/BeginFrame is where
|
||||||
// the slot's slices stop being reachable, and that is where these releases land.
|
// the slot's slices stop being reachable, and that is where these releases land.
|
||||||
for (Uint32 frameIndex = 0; frameIndex < m_deferredBufferReleases.size(); ++frameIndex) {
|
for (Uint32 frameIndex = 0; frameIndex < m_deferredBufferReleases.size(); ++frameIndex) {
|
||||||
CollectDeferredReleases(frameIndex);
|
CollectDeferredReleases(frameIndex);
|
||||||
|
|||||||
@@ -7063,6 +7063,10 @@ void main() {
|
|||||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||||
RewindWireDescriptorSetsIfDue();
|
RewindWireDescriptorSetsIfDue();
|
||||||
|
// The wire route returns before the monolith branch's draw-gated
|
||||||
|
// sweep. Dead wire texture/renderbuffer records otherwise live
|
||||||
|
// until the next frame boundary, which a long trace may not reach.
|
||||||
|
m_textureManager->CollectGarbage();
|
||||||
return SetupWireDraw(frame, mode, aspects, drawParams, pIndexBufferView);
|
return SetupWireDraw(frame, mode, aspects, drawParams, pIndexBufferView);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -7685,6 +7689,7 @@ void main() {
|
|||||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||||
RewindWireDescriptorSetsIfDue();
|
RewindWireDescriptorSetsIfDue();
|
||||||
|
m_textureManager->CollectGarbage();
|
||||||
DispatchWireCompute(numGroupsX, numGroupsY, numGroupsZ);
|
DispatchWireCompute(numGroupsX, numGroupsY, numGroupsZ);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -13527,12 +13532,32 @@ void main() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (m_device == VK_NULL_HANDLE || m_graphicsQueue == VK_NULL_HANDLE) {
|
if (m_device == VK_NULL_HANDLE || m_graphicsQueue == VK_NULL_HANDLE) {
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
return IsFrameSerialComplete(serial);
|
||||||
|
#else
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
// Every submission is recorded with the frame serial it was made under, so the wait can be
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
// narrowed to the first submission at or past the requested serial instead of draining the
|
// More than one submit can carry the same frame serial (mid-frame flush,
|
||||||
// whole queue. OnSubmitsCompletedUpTo calls NotifyFrameSerialComplete for every record it
|
// then Present). The completed floor is clamped below any serial still
|
||||||
// retires, so the completed-serial floor still advances correctly after one fence wait.
|
// held by an in-flight record; the first matching fence is not proof.
|
||||||
|
// OnSubmitsCompletedUpTo erases records, so copy fields and restart the
|
||||||
|
// search after every wait instead of continuing an invalidated iterator.
|
||||||
|
for (;;) {
|
||||||
|
const auto record = std::find_if(m_inFlightSubmits.begin(), m_inFlightSubmits.end(),
|
||||||
|
[serial](const auto& candidate) {
|
||||||
|
return candidate.frameSerial >= serial && candidate.fence != VK_NULL_HANDLE;
|
||||||
|
});
|
||||||
|
if (record == m_inFlightSubmits.end()) break;
|
||||||
|
const Uint64 submitIndex = record->submitIndex;
|
||||||
|
if (!WaitForSubmitsUpTo(submitIndex, UINT64_MAX))
|
||||||
|
break; // fall through to the queue drain below
|
||||||
|
TryDrainFrameTransients();
|
||||||
|
if (IsFrameSerialComplete(serial)) return true;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Pull build's historical one-fence wait stays byte-identical.
|
||||||
for (const auto& record : m_inFlightSubmits) {
|
for (const auto& record : m_inFlightSubmits) {
|
||||||
if (record.frameSerial < serial || record.fence == VK_NULL_HANDLE) {
|
if (record.frameSerial < serial || record.fence == VK_NULL_HANDLE) {
|
||||||
continue;
|
continue;
|
||||||
@@ -13546,6 +13571,7 @@ void main() {
|
|||||||
TryDrainFrameTransients();
|
TryDrainFrameTransients();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// No usable record - fall back to draining the graphics queue. This over-waits (bounded by
|
// No usable record - fall back to draining the graphics queue. This over-waits (bounded by
|
||||||
// the in-flight frame count) but never deadlocks.
|
// the in-flight frame count) but never deadlocks.
|
||||||
@@ -13559,7 +13585,11 @@ void main() {
|
|||||||
// The queue was just drained; take the free frame-boundary drain when
|
// The queue was just drained; take the free frame-boundary drain when
|
||||||
// nothing is recorded (present-less timer-query loops). No-op otherwise.
|
// nothing is recorded (present-less timer-query loops). No-op otherwise.
|
||||||
TryDrainFrameTransients();
|
TryDrainFrameTransients();
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
return IsFrameSerialComplete(serial);
|
||||||
|
#else
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint64 VulkanRenderer::GetSyncPointSubmitIndex() const {
|
Uint64 VulkanRenderer::GetSyncPointSubmitIndex() const {
|
||||||
@@ -13573,7 +13603,12 @@ void main() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto& frame = m_frameContext.GetCurrent();
|
const auto& frame = m_frameContext.GetCurrent();
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
return frame.isCommandRecording || frame.hasCommandBufferRecorded ||
|
||||||
|
frame.isPreCommandRecording || frame.hasPreCommandBufferRecorded;
|
||||||
|
#else
|
||||||
return frame.isCommandRecording || frame.hasCommandBufferRecorded;
|
return frame.isCommandRecording || frame.hasCommandBufferRecorded;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
@@ -13639,6 +13674,29 @@ void main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
Bool VulkanRenderer::WaitForSubmitsUpTo(Uint64 submitIndex, Uint64 timeoutNs) {
|
||||||
|
if (submitIndex <= m_completedSubmitCounter) return true;
|
||||||
|
if (submitIndex > m_submitCounter || m_device == VK_NULL_HANDLE) return false;
|
||||||
|
Vector<VkFence> fences;
|
||||||
|
for (const auto& record : m_inFlightSubmits) {
|
||||||
|
if (record.submitIndex > submitIndex) break;
|
||||||
|
if (record.fence == VK_NULL_HANDLE) return false;
|
||||||
|
fences.push_back(record.fence);
|
||||||
|
}
|
||||||
|
if (fences.empty()) return false;
|
||||||
|
const VkResult result = vkWaitForFences(m_device, static_cast<Uint32>(fences.size()),
|
||||||
|
fences.data(), VK_TRUE, timeoutNs);
|
||||||
|
if (result == VK_SUCCESS) {
|
||||||
|
OnSubmitsCompletedUpTo(submitIndex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (result != VK_TIMEOUT)
|
||||||
|
MGLOG_E_ONCE("WaitForSubmitsUpTo: vkWaitForFences returned %d", result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void VulkanRenderer::OnSubmitsCompletedUpTo(Uint64 submitIndex) {
|
void VulkanRenderer::OnSubmitsCompletedUpTo(Uint64 submitIndex) {
|
||||||
m_completedSubmitCounter = std::max(m_completedSubmitCounter, submitIndex);
|
m_completedSubmitCounter = std::max(m_completedSubmitCounter, submitIndex);
|
||||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
@@ -13945,7 +14003,12 @@ void main() {
|
|||||||
// pooled fences and mid-frame command buffers.
|
// pooled fences and mid-frame command buffers.
|
||||||
RefreshCompletedSubmits();
|
RefreshCompletedSubmits();
|
||||||
auto& frame = m_frameContext.GetCurrent();
|
auto& frame = m_frameContext.GetCurrent();
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
if (!frame.isCommandRecording && !frame.hasCommandBufferRecorded &&
|
||||||
|
!frame.isPreCommandRecording && !frame.hasPreCommandBufferRecorded) {
|
||||||
|
#else
|
||||||
if (!frame.isCommandRecording && !frame.hasCommandBufferRecorded) {
|
if (!frame.isCommandRecording && !frame.hasCommandBufferRecorded) {
|
||||||
|
#endif
|
||||||
// GL flush semantics still demand batched texture uploads start
|
// GL flush semantics still demand batched texture uploads start
|
||||||
// executing in finite time even when no draw was recorded.
|
// executing in finite time even when no draw was recorded.
|
||||||
if (m_textureManager) {
|
if (m_textureManager) {
|
||||||
@@ -13988,9 +14051,15 @@ void main() {
|
|||||||
const VkResult retireResult = m_frameContext.RetireCurrentCommandBuffer(submittingPreCommandBuffer);
|
const VkResult retireResult = m_frameContext.RetireCurrentCommandBuffer(submittingPreCommandBuffer);
|
||||||
if (retireResult != VK_SUCCESS) {
|
if (retireResult != VK_SUCCESS) {
|
||||||
MGLOG_E_ONCE("FlushPendingCommands: RetireCurrentCommandBuffer returned %d; draining submission", retireResult);
|
MGLOG_E_ONCE("FlushPendingCommands: RetireCurrentCommandBuffer returned %d; draining submission", retireResult);
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
if (WaitForSubmitsUpTo(m_submitCounter, UINT64_MAX)) {
|
||||||
|
// Every registered fence through this submission was waited.
|
||||||
|
} else if (vkQueueWaitIdle(m_graphicsQueue) == VK_SUCCESS) {
|
||||||
|
#else
|
||||||
if (vkWaitForFences(m_device, 1, &fence, VK_TRUE, UINT64_MAX) == VK_SUCCESS) {
|
if (vkWaitForFences(m_device, 1, &fence, VK_TRUE, UINT64_MAX) == VK_SUCCESS) {
|
||||||
OnSubmitsCompletedUpTo(m_submitCounter);
|
OnSubmitsCompletedUpTo(m_submitCounter);
|
||||||
} else if (vkQueueWaitIdle(m_graphicsQueue) == VK_SUCCESS) {
|
} else if (vkQueueWaitIdle(m_graphicsQueue) == VK_SUCCESS) {
|
||||||
|
#endif
|
||||||
m_bufferManager.NotifyDeviceIdle();
|
m_bufferManager.NotifyDeviceIdle();
|
||||||
OnSubmitsCompletedUpTo(m_submitCounter);
|
OnSubmitsCompletedUpTo(m_submitCounter);
|
||||||
} else {
|
} else {
|
||||||
@@ -14028,6 +14097,12 @@ void main() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
if (!WaitForSubmitsUpTo(submitIndex, timeoutNs)) return false;
|
||||||
|
// A blocking wait can drain a present-less loop's frame transients.
|
||||||
|
TryDrainFrameTransients();
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
for (const auto& record : m_inFlightSubmits) {
|
for (const auto& record : m_inFlightSubmits) {
|
||||||
if (record.submitIndex >= submitIndex) {
|
if (record.submitIndex >= submitIndex) {
|
||||||
const VkResult result = vkWaitForFences(m_device, 1, &record.fence, VK_TRUE, timeoutNs);
|
const VkResult result = vkWaitForFences(m_device, 1, &record.fence, VK_TRUE, timeoutNs);
|
||||||
@@ -14048,6 +14123,7 @@ void main() {
|
|||||||
// No in-flight record at or beyond the index: it was already observed
|
// No in-flight record at or beyond the index: it was already observed
|
||||||
// complete via a fence wait on a later submission.
|
// complete via a fence wait on a later submission.
|
||||||
return true;
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderer::OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) {
|
void VulkanRenderer::OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) {
|
||||||
@@ -14196,6 +14272,15 @@ void main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_presentSuspended = false;
|
m_presentSuspended = false;
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
// A slot's Present fence alone does not prove its older pooled
|
||||||
|
// flushes retired. Wait the full prefix before FrameContext may
|
||||||
|
// free its retired command buffers and reset the slot fence.
|
||||||
|
const Uint64 slotSubmit = m_frameContext.GetCurrent().lastSubmitIndex;
|
||||||
|
if (slotSubmit > m_completedSubmitCounter &&
|
||||||
|
!WaitForSubmitsUpTo(slotSubmit, UINT64_MAX))
|
||||||
|
MagmaWireFatal("deferred-acquire-submit-wait");
|
||||||
|
#endif
|
||||||
const VkResult acquireResult =
|
const VkResult acquireResult =
|
||||||
m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired);
|
m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired);
|
||||||
if (acquireResult == VK_SUBOPTIMAL_KHR) {
|
if (acquireResult == VK_SUBOPTIMAL_KHR) {
|
||||||
@@ -14326,6 +14411,16 @@ void main() {
|
|||||||
// 3) Advance frame slot.
|
// 3) Advance frame slot.
|
||||||
m_frameContext.AdvanceToNext();
|
m_frameContext.AdvanceToNext();
|
||||||
|
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
// FrameContext waits then resets the slot fence and frees retired
|
||||||
|
// command buffers. A later Present fence is not aggregate proof for
|
||||||
|
// earlier pooled flushes, so wait every registered submission in this
|
||||||
|
// slot's prefix BEFORE the reset/free happens.
|
||||||
|
const Uint64 slotSubmit = m_frameContext.GetCurrent().lastSubmitIndex;
|
||||||
|
if (slotSubmit > m_completedSubmitCounter &&
|
||||||
|
!WaitForSubmitsUpTo(slotSubmit, UINT64_MAX))
|
||||||
|
MagmaWireFatal("present-slot-submit-wait");
|
||||||
|
#endif
|
||||||
// 4) Wait/reset/acquire for next frame.
|
// 4) Wait/reset/acquire for next frame.
|
||||||
result = m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired);
|
result = m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired);
|
||||||
if (result == VK_SUBOPTIMAL_KHR) {
|
if (result == VK_SUBOPTIMAL_KHR) {
|
||||||
@@ -14350,10 +14445,9 @@ void main() {
|
|||||||
m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired);
|
m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired);
|
||||||
}
|
}
|
||||||
VK_VERIFY(result, "Present, vkAcquireNextImageKHR");
|
VK_VERIFY(result, "Present, vkAcquireNextImageKHR");
|
||||||
// The acquired slot's fence has been waited: its last submission
|
// Pull keeps its historical slot-fence inference. In a disaggregated
|
||||||
// (and, in queue order, everything before it) is complete. The frame
|
// build the aggregate wait above already retired the registered prefix
|
||||||
// serials those submissions carried advance the buffer-manager floor
|
// before FrameContext reset the slot fence; this repeat is idempotent.
|
||||||
// inside OnSubmitsCompletedUpTo.
|
|
||||||
OnSubmitsCompletedUpTo(m_frameContext.GetCurrent().lastSubmitIndex);
|
OnSubmitsCompletedUpTo(m_frameContext.GetCurrent().lastSubmitIndex);
|
||||||
CollectDeferredDepthMipmapCleanup(m_frameContext.GetCurrentFrameIndex());
|
CollectDeferredDepthMipmapCleanup(m_frameContext.GetCurrentFrameIndex());
|
||||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
|||||||
@@ -147,7 +147,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
VkBufferManager& GetWireBufferManager() { return m_bufferManager; }
|
VkBufferManager& GetWireBufferManager() { return m_bufferManager; }
|
||||||
Bool FlushWirePendingCommandsForTextureUpdate() {
|
Bool FlushWirePendingCommandsForTextureUpdate() {
|
||||||
return !HasPendingRecordedWork() || FlushPendingCommands();
|
// A new texture upload must not race graphics work that samples
|
||||||
|
// the old texels. No current recording does not imply no in-flight
|
||||||
|
// submission: finish the recorded work, then wait its whole prefix.
|
||||||
|
if (HasPendingRecordedWork() && !FlushPendingCommands()) return false;
|
||||||
|
return WaitForSubmitsUpTo(m_submitCounter, UINT64_MAX);
|
||||||
}
|
}
|
||||||
// P7 wave 2 package B3: the submission that will carry whatever is recorded NEXT.
|
// P7 wave 2 package B3: the submission that will carry whatever is recorded NEXT.
|
||||||
// RetireWireObjects (WireDraw.inc) already tags future objects with exactly this, and
|
// RetireWireObjects (WireDraw.inc) already tags future objects with exactly this, and
|
||||||
@@ -685,6 +689,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// was waited or the device was idled); drops their records and
|
// was waited or the device was idled); drops their records and
|
||||||
// recycles pooled fences.
|
// recycles pooled fences.
|
||||||
void OnSubmitsCompletedUpTo(Uint64 submitIndex);
|
void OnSubmitsCompletedUpTo(Uint64 submitIndex);
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
// A later fence alone cannot retire earlier submissions. Wait for the
|
||||||
|
// whole still-live prefix before advancing the completed submit floor.
|
||||||
|
Bool WaitForSubmitsUpTo(Uint64 submitIndex, Uint64 timeoutNs);
|
||||||
|
#endif
|
||||||
VkFence AcquirePooledSubmitFence();
|
VkFence AcquirePooledSubmitFence();
|
||||||
void DestroySubmitFencePool();
|
void DestroySubmitFencePool();
|
||||||
Bool HasPendingRecordedWork() const;
|
Bool HasPendingRecordedWork() const;
|
||||||
|
|||||||
@@ -236,6 +236,16 @@ namespace MobileGL::MG_Remote::Server {
|
|||||||
if (!m_copies) return nullptr;
|
if (!m_copies) return nullptr;
|
||||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
LevelShadow& shadow = m_shadows[key].Levels[PackLevel(uploadTarget, level)];
|
LevelShadow& shadow = m_shadows[key].Levels[PackLevel(uploadTarget, level)];
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
// A run can be the first piece of a redefined level. Old coverage
|
||||||
|
// belongs to the previous coordinate system even when the new run
|
||||||
|
// happens to touch its old range; retaining it invents bytes that
|
||||||
|
// the current definition never supplied.
|
||||||
|
if (!shadow.Defined || shadow.Extent != extent) {
|
||||||
|
shadow.Bytes.clear();
|
||||||
|
shadow.Covered.clear();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
shadow.Extent = extent;
|
shadow.Extent = extent;
|
||||||
shadow.Defined = true;
|
shadow.Defined = true;
|
||||||
shadow.GpuDirty = false;
|
shadow.GpuDirty = false;
|
||||||
|
|||||||
@@ -307,6 +307,27 @@ TEST(StagedTextureStoreTest, StageChunkRunsAssembleTheLevelImageAndAGapIsNotCove
|
|||||||
EXPECT_EQ(store.LevelByteSize(key, kTex2DTarget, 0), 0u);
|
EXPECT_EQ(store.LevelByteSize(key, kTex2DTarget, 0), 0u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||||
|
TEST(StagedTextureStoreTest, AdoptRunExtentMoveDoesNotInheritOldCoverage) {
|
||||||
|
Server::StagedTextureStore store(/*copies=*/true);
|
||||||
|
const Uint64 key = Server::StagedTextureStore::KeyForHandle(TestHandle(15, 1));
|
||||||
|
Vector<Uint8> oldRun(16, 0x44);
|
||||||
|
Vector<Uint8> newRun(16, 0x99);
|
||||||
|
store.AdoptRun(key, kTex2DTarget, 0, IntVec3{4, 4, 1}, 0, oldRun.data(), oldRun.size());
|
||||||
|
ASSERT_TRUE(store.IsCovered(key, kTex2DTarget, 0));
|
||||||
|
store.AdoptRun(key, kTex2DTarget, 0, IntVec3{8, 4, 1}, 16, newRun.data(), newRun.size());
|
||||||
|
EXPECT_FALSE(store.IsCovered(key, kTex2DTarget, 0))
|
||||||
|
<< "the old level's [0,16) run was mistaken for bytes of the new extent";
|
||||||
|
EXPECT_EQ(store.LevelCoveredRunCount(key, kTex2DTarget, 0), 1u);
|
||||||
|
Vector<Uint8> newLower(16, 0xAA);
|
||||||
|
store.AdoptRun(key, kTex2DTarget, 0, IntVec3{8, 4, 1}, 0, newLower.data(), newLower.size());
|
||||||
|
const Uint8* base = store.RequireLevelBytes(key, kTex2DTarget, 0, "unit_extent_move");
|
||||||
|
ASSERT_NE(base, nullptr);
|
||||||
|
EXPECT_EQ(base[0], 0xAAu);
|
||||||
|
EXPECT_EQ(base[16], 0x99u);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// WHICH OF THE TWO STAGED-RUN SHAPES A RECORD CARRIES, read off the run's own LENGTH because the
|
// WHICH OF THE TWO STAGED-RUN SHAPES A RECORD CARRIES, read off the run's own LENGTH because the
|
||||||
// wire layout is frozen and carries no field for it. The whole-level run (the shape this half has
|
// wire layout is frozen and carries no field for it. The whole-level run (the shape this half has
|
||||||
// always staged) is the level's complete shadow and begins at the level's first byte; a slab's run
|
// always staged) is the level's complete shadow and begins at the level's first byte; a slab's run
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# P7 B4:Magma 提交等待与长帧清理(草稿,未落地)
|
||||||
|
|
||||||
|
基线:`0e16ca27`,独立 worktree `~/w7/p7-b4`。本包仍在实现/审查,以下是当前源码状态,不是集成树结论。
|
||||||
|
|
||||||
|
## 提交与 frame serial
|
||||||
|
|
||||||
|
`WaitForSubmitsUpTo(index, timeout)` 收集 tracker 中所有 `submitIndex <= index` 的在飞 fence,以 `vkWaitForFences(..., VK_TRUE)` 等全部信号之后才推进完成计数。`WaitForSubmitIndex` 不再拿一条较晚 fence 代替整个前缀。`WaitForFrameSerial` 等待第一条匹配记录后重查地板;若同一 serial 仍被第二条提交持有,就从头重扫(不能在 `OnSubmitsCompletedUpTo` erase 后继续旧迭代器,也不再读 `record` 引用)。没有可用记录时才 queue idle,最终只按 `IsFrameSerialComplete` 答 true。
|
||||||
|
|
||||||
|
`Present` 在 `FrameContext::WaitAndAcquireNextImage` 重置槽 fence / 释放退休命令缓冲之前,先等该槽 `lastSubmitIndex` 的完整前缀;suspended 后首次 acquire 同样做。readback 原本已有全 fence 等待,保持;mid-frame flush 的退化重用分支改走聚合等待。`HasPendingRecordedWork` 在 disagg 构建下也计 pre-pass 录制,`FlushPendingCommands` 的空批判断对应扩展。这样 `TryDrainFrameTransients` 的 wire `CollectWireObjects(all=true)` 不会在 pre-pass 尚未提交时把未来标签视作空闲。pull 分支的可执行语句保持原样。
|
||||||
|
|
||||||
|
`FlushWirePendingCommandsForTextureUpdate` 在提交当前录制后等所有在飞 graphics 提交,才允许新的独立上传批修改旧 draw 可能采样的图像。这里可能有明显的 stall 代价,按规则只记录性能,不设门;需在长 trace 上实测。该修正还没有针对单 fence 错退的确定性负控,当前包不得以现有绿数直接落地。
|
||||||
|
|
||||||
|
## 长帧回收与 staged bytes
|
||||||
|
|
||||||
|
wire draw / dispatch 入口现在调用 `VkTextureManager::CollectGarbage`,使每 256 次的 `pruneWire` 能在一帧内执行。`StagedTextureStore::AdoptRun` 遇到 extent 变化时清旧 `Bytes` 与 `Covered`:单测先送旧 [0,16),再送新 extent 的 [16,32),不能把两种坐标系拼成已覆盖。禁用清理时该单测在旧范围误判覆盖处红,恢复后绿。
|
||||||
|
|
||||||
|
## 已验证/待做
|
||||||
|
|
||||||
|
完整 `MobileGL`、`MobileGLServer`、`MobileGLIntegrationTest` 与 `StagedTextureStoreTest` 构建通过;新 extent 单测绿,红一次按上述覆盖断言红;Magma split/spawn/tcp 为 101/80/71、full-split 539,全绿;OpenRA DirectVulkan inproc / spawn 都 ssim 1.000000、0 Fatal、0 `unsound-serial-complete`。原始日志 `~/w7/logs/p7b4/`、`~/w7/logs/p7-gate-b4a-retrace/`。
|
||||||
|
|
||||||
|
待做:聚合等待的可证伪负控;G1 / 普查 / 棘轮 / parity / 完整八车道 / verify 门;集成者对 pipe 源码复审;与 M3 的 `VulkanRenderer.cpp` 冲突合并;真机回归。未完成前不推送本包。
|
||||||
Reference in New Issue
Block a user