mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
GS/OpenGL: ARM framebuffer fetch does order overlapping primitives
d8e2741234 stopped dropping the full barrier under framebuffer fetch when a
draw's primitives overlap, because fetch replaces the destination read without
necessarily ordering fragments. That is true of the EXT extension and it is
where the defect was measured -- Mesa 25.3.6 / Apple M2, 18% of an MGS3 frame
changing between identical replays. The conclusion was then written as
framebuffer_fetch_orders_overlap = false for the whole GL backend.
ARM_shader_framebuffer_fetch guarantees the opposite. Its spec: "when an
individual sample is covered by multiple primitives, rendering for that sample
is performed sequentially in the order in which the primitives were submitted",
and a read of gl_LastFragColorARM "must wait for the processing of all previous
fragments destined for the current pixel to complete". That is the same contract
Vulkan's rasterization-order attachment access and Metal's programmable blending
provide, and both of those keep the barrier-free path.
Every Mali device on Android takes the ARM path -- it is the only one that
works there, which is why the Mali profile selects it even when EXT is also
advertised. So the blanket answer put the entire Mali install base on a split
draw, one draw call per primitive group, for every overlapping blended draw.
That is a large part of what made 2.6.6.5 slower than 2.6.6.4 on Mali, which is
the population that reported it.
So make the question per-extension, where it belongs: ARM orders, EXT does not.
A driver that violates the ARM guarantee is a driver bug and goes in the
driver-bug database as a fetch blocklist entry, which is the mechanism r44p1
already uses -- not a blanket rule that also penalises every healthy Mali.
Vulkan, Metal and the EXT path are all unchanged. 21/21 policy tests.
This commit is contained in:
@@ -1407,7 +1407,7 @@ public:
|
||||
bool dxt_textures : 1; ///< Supports DXTn texture compression, i.e. S3TC and BC1-3.
|
||||
bool bptc_textures : 1; ///< Supports BC6/7 texture compression.
|
||||
bool framebuffer_fetch : 1; ///< Can sample from the framebuffer without texture barriers.
|
||||
bool framebuffer_fetch_orders_overlap : 1; ///< Framebuffer fetch also orders overlapping primitives *within* a single draw, so a full barrier is redundant. Vulkan's rasterization-order attachment access and Metal's programmable blending guarantee this; the GL fetch extensions do not deliver it in practice.
|
||||
bool framebuffer_fetch_orders_overlap : 1; ///< Framebuffer fetch also orders overlapping primitives *within* a single draw, so a full barrier is redundant. Vulkan's rasterization-order attachment access, Metal's programmable blending and GL's ARM_shader_framebuffer_fetch all guarantee this by spec; GL's EXT_shader_framebuffer_fetch does not deliver it in practice.
|
||||
bool stencil_buffer : 1; ///< Supports stencil buffer, and can use for DATE.
|
||||
bool cas_sharpening : 1; ///< Supports sufficient functionality for contrast adaptive sharpening.
|
||||
bool test_and_sample_depth: 1; ///< Supports concurrently binding the depth-stencil buffer for sampling and depth testing.
|
||||
|
||||
@@ -89,6 +89,38 @@ constexpr GSFramebufferFetchDecision DecideGLFramebufferFetch(bool has_arm_fetch
|
||||
return decision;
|
||||
}
|
||||
|
||||
// Whether a GL framebuffer-fetch backend also orders overlapping primitives within one draw.
|
||||
//
|
||||
// This is a per-EXTENSION property, not a per-API one, and conflating the two cost every Mali
|
||||
// device a large amount of performance in 2.6.6.5.
|
||||
//
|
||||
// ARM_shader_framebuffer_fetch is the coherent spelling. Its spec is explicit: "when an individual
|
||||
// sample is covered by multiple primitives, rendering for that sample is performed sequentially in
|
||||
// the order in which the primitives were submitted", and a read of gl_LastFragColorARM "must wait
|
||||
// for the processing of all previous fragments destined for the current pixel to complete". That
|
||||
// is the same contract Vulkan's rasterization-order attachment access and Metal's programmable
|
||||
// blending provide, so the ARM path earns the same barrier-free treatment they get. It is also
|
||||
// what the hardware does anyway -- a tiler blends in tile memory in primitive order.
|
||||
//
|
||||
// EXT_shader_framebuffer_fetch does not earn it. Measured on Mesa 25.3.6 / Apple M2 through the
|
||||
// EXT path, a 640x480 MGS3 frame came out 101x further from the software rasteriser than the
|
||||
// RT-copy path (76872 pixels wrong by >=8 against 759), and 18% of the frame changed between
|
||||
// identical replays -- the nondeterminism is what identified it as an ordering failure rather than
|
||||
// an arithmetic one. So EXT keeps its barrier.
|
||||
//
|
||||
// ⚠️ The measurement above was taken on ONE driver through the EXT extension, and the conclusion
|
||||
// drawn from it was applied to all of GL, including ARM's extension, which guarantees the opposite.
|
||||
// A driver that violates the ARM guarantee is a driver bug and belongs in the driver-bug database
|
||||
// as a fetch blocklist entry (see UseRenderTargetCopyForFeedback), not in a blanket rule here --
|
||||
// that is the mechanism r44p1 already uses.
|
||||
constexpr bool FbFetchOrdersOverlappingPrims(GSFramebufferFetchBackend backend)
|
||||
{
|
||||
return backend == GSFramebufferFetchBackend::ARM;
|
||||
}
|
||||
static_assert(FbFetchOrdersOverlappingPrims(GSFramebufferFetchBackend::ARM));
|
||||
static_assert(!FbFetchOrdersOverlappingPrims(GSFramebufferFetchBackend::EXT));
|
||||
static_assert(!FbFetchOrdersOverlappingPrims(GSFramebufferFetchBackend::None));
|
||||
|
||||
// Whether a draw may drop its barrier requirement because framebuffer fetch is available.
|
||||
//
|
||||
// Fetch replaces the destination READ. Whether it also orders overlapping primitives WITHIN one
|
||||
@@ -97,16 +129,14 @@ constexpr GSFramebufferFetchDecision DecideGLFramebufferFetch(bool has_arm_fetch
|
||||
// ("on fbfetch, one barrier is like full barrier") and asks for a full barrier to get the
|
||||
// per-primitive ordering that path needs. Dropping the barrier on the same reasoning removed the
|
||||
// mechanism that supplied the ordering, so a primitive blended against a destination its
|
||||
// predecessor had not written yet. It raced: on Mesa 25.3.6 / Apple M2, a 640x480 MGS3 frame came
|
||||
// out 101x further from the software rasteriser than the RT-copy path (76872 pixels wrong by >=8
|
||||
// against 759), and 18% of the frame changed between identical replays.
|
||||
// predecessor had not written yet.
|
||||
//
|
||||
// Vulkan's rasterization-order attachment access and Metal's programmable blending guarantee the
|
||||
// ordering by contract, so they still drop the barrier and keep their render passes intact. The
|
||||
// GL fetch extensions do not deliver it in practice. Where the draw's own primitives do not
|
||||
// overlap the question is moot -- a live in-tile read and a pre-draw snapshot are the same value --
|
||||
// so the barrier is dropped there on every backend, which covered 76 of the 84 affected draws in
|
||||
// that frame.
|
||||
// Vulkan's rasterization-order attachment access, Metal's programmable blending and GL's ARM
|
||||
// extension all guarantee the ordering by contract, so they drop the barrier and keep their render
|
||||
// passes intact; the GL EXT path does not (see FbFetchOrdersOverlappingPrims). Where the draw's own
|
||||
// primitives do not overlap the question is moot -- a live in-tile read and a pre-draw snapshot are
|
||||
// the same value -- so the barrier is dropped there on every backend, which covered 76 of the 84
|
||||
// affected draws in that frame.
|
||||
//
|
||||
// `prims_may_overlap` must be true when overlap is merely unknown; guessing "no" is what costs
|
||||
// correctness, and guessing "yes" costs only a split draw.
|
||||
|
||||
@@ -1072,11 +1072,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;
|
||||
// Whether fetch also orders overlapping primitives within one draw is a property of the
|
||||
// extension, not of the API: ARM's guarantees it by spec and EXT's does not (see
|
||||
// FbFetchOrdersOverlappingPrims). Blanket-false here is what put every Mali device on a
|
||||
// per-primitive split draw for overlapping blends in 2.6.6.5.
|
||||
m_features.framebuffer_fetch_orders_overlap = FbFetchOrdersOverlappingPrims(fbfetch.backend);
|
||||
|
||||
switch (fbfetch.veto)
|
||||
{
|
||||
|
||||
@@ -236,6 +236,55 @@ TEST(GSFramebufferFetchPolicy, DroppingBarriersNeverDependsOnOverlapAloneWithout
|
||||
}
|
||||
}
|
||||
|
||||
// Which GL fetch extension orders overlapping primitives (FbFetchOrdersOverlappingPrims).
|
||||
//
|
||||
// The regression these pin: the ordering question was answered "no" for the whole GL API, on a
|
||||
// measurement taken through the EXT extension on one desktop driver. ARM's extension guarantees
|
||||
// the opposite in its spec, and every Mali device on Android takes the ARM path, so the blanket
|
||||
// answer put the entire Mali install base on a split draw -- one draw call per primitive group --
|
||||
// for every overlapping blended draw. That is the 2.6.6.5 slowdown.
|
||||
|
||||
TEST(GSFramebufferFetchPolicy, ArmFetchOrdersOverlappingPrimitives)
|
||||
{
|
||||
// ARM_shader_framebuffer_fetch spec: "when an individual sample is covered by multiple
|
||||
// primitives, rendering for that sample is performed sequentially in the order in which the
|
||||
// primitives were submitted", and a gl_LastFragColorARM read "must wait for the processing of
|
||||
// all previous fragments destined for the current pixel to complete". That is the same
|
||||
// contract Vulkan and Metal provide, so it earns the same barrier-free path.
|
||||
EXPECT_TRUE(FbFetchOrdersOverlappingPrims(GSFramebufferFetchBackend::ARM));
|
||||
EXPECT_TRUE(FbFetchDropsDrawBarriers(
|
||||
FbFetchOrdersOverlappingPrims(GSFramebufferFetchBackend::ARM), /*prims_may_overlap=*/true, false));
|
||||
}
|
||||
|
||||
TEST(GSFramebufferFetchPolicy, ExtFetchDoesNotOrderOverlappingPrimitives)
|
||||
{
|
||||
// The EXT path is where the reordering was actually measured (Mesa 25.3.6 / Apple M2: 18% of an
|
||||
// MGS3 frame changing between identical replays). It keeps its barrier.
|
||||
EXPECT_FALSE(FbFetchOrdersOverlappingPrims(GSFramebufferFetchBackend::EXT));
|
||||
EXPECT_FALSE(FbFetchDropsDrawBarriers(
|
||||
FbFetchOrdersOverlappingPrims(GSFramebufferFetchBackend::EXT), /*prims_may_overlap=*/true, false));
|
||||
}
|
||||
|
||||
TEST(GSFramebufferFetchPolicy, AVetoedFetchNeverClaimsAnOrderingGuarantee)
|
||||
{
|
||||
// A device that lost fetch has no in-tile read at all, so it cannot be claiming to order
|
||||
// anything with one. Swept over the policy's own vetoes so a new one cannot miss this: the
|
||||
// r44p1 blocklist is exactly a driver that violates the ARM guarantee, and the database entry
|
||||
// is where that belongs -- not a blanket rule that also penalises every healthy Mali.
|
||||
for (bool blocklisted : {false, true})
|
||||
{
|
||||
for (bool user_disabled : {false, true})
|
||||
{
|
||||
const GSFramebufferFetchDecision d = Decide({.arm = true, .ext = true,
|
||||
.blocklisted = blocklisted, .user_disabled = user_disabled, .mali_profile = true});
|
||||
SCOPED_TRACE(testing::Message() << "blocklisted=" << blocklisted
|
||||
<< " user_disabled=" << user_disabled);
|
||||
|
||||
EXPECT_EQ(FbFetchOrdersOverlappingPrims(d.backend), d.enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The blend-fallback shape (GLUsesPerPrimitiveFbCopy).
|
||||
//
|
||||
// Third face of the same coupling. Turning fetch off answers "how do we read the destination?" but
|
||||
|
||||
Reference in New Issue
Block a user