mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Fix] (DirectVulkan, SelfTest): honour the primitives-generated probe's leak-on-timeout contract in both of its callers, count paused-span draws the frontend cannot price, refuse a substitute measured worse than the stream query, and derive the POST row's failure clause from the measurement
This commit is contained in:
@@ -1221,11 +1221,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// stale queries are always safe to delete.
|
||||
Uint64 rendererGeneration = 0;
|
||||
// Kind::XfbGenerated - the frontend's paused-draw primitive counter when the
|
||||
// query began. VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT counts only what the
|
||||
// capture saw, so a draw made while the span was paused is invisible to it -
|
||||
// but GL_PRIMITIVES_GENERATED counts what the last vertex processing stage
|
||||
// emitted regardless. The delta closes that gap at result time.
|
||||
// query began. On the affected drivers VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT
|
||||
// counts only what the capture saw, so a draw made while the span was paused is
|
||||
// invisible to it - but GL_PRIMITIVES_GENERATED counts what the last vertex
|
||||
// processing stage emitted regardless. The delta closes that gap at result time.
|
||||
Uint64 pausedPrimitiveSnapshot = 0;
|
||||
// ...unless the GPU already counted those paused draws when the span opened -
|
||||
// through the reroute pool (VulkanRenderer::BeginXfbQueryForDraw reroutes every
|
||||
// draw with no open capture, paused ones included) or, where the probe measured
|
||||
// the stream query as counting capture-less draws, through the stream slot the
|
||||
// paused draw still takes. Adding the CPU delta on top would count them twice,
|
||||
// and the CPU counter is the weaker source anyway: only 3 of the ~15 draw entry
|
||||
// points write it and it answers 0 for GL_PATCHES.
|
||||
Bool pausedPrimitivesCountedByGpu = false;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -1324,7 +1332,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
primitives)) {
|
||||
return false;
|
||||
}
|
||||
if (query->kind == VulkanTimerQuery::Kind::XfbGenerated && MG_State::pGLContext != nullptr) {
|
||||
if (query->kind == VulkanTimerQuery::Kind::XfbGenerated &&
|
||||
!query->pausedPrimitivesCountedByGpu && MG_State::pGLContext != nullptr) {
|
||||
primitives += MG_State::pGLContext->GetTransformFeedbackPausedPrimitiveCounter() -
|
||||
query->pausedPrimitiveSnapshot;
|
||||
}
|
||||
@@ -1373,6 +1382,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
query->rendererGeneration = GetRendererGeneration();
|
||||
query->pausedPrimitiveSnapshot =
|
||||
MG_State::pGLContext ? MG_State::pGLContext->GetTransformFeedbackPausedPrimitiveCounter() : 0;
|
||||
// Read AFTER StartXfbQueryCapture, which is where a failed reroute-pool creation
|
||||
// disarms: the answer is then what this span will actually do for every draw.
|
||||
query->pausedPrimitivesCountedByGpu = generated && pVulkanRenderer->ArePausedDrawsGpuCounted();
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
@@ -3285,9 +3285,10 @@ void main() {
|
||||
m_primGenRerouteActiveSlots.clear();
|
||||
m_primGenRerouteSlotCursor = 0;
|
||||
m_primGenRerouteSlotOpen = false;
|
||||
// Not sticky across renderers: the next bring-up re-decides it (from the
|
||||
// Not sticky across renderers: the next bring-up re-decides both (from the
|
||||
// per-process probe memo, so it re-decides without re-probing).
|
||||
m_primGenRerouteKind = MG_Util::SelfTest::PrimGenRerouteKind::None;
|
||||
m_primGenStreamCountsXfbInactiveDraws = false;
|
||||
m_bufferManager.Shutdown();
|
||||
|
||||
// Device is idle (vkDeviceWaitIdle above); query pools can be destroyed.
|
||||
@@ -11436,6 +11437,17 @@ void main() {
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::ArePausedDrawsGpuCounted() const {
|
||||
// Exactly the gate BeginXfbQueryForDraw applies per draw, so a span told "armed"
|
||||
// really does get a reroute slot for every draw with no open capture - a paused
|
||||
// span's draws included.
|
||||
const Bool rerouteArmed = m_primGenRerouteKind != MG_Util::SelfTest::PrimGenRerouteKind::None &&
|
||||
m_primGenReroutePool != VK_NULL_HANDLE;
|
||||
// Otherwise the paused draw takes a stream slot, which is an exact count of it
|
||||
// on a driver the probe measured as counting capture-less draws.
|
||||
return rerouteArmed || m_primGenStreamCountsXfbInactiveDraws;
|
||||
}
|
||||
|
||||
void VulkanRenderer::StopXfbQueryCapture(Uint32 kind, Vector<Uint32>& outSlots,
|
||||
Vector<Uint32>& outRerouteSlots) {
|
||||
if (kind > 1) {
|
||||
@@ -11503,17 +11515,21 @@ void main() {
|
||||
if ((!m_xfbQueryCaptureActive[0] && !m_xfbQueryCaptureActive[1]) || m_xfbQueryPool == VK_NULL_HANDLE) {
|
||||
return;
|
||||
}
|
||||
// A draw made while the GL span is merely PAUSED is CPU-accounted by the
|
||||
// frontend's paused-primitive counter, whose delta the GENERATED resolve adds
|
||||
// (DirectVulkan.cpp); a reroute slot for it would count it twice. Only a
|
||||
// draw with no capture AND no paused span is the stream query's silent case.
|
||||
const Bool glSpanPausedDraw = MG_State::pGLContext != nullptr &&
|
||||
MG_State::pGLContext->IsTransformFeedbackActive() &&
|
||||
MG_State::pGLContext->IsTransformFeedbackPaused();
|
||||
// Every draw with no OPEN capture is the stream query's silent case, and that
|
||||
// includes a draw made while the GL span is merely PAUSED (the pause closes the
|
||||
// capture, so BeginXfbCaptureForDraw already answered false for it). Paused
|
||||
// draws are rerouted like any other: the frontend's CPU paused-primitive
|
||||
// counter cannot stand in for them - it is written by only 3 of the ~15 draw
|
||||
// entry points (never the instanced, indirect or multi-draw ones) and answers 0
|
||||
// for GL_PATCHES by design, since the tessellator's amplification is not
|
||||
// knowable on the CPU - which is exactly the CTS's shape. Double counting is
|
||||
// prevented on the other side instead: a GENERATED span opened while this
|
||||
// reroute is armed ignores that CPU counter entirely (see
|
||||
// ArePausedDrawsGpuCounted and DirectVulkan.cpp's XfbGenerated resolve), so
|
||||
// every XFB-inactive draw in the span is priced exactly once, by this pool.
|
||||
const Bool rerouteGenerated = m_xfbQueryCaptureActive[1] &&
|
||||
m_primGenRerouteKind != MG_Util::SelfTest::PrimGenRerouteKind::None &&
|
||||
m_primGenReroutePool != VK_NULL_HANDLE && !xfbActive &&
|
||||
!glSpanPausedDraw;
|
||||
m_primGenReroutePool != VK_NULL_HANDLE && !xfbActive;
|
||||
// The stream slot stays for WRITTEN whatever the reroute does (with capture
|
||||
// inactive its primitivesWritten is 0, which is the correct WRITTEN answer),
|
||||
// and for GENERATED wherever this draw is not rerouted - so one GL query span
|
||||
@@ -14234,7 +14250,18 @@ void main() {
|
||||
return RunPrimitivesGeneratedNoXfbProbe(probeContext);
|
||||
}();
|
||||
verdict = EvaluatePrimitivesGeneratedNoXfbVerdict(s_measurement);
|
||||
if (!s_measurement.ran) {
|
||||
if (s_measurement.fenceWaitTimedOut) {
|
||||
// The probe's submission never signaled within its bound, so it left its
|
||||
// command pool, query pools, render pass, framebuffer, shader modules,
|
||||
// pipeline layout, pipelines and fence alive on purpose. This device is the
|
||||
// renderer's own and outlives them, so nothing here may destroy them or
|
||||
// wait the device idle - the queue may still be executing that submission,
|
||||
// and an idle wait is the hang the bound exists to prevent. They leak for
|
||||
// the process's life; a device this sick has bigger problems.
|
||||
MGLOG_W("PRIMITIVES_GENERATED probe timed out waiting on its own submission (%s); its "
|
||||
"Vulkan objects are deliberately leaked and XFB-inactive draws keep the stream "
|
||||
"query", s_measurement.failureReason.c_str());
|
||||
} else if (!s_measurement.ran) {
|
||||
MGLOG_W("PRIMITIVES_GENERATED probe did not run (%s); XFB-inactive draws keep the "
|
||||
"stream query", s_measurement.failureReason.c_str());
|
||||
} else {
|
||||
@@ -14256,6 +14283,13 @@ void main() {
|
||||
logShape("patches+discard", s_measurement.patchesDiscard);
|
||||
}
|
||||
}
|
||||
// A driver whose stream query counts capture-less draws counts a PAUSED span's
|
||||
// draws through the stream slot they take, so that span's result must not have
|
||||
// the frontend's CPU paused counter added on top of it either (the pre-reroute
|
||||
// accounting did exactly that, double counting every paused draw the CPU could
|
||||
// price). Measured, not assumed: the forced arms never ask the probe and leave
|
||||
// this false.
|
||||
m_primGenStreamCountsXfbInactiveDraws = verdict == PrimitivesGeneratedNoXfbVerdict::StreamCounts;
|
||||
m_primGenRerouteKind = ChoosePrimitivesGeneratedReroute(
|
||||
overrideSetting, verdict, primitivesGeneratedQueryUsable, m_pipelineStatisticsQueryFeatureEnabled);
|
||||
if (m_primGenRerouteKind != PrimGenRerouteKind::None) {
|
||||
|
||||
@@ -732,9 +732,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// per primitive reaching primitive clipping - after every vertex processing
|
||||
// stage, before rasterizer discard - which is the same set).
|
||||
// XFB-ACTIVE draws keep the stream slot (exact today, and WRITTEN needs it);
|
||||
// paused-span draws keep the frontend's CPU accounting (see the paused counter
|
||||
// in DirectVulkan.cpp) and never open a reroute slot, or they would count
|
||||
// twice. One GL query span may therefore hold slots of both pools.
|
||||
// every draw with no open capture - a PAUSED span's draws included - takes a
|
||||
// reroute slot, and the span then ignores the frontend's CPU paused-primitive
|
||||
// counter rather than adding it on top (see IsPrimGenRerouteArmed): that
|
||||
// counter is written by only 3 of the ~15 draw entry points and answers 0 for
|
||||
// GL_PATCHES, so it cannot price the draws this reroute exists to repair. One
|
||||
// GL query span may therefore hold slots of both pools.
|
||||
Bool m_pipelineStatisticsQueryFeatureEnabled = false;
|
||||
// VK_EXT_primitives_generated_query: base feature, and the
|
||||
// ...WithRasterizerDiscard feature without which a discarding draw inside the
|
||||
@@ -747,6 +750,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool m_tessellationShaderFeatureEnabled = false;
|
||||
MG_Util::SelfTest::PrimGenRerouteKind m_primGenRerouteKind =
|
||||
MG_Util::SelfTest::PrimGenRerouteKind::None;
|
||||
// The bring-up probe measured this device's stream query as counting draws made
|
||||
// with no capture span open (the StreamCounts verdict) - so it counts the
|
||||
// PAUSED-span ones too, through the stream slot they take when nothing is
|
||||
// rerouted. Only the probe can know this, so it stays false wherever the probe
|
||||
// is not consulted (the forced arms), which keeps those lanes' accounting as it
|
||||
// was.
|
||||
Bool m_primGenStreamCountsXfbInactiveDraws = false;
|
||||
VkQueryPool m_primGenReroutePool = VK_NULL_HANDLE;
|
||||
Uint32 m_primGenRerouteSlotCursor = 0;
|
||||
Vector<Uint32> m_primGenRerouteActiveSlots;
|
||||
@@ -758,6 +768,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
void ArmPrimGenReroute();
|
||||
|
||||
public:
|
||||
// Whether a GENERATED span opened now will have the draws made while the GL
|
||||
// span is PAUSED counted on the GPU - through the reroute pool, which takes
|
||||
// every draw with no open capture, or (where the reroute is not armed because
|
||||
// the stream query was measured to count capture-less draws) through the stream
|
||||
// slot such a draw still takes. The frontend's CPU paused-primitive counter
|
||||
// must not be added on top of either: it would double count, and it cannot
|
||||
// price the draws that matter anyway - only 3 of the ~15 draw entry points
|
||||
// write it and it answers 0 for GL_PATCHES. Read once per span, after
|
||||
// StartXfbQueryCapture (whose pool creation may disarm the reroute).
|
||||
Bool ArePausedDrawsGpuCounted() const;
|
||||
// kind: 0 = PRIMITIVES_WRITTEN, 1 = PRIMITIVES_GENERATED.
|
||||
Bool StartXfbQueryCapture(Uint32 kind);
|
||||
void StopXfbQueryCapture(Uint32 kind, Vector<Uint32>& outSlots, Vector<Uint32>& outRerouteSlots);
|
||||
|
||||
Reference in New Issue
Block a user