mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Perf] (MG_Backend): merge DirectVulkan's contiguous sub-draws, remember four programs
61% of mc_sodium_multidraw's steady-state CPU sat inside the driver encoding one vkCmdDrawIndexed per sub-draw. MultiDrawElements now collapses contiguous runs: merge only when the topology is a list (POINTS/LINES/TRIANGLES), the accumulated count sits on a primitive boundary, primitive restart is off, baseVertex/instanceCount/ firstInstance are identical and firstIndex is adjacent, with a count-overflow guard - the bench's 132x32 sub-draws become 132x1. Dangling-index discard semantics for list topologies are what the GL spec already mandates per draw. No new Vulkan feature, so no DriverPost gate; VK_EXT_multi_draw stays a gated follow-up. The draw fast path's single SetupDraw snapshot died on every program ping-pong (use_program's A/B pattern sent every other draw down the full path, CollectSampledTextures alone 6.2% self). A 4-entry program-keyed snapshot table (MRU by program lifetime id, per-entry sampled-set copies, per-entry invalidation on decline or full-path start, all entries still cleared at command-buffer boundary, pipeline age-out and swapchain recreate) keeps all cycling programs hot. Load-gated 6-round order-alternating A/B, sha1-fingerprinted pair: sodium_multidraw -41.3%, use_program -23.9%, pass_switch -12.7%, tex_param -3.8%, vanilla -2.1%; the one flag (tex_stream +5.4%) reversed to -0.5% across 10 isolated alternating pairs. Espryt untouched and unmoved. Unit tests 421/421.
This commit is contained in:
@@ -5127,10 +5127,34 @@ void main() {
|
|||||||
Bool VulkanRenderer::TrySetupDrawFastPath(FrameContext::FrameData& frame, GLenum mode,
|
Bool VulkanRenderer::TrySetupDrawFastPath(FrameContext::FrameData& frame, GLenum mode,
|
||||||
Flags<DrawSetupAspect> aspects, const DrawCmdParam& drawParams,
|
Flags<DrawSetupAspect> aspects, const DrawCmdParam& drawParams,
|
||||||
const IndexBufferView* pIndexBufferView) {
|
const IndexBufferView* pIndexBufferView) {
|
||||||
SetupDrawSnapshot& snap = m_setupDrawSnapshot;
|
if (!frame.isCommandRecording) {
|
||||||
if (!snap.valid || !frame.isCommandRecording) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Entry select: by the draw program's lifetime id, MRU first (the id pins
|
||||||
|
// the entry; every other fact is re-guarded below, so probing a stale
|
||||||
|
// entry can only decline, never serve stale state).
|
||||||
|
const auto& program = *MG_State::pGLContext->GetProgramForDraw();
|
||||||
|
const Uint64 programLifetimeId = program.GetLifetimeId();
|
||||||
|
SetupDrawSnapshot* snapPtr = nullptr;
|
||||||
|
{
|
||||||
|
SetupDrawSnapshot& mru = m_setupDrawSnapshots[m_setupDrawSnapshotMru];
|
||||||
|
if (mru.valid && mru.programLifetimeId == programLifetimeId) {
|
||||||
|
snapPtr = &mru;
|
||||||
|
} else {
|
||||||
|
for (Uint32 i = 0; i < kSetupDrawSnapshotCount; ++i) {
|
||||||
|
SetupDrawSnapshot& candidate = m_setupDrawSnapshots[i];
|
||||||
|
if (candidate.valid && candidate.programLifetimeId == programLifetimeId) {
|
||||||
|
snapPtr = &candidate;
|
||||||
|
m_setupDrawSnapshotMru = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (snapPtr == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SetupDrawSnapshot& snap = *snapPtr;
|
||||||
if (snap.aspects != aspects.GetRaw() || snap.mode != mode) {
|
if (snap.aspects != aspects.GetRaw() || snap.mode != mode) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -5142,9 +5166,7 @@ void main() {
|
|||||||
snap.imageIndex != m_imageIndexAcquired) {
|
snap.imageIndex != m_imageIndexAcquired) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto& program = *MG_State::pGLContext->GetProgramForDraw();
|
if (program.GetBackendStateVersion() != snap.programVersion) {
|
||||||
if (program.GetLifetimeId() != snap.programLifetimeId ||
|
|
||||||
program.GetBackendStateVersion() != snap.programVersion) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// A changed VAO does NOT decline: the VAO only feeds the pipeline's vertex
|
// A changed VAO does NOT decline: the VAO only feeds the pipeline's vertex
|
||||||
@@ -5282,7 +5304,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bindsMoved &&
|
if (bindsMoved &&
|
||||||
!m_uniformManager->SampledBindingsUnchanged(program, programObj, m_sampledBindingRecordsScratch)) {
|
!m_uniformManager->SampledBindingsUnchanged(program, programObj, snap.sampledBindingRecords)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5291,8 +5313,11 @@ void main() {
|
|||||||
// sampleable, then stamp recording use exactly as the full path would.
|
// sampleable, then stamp recording use exactly as the full path would.
|
||||||
// A feedback case (sampled texture written by the active pass) fails the
|
// A feedback case (sampled texture written by the active pass) fails the
|
||||||
// layout check and falls back to the full path's end-pass handling.
|
// layout check and falls back to the full path's end-pass handling.
|
||||||
const auto& sampledTextures = m_sampledTexturesScratch;
|
// The ENTRY's copies, not the scratch vectors: with more than one entry
|
||||||
const auto& sampledResources = m_sampledResourcesScratch;
|
// the scratch holds only the last full-path draw's set, which may belong
|
||||||
|
// to a different program.
|
||||||
|
const auto& sampledTextures = snap.sampledTextures;
|
||||||
|
const auto& sampledResources = snap.sampledResources;
|
||||||
if (sampledResources.size() != sampledTextures.size()) {
|
if (sampledResources.size() != sampledTextures.size()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -5306,7 +5331,7 @@ void main() {
|
|||||||
// generation means SampledBindingsUnchanged proved the per-binding (texture,
|
// generation means SampledBindingsUnchanged proved the per-binding (texture,
|
||||||
// sampler) pairs identical, and the sums/generation checks below cover every
|
// sampler) pairs identical, and the sums/generation checks below cover every
|
||||||
// remaining descriptor input.
|
// remaining descriptor input.
|
||||||
const Bool layoutSnapshotUsable = m_sampledLayoutSnapshots.size() == sampledTextures.size();
|
const Bool layoutSnapshotUsable = snap.sampledLayouts.size() == sampledTextures.size();
|
||||||
Bool samplerDescriptorsUnchanged = layoutSnapshotUsable;
|
Bool samplerDescriptorsUnchanged = layoutSnapshotUsable;
|
||||||
for (SizeT i = 0; i < sampledTextures.size(); ++i) {
|
for (SizeT i = 0; i < sampledTextures.size(); ++i) {
|
||||||
const auto* sampledTexture = sampledTextures[i];
|
const auto* sampledTexture = sampledTextures[i];
|
||||||
@@ -5317,8 +5342,8 @@ void main() {
|
|||||||
if (resource == nullptr || !IsValidSampledImageLayout(resource->layout)) {
|
if (resource == nullptr || !IsValidSampledImageLayout(resource->layout)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (layoutSnapshotUsable && m_sampledLayoutSnapshots[i] != resource->layout) {
|
if (layoutSnapshotUsable && snap.sampledLayouts[i] != resource->layout) {
|
||||||
m_sampledLayoutSnapshots[i] = resource->layout;
|
snap.sampledLayouts[i] = resource->layout;
|
||||||
samplerDescriptorsUnchanged = false;
|
samplerDescriptorsUnchanged = false;
|
||||||
}
|
}
|
||||||
contentSum += sampledTexture->GetContentVersion();
|
contentSum += sampledTexture->GetContentVersion();
|
||||||
@@ -5437,17 +5462,51 @@ void main() {
|
|||||||
if (TrySetupDrawFastPath(frame, mode, aspects, drawParams, pIndexBufferView)) {
|
if (TrySetupDrawFastPath(frame, mode, aspects, drawParams, pIndexBufferView)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// The fast path declined: whatever it saw may be stale. The full path
|
|
||||||
// below re-resolves everything and refreshes the snapshot on success.
|
|
||||||
m_setupDrawSnapshot.valid = false;
|
|
||||||
const auto& drawFbo =
|
const auto& drawFbo =
|
||||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||||
if (drawFbo != nullptr && IsUnsupportedFramebufferForDirectVulkan(*drawFbo)) {
|
if (drawFbo != nullptr && IsUnsupportedFramebufferForDirectVulkan(*drawFbo)) {
|
||||||
|
// Nothing was mutated: other entries' per-probe guards (FBO identity +
|
||||||
|
// version among them) stay authoritative, so none need invalidating.
|
||||||
RecordUnsupportedFramebufferError(__func__);
|
RecordUnsupportedFramebufferError(__func__);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
|
const auto& vao = *MG_State::pGLContext->GetBoundVertexArray();
|
||||||
const auto& program = *MG_State::pGLContext->GetProgramForDraw();
|
const auto& program = *MG_State::pGLContext->GetProgramForDraw();
|
||||||
|
// The fast path declined (or had no entry for this program): whatever THIS
|
||||||
|
// program's entry saw may be stale, and the full path below mutates state as
|
||||||
|
// it goes, so the entry must not stay matchable if that path fails mid-way.
|
||||||
|
// Select it now - the program's own entry when one exists, else an invalid
|
||||||
|
// slot, else a round-robin victim - and invalidate it until the successful
|
||||||
|
// refill at the end. Other programs' entries keep their validity: every fact
|
||||||
|
// they carry is re-guarded per probe (live pass hash, epochs, versions,
|
||||||
|
// sums), so a full path run in between can only make them decline.
|
||||||
|
SetupDrawSnapshot* fillSnap = nullptr;
|
||||||
|
{
|
||||||
|
Uint32 fillIndex = kSetupDrawSnapshotCount;
|
||||||
|
const Uint64 fillProgramLifetimeId = program.GetLifetimeId();
|
||||||
|
for (Uint32 i = 0; i < kSetupDrawSnapshotCount; ++i) {
|
||||||
|
if (m_setupDrawSnapshots[i].valid &&
|
||||||
|
m_setupDrawSnapshots[i].programLifetimeId == fillProgramLifetimeId) {
|
||||||
|
fillIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fillIndex == kSetupDrawSnapshotCount) {
|
||||||
|
for (Uint32 i = 0; i < kSetupDrawSnapshotCount; ++i) {
|
||||||
|
if (!m_setupDrawSnapshots[i].valid) {
|
||||||
|
fillIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fillIndex == kSetupDrawSnapshotCount) {
|
||||||
|
fillIndex = m_setupDrawSnapshotVictim;
|
||||||
|
m_setupDrawSnapshotVictim = (m_setupDrawSnapshotVictim + 1) % kSetupDrawSnapshotCount;
|
||||||
|
}
|
||||||
|
fillSnap = &m_setupDrawSnapshots[fillIndex];
|
||||||
|
fillSnap->valid = false;
|
||||||
|
m_setupDrawSnapshotMru = fillIndex;
|
||||||
|
}
|
||||||
ProgramFactory::CompileOptionFlags transformFlags =
|
ProgramFactory::CompileOptionFlags transformFlags =
|
||||||
ProgramFactory::CompileOptionFlags(GetBaseTransformFlagsRaw());
|
ProgramFactory::CompileOptionFlags(GetBaseTransformFlagsRaw());
|
||||||
// Captured draws take the xfb-decorated program variant.
|
// Captured draws take the xfb-decorated program variant.
|
||||||
@@ -5773,7 +5832,7 @@ void main() {
|
|||||||
// Snapshot the fully resolved configuration for the consecutive-draw
|
// Snapshot the fully resolved configuration for the consecutive-draw
|
||||||
// fast path (see TrySetupDrawFastPath).
|
// fast path (see TrySetupDrawFastPath).
|
||||||
{
|
{
|
||||||
auto& snap = m_setupDrawSnapshot;
|
auto& snap = *fillSnap;
|
||||||
const auto* nowActiveRenderPass = VkRenderPassManager::GetActiveRenderPass();
|
const auto* nowActiveRenderPass = VkRenderPassManager::GetActiveRenderPass();
|
||||||
if (nowActiveRenderPass != nullptr && !programObj.hasStorageImages) {
|
if (nowActiveRenderPass != nullptr && !programObj.hasStorageImages) {
|
||||||
snap.valid = true;
|
snap.valid = true;
|
||||||
@@ -5815,10 +5874,15 @@ void main() {
|
|||||||
snap.samplingResolutionGeneration = MG_State::pGLContext->GetSamplingResolutionGeneration();
|
snap.samplingResolutionGeneration = MG_State::pGLContext->GetSamplingResolutionGeneration();
|
||||||
Uint64 snapContentSum = 0;
|
Uint64 snapContentSum = 0;
|
||||||
Uint64 snapParamsSum = 0;
|
Uint64 snapParamsSum = 0;
|
||||||
|
// Per-entry copies of this draw's sampled set (the scratch vectors
|
||||||
|
// will be overwritten by the next full-path draw of ANY program).
|
||||||
// Record each resource's layout VALUE for the descriptor-reuse hint;
|
// Record each resource's layout VALUE for the descriptor-reuse hint;
|
||||||
// transitions above updated the resources in place, so this reads the
|
// transitions above updated the resources in place, so this reads the
|
||||||
// layouts the descriptors just resolved against.
|
// layouts the descriptors just resolved against.
|
||||||
m_sampledLayoutSnapshots.assign(sampledTextures.size(), VK_IMAGE_LAYOUT_UNDEFINED);
|
snap.sampledTextures = sampledTextures;
|
||||||
|
snap.sampledResources = sampledResources;
|
||||||
|
snap.sampledBindingRecords = m_sampledBindingRecordsScratch;
|
||||||
|
snap.sampledLayouts.assign(sampledTextures.size(), VK_IMAGE_LAYOUT_UNDEFINED);
|
||||||
for (SizeT i = 0; i < sampledTextures.size(); ++i) {
|
for (SizeT i = 0; i < sampledTextures.size(); ++i) {
|
||||||
const auto* sampledTexture = sampledTextures[i];
|
const auto* sampledTexture = sampledTextures[i];
|
||||||
if (sampledTexture == nullptr) {
|
if (sampledTexture == nullptr) {
|
||||||
@@ -5827,7 +5891,7 @@ void main() {
|
|||||||
snapContentSum += sampledTexture->GetContentVersion();
|
snapContentSum += sampledTexture->GetContentVersion();
|
||||||
snapParamsSum += sampledTexture->GetTextureParamsVersion();
|
snapParamsSum += sampledTexture->GetTextureParamsVersion();
|
||||||
if (sampledResources[i] != nullptr) {
|
if (sampledResources[i] != nullptr) {
|
||||||
m_sampledLayoutSnapshots[i] = sampledResources[i]->layout;
|
snap.sampledLayouts[i] = sampledResources[i]->layout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
snap.sampledContentSum = snapContentSum;
|
snap.sampledContentSum = snapContentSum;
|
||||||
@@ -9439,13 +9503,75 @@ void main() {
|
|||||||
|
|
||||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||||
|
|
||||||
for (Uint32 idraw = 0; idraw < payload.drawCount; ++idraw) {
|
// Collapse contiguous sub-draw runs into one vkCmdDrawIndexed. Per-sub-draw
|
||||||
vkCmdDrawIndexed(commandBuffer,
|
// command emission in the driver dominates a Sodium-shaped multi-draw
|
||||||
payload.pParams[idraw].indexCount,
|
// (steady-state profile: >60% of the case inside the Vulkan driver's
|
||||||
payload.pParams[idraw].instanceCount,
|
// vkCmdDrawIndexed encoding for 132x32 sub-draws/frame), and a chunk
|
||||||
payload.pParams[idraw].firstIndex,
|
// renderer's sub-draws are runs of adjacent index ranges over one buffer.
|
||||||
payload.pParams[idraw].vertexOffset,
|
// Two draws are one iff they concatenate to an identical index stream:
|
||||||
payload.pParams[idraw].firstInstance);
|
// - a LIST topology (points/lines/triangles). Strips/fans/loops would
|
||||||
|
// weld primitives across the seam.
|
||||||
|
// - the accumulated count ends on a primitive boundary, otherwise GL
|
||||||
|
// discards the dangling indices at the sub-draw's end but the merged
|
||||||
|
// stream would assemble them with the next sub-draw's indices.
|
||||||
|
// - primitive restart is off: with restart on, a sentinel mid-stream
|
||||||
|
// resets assembly, so a partial primitive before the seam would
|
||||||
|
// otherwise be discarded per sub-draw (same dangling-index argument).
|
||||||
|
// - identical baseVertex/instancing and firstIndex adjacency, so the
|
||||||
|
// merged range fetches exactly the two sub-draws' indices in order.
|
||||||
|
Uint32 mergeGranularity = 0;
|
||||||
|
switch (payload.mode) {
|
||||||
|
case GL_POINTS: mergeGranularity = 1; break;
|
||||||
|
case GL_LINES: mergeGranularity = 2; break;
|
||||||
|
case GL_TRIANGLES: mergeGranularity = 3; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
if (mergeGranularity != 0) {
|
||||||
|
const RenderStateParameters& rsp = MG_State::pGLContext->GetRenderStateParameters();
|
||||||
|
if (rsp.PrimitiveRestartEnabled || rsp.PrimitiveRestartFixedIndexEnabled) {
|
||||||
|
mergeGranularity = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mergeGranularity == 0) {
|
||||||
|
for (Uint32 idraw = 0; idraw < payload.drawCount; ++idraw) {
|
||||||
|
vkCmdDrawIndexed(commandBuffer,
|
||||||
|
payload.pParams[idraw].indexCount,
|
||||||
|
payload.pParams[idraw].instanceCount,
|
||||||
|
payload.pParams[idraw].firstIndex,
|
||||||
|
payload.pParams[idraw].vertexOffset,
|
||||||
|
payload.pParams[idraw].firstInstance);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Uint32 idraw = 0;
|
||||||
|
while (idraw < payload.drawCount) {
|
||||||
|
const DrawIndexedCmdParam& head = payload.pParams[idraw];
|
||||||
|
++idraw;
|
||||||
|
if (head.indexCount == 0) {
|
||||||
|
continue; // draws nothing, contributes nothing to a run
|
||||||
|
}
|
||||||
|
Uint32 mergedIndexCount = head.indexCount;
|
||||||
|
if (head.instanceCount == 1) {
|
||||||
|
while (idraw < payload.drawCount) {
|
||||||
|
const DrawIndexedCmdParam& next = payload.pParams[idraw];
|
||||||
|
if (next.indexCount == 0) {
|
||||||
|
++idraw;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (mergedIndexCount % mergeGranularity != 0 ||
|
||||||
|
next.instanceCount != 1 ||
|
||||||
|
next.vertexOffset != head.vertexOffset ||
|
||||||
|
next.firstInstance != head.firstInstance ||
|
||||||
|
next.firstIndex != head.firstIndex + mergedIndexCount ||
|
||||||
|
mergedIndexCount + next.indexCount < mergedIndexCount) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mergedIndexCount += next.indexCount;
|
||||||
|
++idraw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vkCmdDrawIndexed(commandBuffer, mergedIndexCount, head.instanceCount, head.firstIndex,
|
||||||
|
head.vertexOffset, head.firstInstance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10105,7 +10231,7 @@ void main() {
|
|||||||
void VulkanRenderer::OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) {
|
void VulkanRenderer::OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) {
|
||||||
// Dynamic state does not survive a command-buffer boundary.
|
// Dynamic state does not survive a command-buffer boundary.
|
||||||
ResetDynamicStateShadow();
|
ResetDynamicStateShadow();
|
||||||
m_setupDrawSnapshot.valid = false;
|
InvalidateSetupDrawSnapshots();
|
||||||
if (m_uniformManager) {
|
if (m_uniformManager) {
|
||||||
m_uniformManager->OnCommandBufferBoundary();
|
m_uniformManager->OnCommandBufferBoundary();
|
||||||
}
|
}
|
||||||
@@ -10271,7 +10397,7 @@ void main() {
|
|||||||
// A recreated pipeline could reuse a freed handle value and alias
|
// A recreated pipeline could reuse a freed handle value and alias
|
||||||
// the bind-dedup shadow; force the next draw to re-bind.
|
// the bind-dedup shadow; force the next draw to re-bind.
|
||||||
g_dynamicStateShadow.graphicsPipelineValid = false;
|
g_dynamicStateShadow.graphicsPipelineValid = false;
|
||||||
m_setupDrawSnapshot.valid = false;
|
InvalidateSetupDrawSnapshots();
|
||||||
}
|
}
|
||||||
m_vertexInputStateFactory->OnFrameBoundary();
|
m_vertexInputStateFactory->OnFrameBoundary();
|
||||||
m_samplerManager->OnFrameBoundary();
|
m_samplerManager->OnFrameBoundary();
|
||||||
@@ -11653,7 +11779,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
InvalidatePipelineMemo(); // pipelines freed -> the memoized handle would dangle
|
InvalidatePipelineMemo(); // pipelines freed -> the memoized handle would dangle
|
||||||
g_dynamicStateShadow.graphicsPipelineValid = false;
|
g_dynamicStateShadow.graphicsPipelineValid = false;
|
||||||
m_setupDrawSnapshot.valid = false;
|
InvalidateSetupDrawSnapshots();
|
||||||
DestroyComputePipelines();
|
DestroyComputePipelines();
|
||||||
if (m_frameContext.GetFrameCount() > 0) {
|
if (m_frameContext.GetFrameCount() > 0) {
|
||||||
m_frameContext.GetCurrent().isCommandRecording = false;
|
m_frameContext.GetCurrent().isCommandRecording = false;
|
||||||
|
|||||||
@@ -765,8 +765,36 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// it bypasses GetOrCreateProgram, or the idle sweep could evict a live entry.
|
// it bypasses GetOrCreateProgram, or the idle sweep could evict a live entry.
|
||||||
const ProgramFactory::VkProgramObject* programObj = nullptr;
|
const ProgramFactory::VkProgramObject* programObj = nullptr;
|
||||||
Uint64 programFactoryEpoch = 0;
|
Uint64 programFactoryEpoch = 0;
|
||||||
|
// Per-entry copies of the snapshotting draw's sampled set (the scratch
|
||||||
|
// vectors below hold only the LAST full-path draw's set, which with more
|
||||||
|
// than one snapshot entry is not necessarily this entry's program).
|
||||||
|
// sampledTextures/sampledResources carry the same epoch-guarded pointer
|
||||||
|
// lifetime rules as the scratch originals: textureEraseEpoch (checked
|
||||||
|
// every probe) declines the entry before any erased resource pointer
|
||||||
|
// could be dereferenced. sampledLayouts is the layout VALUE each
|
||||||
|
// resource held when this entry's descriptors were built (the
|
||||||
|
// descriptor-reuse hint needs the SAME layout, not just a sampleable
|
||||||
|
// one), and sampledBindingRecords feeds SampledBindingsUnchanged when
|
||||||
|
// the bind generation moved.
|
||||||
|
Vector<MG_State::GLState::ITextureObject*> sampledTextures;
|
||||||
|
Vector<VkTextureManager::TextureResource*> sampledResources;
|
||||||
|
Vector<VkImageLayout> sampledLayouts;
|
||||||
|
Vector<UniformManager::SampledBindingRecord> sampledBindingRecords;
|
||||||
};
|
};
|
||||||
SetupDrawSnapshot m_setupDrawSnapshot;
|
// Program-keyed snapshot entries: program ping-pong (Sodium switches programs
|
||||||
|
// mid-frame every few draws) would otherwise evict the single snapshot on
|
||||||
|
// every switch and send every draw through the full path. Entries are found
|
||||||
|
// by programLifetimeId (MRU-first probe); every other guard stays per-probe,
|
||||||
|
// so a stale entry declines itself exactly like the old single snapshot did.
|
||||||
|
static constexpr Uint32 kSetupDrawSnapshotCount = 4;
|
||||||
|
SetupDrawSnapshot m_setupDrawSnapshots[kSetupDrawSnapshotCount];
|
||||||
|
Uint32 m_setupDrawSnapshotMru = 0; // last entry that hit or was filled
|
||||||
|
Uint32 m_setupDrawSnapshotVictim = 0; // round-robin fill cursor when all entries are live
|
||||||
|
void InvalidateSetupDrawSnapshots() {
|
||||||
|
for (auto& snapshot : m_setupDrawSnapshots) {
|
||||||
|
snapshot.valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Per-draw scratch buffers (clear keeps capacity) — these paths run for every
|
// Per-draw scratch buffers (clear keeps capacity) — these paths run for every
|
||||||
// draw call and must not allocate.
|
// draw call and must not allocate.
|
||||||
@@ -782,13 +810,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// post-transition loop can skip re-resolving textures whose layout is
|
// post-transition loop can skip re-resolving textures whose layout is
|
||||||
// already sampleable.
|
// already sampleable.
|
||||||
Vector<VkTextureManager::TextureResource*> m_sampledResourcesScratch;
|
Vector<VkTextureManager::TextureResource*> m_sampledResourcesScratch;
|
||||||
// Layout VALUE of each sampled resource when the snapshot (and so the cached
|
|
||||||
// sampler descriptors) was built, parallel to m_sampledResourcesScratch. The
|
|
||||||
// fast path's validity check only proves the layout is still sampleable; the
|
|
||||||
// descriptor-reuse hint additionally needs it to be the SAME sampleable
|
|
||||||
// layout (a mid-frame compute dispatch can move a sampled texture from
|
|
||||||
// READ_ONLY_OPTIMAL to GENERAL, both valid, different descriptor).
|
|
||||||
Vector<VkImageLayout> m_sampledLayoutSnapshots;
|
|
||||||
Vector<MG_State::GLState::ITextureObject*> m_storageImageTexturesScratch;
|
Vector<MG_State::GLState::ITextureObject*> m_storageImageTexturesScratch;
|
||||||
Vector<VkBuffer> m_vertexBuffersScratch;
|
Vector<VkBuffer> m_vertexBuffersScratch;
|
||||||
Vector<VkDeviceSize> m_vertexOffsetsScratch;
|
Vector<VkDeviceSize> m_vertexOffsetsScratch;
|
||||||
|
|||||||
Reference in New Issue
Block a user