mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[Perf] (Magma): a wire draw that provably continues the open pass skips the attachment resolve and pass-key walk (rd12 monolith 63.2 -> 65.5, inproc 89.8 -> 92.6 fps).
This commit is contained in:
@@ -568,6 +568,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
Uint32 swapchainImageIndex = 0;
|
Uint32 swapchainImageIndex = 0;
|
||||||
Bool is3D = false;
|
Bool is3D = false;
|
||||||
MG_Pipe::MGPipeHandle storage = MG_Pipe::kMGPipeNullHandle;
|
MG_Pipe::MGPipeHandle storage = MG_Pipe::kMGPipeNullHandle;
|
||||||
|
// The storage's resource, for a non-default surface (null for the default framebuffer).
|
||||||
|
VkTextureManager::TextureResource* resource = nullptr;
|
||||||
};
|
};
|
||||||
// `isWriteTarget` selects which side of the default framebuffer this resolves: a write
|
// `isWriteTarget` selects which side of the default framebuffer this resolves: a write
|
||||||
// re-points it at the image Present() acquired, a read keeps the current one (see
|
// re-points it at the image Present() acquired, a read keeps the current one (see
|
||||||
@@ -834,6 +836,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// The current wire pass's attachments were marked GPU-written at this StagedTextureStore
|
// The current wire pass's attachments were marked GPU-written at this StagedTextureStore
|
||||||
// clear generation (SetupWireDraw); false whenever the pass changes.
|
// clear generation (SetupWireDraw); false whenever the pass changes.
|
||||||
Bool m_wirePassMarksValid = false;
|
Bool m_wirePassMarksValid = false;
|
||||||
|
// P14: THE OPEN PASS'S ATTACHMENTS, as the last full attachment walk resolved them. A draw
|
||||||
|
// into the same framebuffer record (FramebufferSerial moves on every framebuffer write,
|
||||||
|
// ContextSerial on every applier reset) while the pass is still open on this command buffer,
|
||||||
|
// with no texture image re-created or erased, and every attachment still in GENERAL with no
|
||||||
|
// pending upload, would resolve exactly these again and continue the pass: it skips both
|
||||||
|
// resolves and the pass key. Never for the default framebuffer, whose image index moves
|
||||||
|
// with each acquire.
|
||||||
|
struct WirePassMemo {
|
||||||
|
Bool valid = false;
|
||||||
|
Uint64 framebufferSerial = 0, contextSerial = 0;
|
||||||
|
MG_Pipe::MGPipeHandle drawFramebuffer = MG_Pipe::kMGPipeNullHandle;
|
||||||
|
Uint64 textureImageEpoch = 0, resourceEraseEpoch = 0;
|
||||||
|
Bool framebufferSrgb = false;
|
||||||
|
Uint64 passHash = 0;
|
||||||
|
struct Attachment {
|
||||||
|
VkTextureManager::TextureResource* resource = nullptr;
|
||||||
|
MG_Pipe::MGPipeHandle storage = MG_Pipe::kMGPipeNullHandle;
|
||||||
|
Bool renderbuffer = false;
|
||||||
|
};
|
||||||
|
Vector<Attachment> attachments;
|
||||||
|
Vector<WireDrawScratch::GpuWriteMark> marks;
|
||||||
|
};
|
||||||
|
WirePassMemo m_wirePassMemo;
|
||||||
|
Bool WirePassMemoHolds(const MG_Pipe::MGPFramebufferState& fbo, const FrameContext::FrameData& frame) const;
|
||||||
Uint64 m_wirePassMarksClearGeneration = 0;
|
Uint64 m_wirePassMarksClearGeneration = 0;
|
||||||
WireDrawScratch m_wireDrawScratch;
|
WireDrawScratch m_wireDrawScratch;
|
||||||
struct WireRetiredObjects {
|
struct WireRetiredObjects {
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
m_wireDrawPass.reset();
|
m_wireDrawPass.reset();
|
||||||
m_wireDrawPassCompatibilityId = 0;
|
m_wireDrawPassCompatibilityId = 0;
|
||||||
m_wirePassMarksValid = false;
|
m_wirePassMarksValid = false;
|
||||||
|
m_wirePassMemo.valid = false;
|
||||||
for (const auto view : m_wireDrawViews) vkDestroyImageView(m_device, view, nullptr);
|
for (const auto view : m_wireDrawViews) vkDestroyImageView(m_device, view, nullptr);
|
||||||
m_wireDrawViews.clear();
|
m_wireDrawViews.clear();
|
||||||
m_wireDrawPassKey = {};
|
m_wireDrawPassKey = {};
|
||||||
@@ -121,6 +122,7 @@
|
|||||||
void VulkanRenderer::RetireWireDrawPass() {
|
void VulkanRenderer::RetireWireDrawPass() {
|
||||||
m_wireDrawPassCompatibilityId = 0;
|
m_wireDrawPassCompatibilityId = 0;
|
||||||
m_wirePassMarksValid = false;
|
m_wirePassMarksValid = false;
|
||||||
|
m_wirePassMemo.valid = false;
|
||||||
if (!m_wireDrawPass) return;
|
if (!m_wireDrawPass) return;
|
||||||
auto& frame = m_frameContext.GetCurrent();
|
auto& frame = m_frameContext.GetCurrent();
|
||||||
if (frame.isCommandRecording && VkRenderPassManager::GetActiveRenderPass())
|
if (frame.isCommandRecording && VkRenderPassManager::GetActiveRenderPass())
|
||||||
@@ -202,6 +204,37 @@
|
|||||||
~WirePreparationScope() { --depth; }
|
~WirePreparationScope() { --depth; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Bool VulkanRenderer::WirePassMemoHolds(const MG_Pipe::MGPFramebufferState& fbo,
|
||||||
|
const FrameContext::FrameData& frame) const {
|
||||||
|
const auto& memo = m_wirePassMemo;
|
||||||
|
if (!memo.valid || fbo.IsDefault || !m_wireDrawPass || !frame.isCommandRecording) return false;
|
||||||
|
const auto* active = VkRenderPassManager::GetActiveRenderPass();
|
||||||
|
if (active == nullptr || active->commandBuffer != frame.commandBuffer || active->hash != m_wireDrawPass->hash ||
|
||||||
|
active->hash != memo.passHash)
|
||||||
|
return false;
|
||||||
|
const auto& st = MG_Pipe::MGPipeApplier();
|
||||||
|
if (memo.framebufferSerial != st.FramebufferSerial || memo.contextSerial != st.ContextSerial ||
|
||||||
|
!(memo.drawFramebuffer == st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)]) ||
|
||||||
|
memo.textureImageEpoch != m_textureManager->GetTextureImageEpoch() ||
|
||||||
|
memo.resourceEraseEpoch != m_textureManager->GetResourceEraseEpoch() ||
|
||||||
|
memo.framebufferSrgb != MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::FramebufferSrgb))
|
||||||
|
return false;
|
||||||
|
// The resources are good while the erase epoch holds. Each must still be what the walk
|
||||||
|
// left: in GENERAL and synced to its record's serial with nothing pending.
|
||||||
|
for (const auto& attachment : memo.attachments) {
|
||||||
|
const auto* resource = attachment.resource;
|
||||||
|
if (resource == nullptr || resource->layout != VK_IMAGE_LAYOUT_GENERAL) return false;
|
||||||
|
const auto& records = attachment.renderbuffer ? st.RenderbufferResources : st.TextureResources;
|
||||||
|
const auto handle = attachment.storage;
|
||||||
|
if (handle.Slot >= records.size()) return false;
|
||||||
|
const auto& record = records[handle.Slot];
|
||||||
|
if (!record.Live || record.Gen != handle.Gen || record.Serial != resource->syncedWireSerial ||
|
||||||
|
!record.PendingUploads.empty())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Bool VulkanRenderer::SetupWireDraw(FrameContext::FrameData& frame, GLenum mode,
|
Bool VulkanRenderer::SetupWireDraw(FrameContext::FrameData& frame, GLenum mode,
|
||||||
Flags<DrawSetupAspect> aspects, const DrawCmdParam& drawParams, const IndexBufferView* indices) {
|
Flags<DrawSetupAspect> aspects, const DrawCmdParam& drawParams, const IndexBufferView* indices) {
|
||||||
auto& state = MG_Pipe::MGPipeApplier();
|
auto& state = MG_Pipe::MGPipeApplier();
|
||||||
@@ -259,15 +292,19 @@
|
|||||||
// (WireSharedImage.inc).
|
// (WireSharedImage.inc).
|
||||||
AcquireNotedSharedImages();
|
AcquireNotedSharedImages();
|
||||||
// Lazy image uploads/promotions can submit the old recording. Resolve
|
// Lazy image uploads/promotions can submit the old recording. Resolve
|
||||||
// ALL attachments before descriptor binding captures the current cmd.
|
// ALL attachments before descriptor binding captures the current cmd - unless the open
|
||||||
for (Uint32 i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) {
|
// pass's attachments are provably the ones this draw would resolve (WirePassMemo).
|
||||||
const Int32 slot = fbo->DrawBuffers[i];
|
Bool passContinues = WirePassMemoHolds(*fbo, frame);
|
||||||
if (slot >= 0) (void)ResolveWireImage(*fbo, fbo->Color[slot], VK_IMAGE_ASPECT_COLOR_BIT);
|
if (!passContinues) {
|
||||||
|
for (Uint32 i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) {
|
||||||
|
const Int32 slot = fbo->DrawBuffers[i];
|
||||||
|
if (slot >= 0) (void)ResolveWireImage(*fbo, fbo->Color[slot], VK_IMAGE_ASPECT_COLOR_BIT);
|
||||||
|
}
|
||||||
|
if (fbo->IsDefault || fbo->Depth.Kind != MG_Pipe::kMGPipeSurfaceKindNone)
|
||||||
|
(void)ResolveWireImage(*fbo, fbo->Depth, VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||||
|
else if (fbo->Stencil.Kind != MG_Pipe::kMGPipeSurfaceKindNone)
|
||||||
|
(void)ResolveWireImage(*fbo, fbo->Stencil, VK_IMAGE_ASPECT_STENCIL_BIT);
|
||||||
}
|
}
|
||||||
if (fbo->IsDefault || fbo->Depth.Kind != MG_Pipe::kMGPipeSurfaceKindNone)
|
|
||||||
(void)ResolveWireImage(*fbo, fbo->Depth, VK_IMAGE_ASPECT_DEPTH_BIT);
|
|
||||||
else if (fbo->Stencil.Kind != MG_Pipe::kMGPipeSurfaceKindNone)
|
|
||||||
(void)ResolveWireImage(*fbo, fbo->Stencil, VK_IMAGE_ASPECT_STENCIL_BIT);
|
|
||||||
m_textureManager->FlushPendingUploads();
|
m_textureManager->FlushPendingUploads();
|
||||||
const auto vaoHandle = state.BoundVertexElements;
|
const auto vaoHandle = state.BoundVertexElements;
|
||||||
if (MG_Pipe::MGPipeHandleIsNull(vaoHandle)) MagmaWireFatal("vertex-elements-record");
|
if (MG_Pipe::MGPipeHandleIsNull(vaoHandle)) MagmaWireFatal("vertex-elements-record");
|
||||||
@@ -483,7 +520,21 @@
|
|||||||
MGL_WIRE_DECLINE_AT(UniformBufferBinding, "the program's uniform/storage bindings could not be bound");
|
MGL_WIRE_DECLINE_AT(UniformBufferBinding, "the program's uniform/storage bindings could not be bound");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Asked again: the descriptor resolve may have closed the pass, re-created an image or
|
||||||
|
// moved a layout.
|
||||||
|
if (passContinues) passContinues = WirePassMemoHolds(*fbo, frame);
|
||||||
|
// A memo that holds is the open pass itself (WirePassMemoHolds compared the active pass).
|
||||||
|
Bool continuing = true;
|
||||||
|
if (passContinues) {
|
||||||
|
auto& store = MG_Record::ServerStagedTexture();
|
||||||
|
if (!m_wirePassMarksValid || m_wirePassMarksClearGeneration != store.GpuDirtyClearGeneration()) {
|
||||||
|
for (const auto& mark : m_wirePassMemo.marks)
|
||||||
|
m_textureManager->MarkWireTextureGpuWritten(mark.storage, mark.level, mark.layer, mark.layers);
|
||||||
|
m_wirePassMarksValid = true;
|
||||||
|
m_wirePassMarksClearGeneration = store.GpuDirtyClearGeneration();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m_wirePassMemo.valid = false;
|
||||||
auto& attachments = scratch.attachments;
|
auto& attachments = scratch.attachments;
|
||||||
auto& viewInfos = scratch.viewInfos;
|
auto& viewInfos = scratch.viewInfos;
|
||||||
auto& colors = scratch.colors;
|
auto& colors = scratch.colors;
|
||||||
@@ -509,10 +560,13 @@
|
|||||||
auto& sameLayoutAttachments = scratch.sameLayoutAttachments;
|
auto& sameLayoutAttachments = scratch.sameLayoutAttachments;
|
||||||
sameLayoutAttachments.clear();
|
sameLayoutAttachments.clear();
|
||||||
scratch.gpuWriteMarks.clear();
|
scratch.gpuWriteMarks.clear();
|
||||||
|
m_wirePassMemo.attachments.clear();
|
||||||
const auto add = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect) -> Uint32 {
|
const auto add = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect) -> Uint32 {
|
||||||
WireImage image = ResolveWireImage(*fbo, surface, aspect, /*isWriteTarget=*/true);
|
WireImage image = ResolveWireImage(*fbo, surface, aspect, /*isWriteTarget=*/true);
|
||||||
if (!image.image) return VK_ATTACHMENT_UNUSED;
|
if (!image.image) return VK_ATTACHMENT_UNUSED;
|
||||||
m_textureManager->FlushPendingUploads();
|
m_textureManager->FlushPendingUploads();
|
||||||
|
m_wirePassMemo.attachments.push_back(
|
||||||
|
{image.resource, image.storage, surface.Kind == MG_Pipe::kMGPipeSurfaceKindRenderbuffer});
|
||||||
if (aspect == VK_IMAGE_ASPECT_COLOR_BIT)
|
if (aspect == VK_IMAGE_ASPECT_COLOR_BIT)
|
||||||
image.format = ResolveSrgbAttachmentWriteFormat(image.format,
|
image.format = ResolveSrgbAttachmentWriteFormat(image.format,
|
||||||
MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::FramebufferSrgb));
|
MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::FramebufferSrgb));
|
||||||
@@ -588,7 +642,7 @@
|
|||||||
passInfo.pAttachments = attachments.data();
|
passInfo.pAttachments = attachments.data();
|
||||||
passInfo.subpassCount = 1;
|
passInfo.subpassCount = 1;
|
||||||
passInfo.pSubpasses = &subpass;
|
passInfo.pSubpasses = &subpass;
|
||||||
const Bool continuing = m_wireDrawPass && m_wireDrawPassKey == passKey;
|
continuing = m_wireDrawPass && m_wireDrawPassKey == passKey;
|
||||||
{
|
{
|
||||||
const auto* active = VkRenderPassManager::GetActiveRenderPass();
|
const auto* active = VkRenderPassManager::GetActiveRenderPass();
|
||||||
const Bool stillOpen = continuing && active != nullptr && active->commandBuffer == frame.commandBuffer;
|
const Bool stillOpen = continuing && active != nullptr && active->commandBuffer == frame.commandBuffer;
|
||||||
@@ -642,8 +696,21 @@
|
|||||||
m_wirePassMarksClearGeneration = store.GpuDirtyClearGeneration();
|
m_wirePassMarksClearGeneration = store.GpuDirtyClearGeneration();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto& pass = *m_wireDrawPass;
|
{
|
||||||
// Interned once per pass: a continued pass has the same key, so the same compatibility.
|
// What this walk proved, for WirePassMemoHolds on the next draw.
|
||||||
|
const auto& st = MG_Pipe::MGPipeApplier();
|
||||||
|
auto& memo = m_wirePassMemo;
|
||||||
|
memo.valid = !fbo->IsDefault;
|
||||||
|
memo.framebufferSerial = st.FramebufferSerial;
|
||||||
|
memo.contextSerial = st.ContextSerial;
|
||||||
|
memo.drawFramebuffer = st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)];
|
||||||
|
memo.textureImageEpoch = m_textureManager->GetTextureImageEpoch();
|
||||||
|
memo.resourceEraseEpoch = m_textureManager->GetResourceEraseEpoch();
|
||||||
|
memo.framebufferSrgb = MG_Pipe::gPipeInputs.IsCapabilityEnabled(CapabilityInput::FramebufferSrgb);
|
||||||
|
memo.passHash = m_wireDrawPass->hash;
|
||||||
|
memo.marks.assign(scratch.gpuWriteMarks.begin(), scratch.gpuWriteMarks.end());
|
||||||
|
}
|
||||||
|
// The compatibility key is built from this walk's attachments only when the pass is new.
|
||||||
if (m_wireDrawPassCompatibilityId == 0) {
|
if (m_wireDrawPassCompatibilityId == 0) {
|
||||||
auto& pipelinePassKey = scratch.compatibilityKey;
|
auto& pipelinePassKey = scratch.compatibilityKey;
|
||||||
pipelinePassKey.attachmentFormatsAndSamples.clear();
|
pipelinePassKey.attachmentFormatsAndSamples.clear();
|
||||||
@@ -655,6 +722,9 @@
|
|||||||
pipelinePassKey.depthReference = depth.attachment;
|
pipelinePassKey.depthReference = depth.attachment;
|
||||||
m_wireDrawPassCompatibilityId = m_wireRenderPassCompatibility.Intern(pipelinePassKey);
|
m_wireDrawPassCompatibilityId = m_wireRenderPassCompatibility.Intern(pipelinePassKey);
|
||||||
}
|
}
|
||||||
|
} // !passContinues
|
||||||
|
auto& pass = *m_wireDrawPass;
|
||||||
|
// Interned once per pass (above): a continued pass has the same compatibility.
|
||||||
const Uint64 compatibilityId = m_wireDrawPassCompatibilityId;
|
const Uint64 compatibilityId = m_wireDrawPassCompatibilityId;
|
||||||
const auto pipeline = GetOrCreatePipelineWithInput(mode, program, programObj, flags, input, pass,
|
const auto pipeline = GetOrCreatePipelineWithInput(mode, program, programObj, flags, input, pass,
|
||||||
ResolvePrimitiveRestartEnable(aspects, indices), compatibilityId);
|
ResolvePrimitiveRestartEnable(aspects, indices), compatibilityId);
|
||||||
|
|||||||
@@ -172,6 +172,7 @@
|
|||||||
std::max(resource->extent.height >> level, 1u)};
|
std::max(resource->extent.height >> level, 1u)};
|
||||||
out.layout = resource->layout;
|
out.layout = resource->layout;
|
||||||
out.trackedLayout = &resource->layout;
|
out.trackedLayout = &resource->layout;
|
||||||
|
out.resource = resource;
|
||||||
out.samples = resource->sampleCount;
|
out.samples = resource->sampleCount;
|
||||||
// P7 wave 2-B2: the shader multisample resolve SAMPLES this image, which is a question
|
// P7 wave 2-B2: the shader multisample resolve SAMPLES this image, which is a question
|
||||||
// about the image and not about the format - VkTextureManager asks for SAMPLED on every
|
// about the image and not about the format - VkTextureManager asks for SAMPLED on every
|
||||||
|
|||||||
Reference in New Issue
Block a user