GS: derive fetch-orders-overlap where it cannot go stale

The Vulkan backend derived framebuffer_fetch_orders_overlap from
framebuffer_fetch immediately after the first assignment, but three later
statements still write framebuffer_fetch -- the RT-copy workaround's
texture_barrier mask among them. On Adreno that mask clears fetch, and the
derived bit kept the value it had beforehand, so the device came out
advertising no framebuffer fetch and "fetch orders overlapping primitives"
at the same time.

Nothing reads the stale value today: DetermineBarriers is the only consumer
and it sits inside an `if (features.framebuffer_fetch)` gate, so the
contradiction is unreachable. That is a property of the current single call
site, not of the bit, and it is the kind of guarantee a second reader removes
without noticing. Derive it after the last write instead.

Record what the Turnip source says about the contract while the bit is being
explained, because the file already carries a measurement that reads like a
counter-example and is not one. Turnip does request the ordering when tiled
(SINGLE_PRIM_MODE = FLUSH_PER_OVERLAP under rasterization-order access, which
the a6xx docs define as waiting for overlapping primitives); what it only sets
untiled is the stronger mode that additionally keeps UCHE and CCU in sync when
fetching the current pixel's previous value. So the Adreno failure recorded
above is read visibility while tiled, not primitive ordering, and it does not
generalise to a tiler whose fetch is a genuine tile-local read.

OpenGL never assigned the bit at all and took false from the FeatureSupport
memset, which is the value it wants -- GL fetch does not order overlapping
primitives, which is why the flag exists. Say so explicitly: Vulkan and Metal
both assign it, and the one backend that stays silent reads as an omission
rather than as an answer.

No behaviour change on any backend. gs_vertex_tests 48/48.
This commit is contained in:
Brian Degenhardt
2026-08-09 10:37:51 -07:00
parent 7668167aa1
commit 84f7c33822
2 changed files with 31 additions and 5 deletions
@@ -1065,6 +1065,11 @@ bool GSDeviceOGL::CheckFeatures()
GLAD_GL_EXT_shader_framebuffer_fetch, GLAD_GL_EXT_shader_pixel_local_storage, fbfetch_driver_blocklisted,
GSConfig.DisableFramebufferFetch, use_mali_profile);
m_features.framebuffer_fetch = fbfetch.enabled;
// GL fetch replaces the destination read but does NOT order overlapping primitives within one
// draw, so an overlapping draw keeps its full barrier (see FbFetchDropsDrawBarriers). Stated
// explicitly rather than left to the FeatureSupport memset: Vulkan and Metal both assign this
// bit, and a backend that stays silent reads as an oversight rather than as the answer.
m_features.framebuffer_fetch_orders_overlap = false;
switch (fbfetch.veto)
{
+26 -5
View File
@@ -3602,11 +3602,6 @@ bool GSDeviceVK::CheckFeatures()
(is_mali_vk || is_adreno || GSConfig.EnableAdrenoFramebufferFetch) && !is_xclipse_vk;
m_features.framebuffer_fetch = vendor_allows_fbfetch &&
m_optional_extensions.vk_ext_rasterization_order_attachment_access && !GSConfig.DisableFramebufferFetch;
// The Vulkan spelling of framebuffer fetch *is* rasterization-order attachment access, whose
// contract is that overlapping fragments in one draw observe each other in primitive order.
// So the ordering a full barrier would provide is already guaranteed, and keeping the barrier
// would only reintroduce the render-pass breaks this path exists to avoid.
m_features.framebuffer_fetch_orders_overlap = m_features.framebuffer_fetch;
m_features.texture_barrier = GSConfig.OverrideTextureBarriers != 0;
// No working in-pass render-target self-read (ARMSX2 #442, Qualcomm/Turnip). Force the RT-COPY
// path: with texture barriers off, GSRendererHW reads Cd from a separate copy of the target
@@ -3682,6 +3677,32 @@ bool GSDeviceVK::CheckFeatures()
// Fbfetch is useless if we don't have barriers enabled.
m_features.framebuffer_fetch &= m_features.texture_barrier;
// The Vulkan spelling of framebuffer fetch *is* rasterization-order attachment access, whose
// contract is that overlapping fragments in one draw observe each other in primitive order.
// So the ordering a full barrier would provide is already guaranteed, and keeping the barrier
// would only reintroduce the render-pass breaks this path exists to avoid.
//
// Derived AFTER every write to framebuffer_fetch above, including the RT-copy workaround's
// texture_barrier mask. Deriving it beside the first assignment left the pair disagreeing on
// Adreno — no fetch, but "fetch orders overlap" still true — which is inert only for as long as
// every reader sits inside an `if (features.framebuffer_fetch)` gate, as DetermineBarriers
// currently does. Keep this the last word on the bit rather than relying on that.
//
// ⚠️ Delivering that contract for an in-pass SELF-read is a separate question from ordering, and
// Turnip answers the two differently depending on tiling. Read against mesa-26.1.2: the tiled
// path sets GRAS_SC_CNTL.SINGLE_PRIM_MODE = FLUSH_PER_OVERLAP under rasterization-order access,
// which the a6xx register docs define as waiting for any overlapping primitive prior to the
// current one — the ordering really is requested. The stronger FLUSH_PER_OVERLAP_AND_OVERWRITE,
// whose documented extra guarantee is that UCHE and CCU stay in sync "when fetching the previous
// value for the current pixel", is only ever set on the UNTILED sysmem path (and there a feedback
// loop alone is enough to get it). The device agrees: forcing ROAA on every pipeline changed
// nothing while tiled, and the same draw came out correct under TU_DEBUG=sysmem. So what fails
// on Adreno is read VISIBILITY while tiled — writes sit unresolved in GMEM while the fetch goes
// out through UCHE — not primitive ordering. Adreno never reaches this line with fetch enabled,
// so nothing here depends on it; a tiler whose fetch is a genuine tile-local read is a different
// case and is not implicated by any of the above.
m_features.framebuffer_fetch_orders_overlap = m_features.framebuffer_fetch;
// Mali Vulkan stacks frequently report dualSrcBlend=false. When absent, GSRendererHW SW-blends
// the specific draws that need SRC1 instead of relying on a global high blending-accuracy level
// (which is why Mali no longer needs Blending=Max by hand). Ported from sashkinbro/EmuCoreX.