[Feature, Test] (SelfTest): probe the four remaining known driver bugs from the POST

This commit is contained in:
2026-08-22 08:45:05 -04:00
parent a5f36c8f8d
commit dcb568d445
3 changed files with 1876 additions and 14 deletions
+118 -3
View File
@@ -33,9 +33,7 @@ namespace MobileGL::MG_Util::SelfTest {
//
// ADDING A SIBLING IS ONE FUNCTION: write an `Optional<DriverBugFinding> ProbeXxx(gl)`
// that returns nullopt when the driver is not affected, and add it to the table in
// CollectGlesKnownDriverBugs(). Known siblings still to be probed: the R32F-MSAA swizzle
// corruption, the image-location-per-name link limit, the cross-stage qualifier merge, and
// the coherency residual.
// CollectGlesKnownDriverBugs().
// What MobileGL can do about a bug this device HAS. There is deliberately no "not
// affected" member: a driver that passes the probe produces no finding at all, so the
@@ -78,6 +76,123 @@ namespace MobileGL::MG_Util::SelfTest {
// ProbeGeometryStageSsboWriteAfterEmitDropped(), evaluated at most once per process.
Bool GeometryStageSsboWriteAfterEmitDropped(const MG_External::GLESFunctionsTable& gl);
// Samples one R32F GL_TEXTURE_2D_MULTISAMPLE texel through a swizzled alpha channel, twice,
// with a separately linked program each time. Returns true only when the swizzled read goes
// wrong while every control read stays right.
//
// Adreno 830 returns uninitialised memory - a different value every run - for
// texelFetch(sampler2DMS, ..., sampleIndex != 0).w on an R32F multisample texture whose
// GL_TEXTURE_SWIZZLE_A is not the default, from the SECOND such program in the context
// onward. The first program reads correctly, which is why the probe links two.
//
// THREE CONTROLS, each identical to the subject but for one variable, and all three must
// read correctly for a wrong subject to count: (1) the same fetch with
// GL_TEXTURE_SWIZZLE_A left at its default, (2) the same fetch at sample index 0, and
// (3) the same swizzled texture read through .x instead of .w. Without them a driver that
// simply cannot render R32F, or cannot sample multisample textures at all, would be
// reported as having this very specific corruption.
//
// Returns false when the driver cannot host the shape (no multisample R32F colour target,
// fewer than two samples, a missing entry point, an incomplete framebuffer): an
// inconclusive probe must never be reported as a bug. Restores every piece of GL state it
// touches.
Bool ProbeR32FMultisampleSwizzleCorruption(const MG_External::GLESFunctionsTable& gl);
// ProbeR32FMultisampleSwizzleCorruption(), evaluated at most once per process.
Bool R32FMultisampleSwizzleCorrupted(const MG_External::GLESFunctionsTable& gl);
// What the image-location budget probe measured. `detected` is the only field the verdict
// depends on; the rest exist so the report can say what the shape was instead of asserting
// a number that was true on one device in one campaign.
struct ImageLocationBudgetMeasurement {
Bool detected = false;
// Image uniforms declared per stage in both the subject and the control - one more than
// GL_MAX_GEOMETRY_IMAGE_UNIFORMS, which is the smallest of the three stages' budgets.
Int perStageImageUniforms = 0;
// Distinct uniform NAMES in the subject (per-stage-unique) and in the control (shared).
Int subjectDistinctNames = 0;
Int controlDistinctNames = 0;
// The first line of the driver's info log for the failing link, so the report quotes the
// driver rather than paraphrasing it.
String driverMessage;
};
// Links the same three-stage (vertex, geometry, fragment) program twice: once with every
// stage naming its image uniforms uniquely, once with all three stages sharing one set of
// names. Both declare the same number of image uniforms per stage, on the same bindings,
// with the same qualifier and the same stores - the names are the only difference.
//
// Adreno 830 charges its image-location budget per distinct NAME, so the shared-name program
// links while the per-stage-named one is rejected with "Image location or component exceeds
// max allowed", even though nothing about the image USAGE changed. That is what makes the
// shared-name link the control: it proves the driver can host this exact amount of image
// work and that only the naming moved the answer.
//
// `detected` is false unless the subject fails AND the control links. Both failing means the
// shape is simply too large for the driver (an honest refusal); both linking means the
// driver does not have this bug.
ImageLocationBudgetMeasurement ProbeImageLocationPerNameBudget(const MG_External::GLESFunctionsTable& gl);
// ProbeImageLocationPerNameBudget(), evaluated at most once per process.
const ImageLocationBudgetMeasurement& ImageLocationPerNameBudget(const MG_External::GLESFunctionsTable& gl);
// Draws one quad whose vertex stage stores to a `coherent writeonly` image and whose
// fragment stage reads the same image declared `coherent readonly` under the SAME name, then
// checks every fragment saw the store. Returns true only when the same-name program loses
// the store while the different-name control keeps it.
//
// Adreno 830 merges the two declarations into one uniform and silently discards the writing
// stage's stores. The control is the identical pair of shaders with the two halves renamed -
// exactly what MobileGL's image-uniform repair emits - which keeps every store. Without it
// the probe would be indistinguishable from "this driver cannot store to images from the
// vertex stage", which is a different and much larger claim.
//
// Returns false when the driver advertises no vertex-stage image uniforms, when an entry
// point is missing, or when the setup fails.
Bool ProbeCrossStageImageQualifierMergeDropsWrites(const MG_External::GLESFunctionsTable& gl);
// ProbeCrossStageImageQualifierMergeDropsWrites(), evaluated at most once per process.
Bool CrossStageImageQualifierMergeDropsWrites(const MG_External::GLESFunctionsTable& gl);
// What the image coherency probe measured. The residual is reported rather than hard-coded:
// it is a rate, it differs between devices, and a report that quotes a number measured
// somewhere else is worse than no number at all.
struct ImageCoherencyResidualMeasurement {
Bool detected = false;
// Texels the STRONGEST in-shader shape missed - that is what makes the defect unfixable.
Int mismatchedTexels = 0;
// Texels the shape MobileGL emits today missed, on the same driver in the same run. It
// is what applications actually get, and it is not always the same number.
Int emittedShapeMismatchedTexels = 0;
Int totalTexels = 0;
};
// Counts the texels whose dependent imageLoad() did not observe the imageStore() that
// precedes it in the same fragment invocation.
//
// THE SUBJECT IS THE STRONGEST SHAPE THE LANGUAGE OFFERS - a `coherent volatile`
// readonly/writeonly pair on one binding with BOTH memoryBarrierImage() and memoryBarrier()
// between the store and the read - and that choice is the whole reason the row can say
// "unfixable". Probing only the shape MobileGL emits today (`coherent` plus
// memoryBarrierImage()) reports a bug on drivers where simply adding `volatile` makes the
// read correct, which is a defect MobileGL could fix rather than one it cannot: measured on
// Mesa llvmpipe, the emitted shape misses every texel while the `volatile` shape misses
// none. Only a driver that fails even the strongest shape has no in-shader substitute left.
//
// The control is the same dependency split across TWO draws with a glMemoryBarrier and a
// glFinish between them. It separates "this driver cannot make image writes visible at all"
// (control also dirty - a far worse defect, and the probe declines to call it this one) from
// the finding, which is about ordering inside one invocation.
//
// `detected` is false unless the strongest shape is dirty AND the control is clean. The
// shape MobileGL emits is measured either way, so the report can say what applications get.
ImageCoherencyResidualMeasurement ProbeImageWriteReadCoherencyResidual(
const MG_External::GLESFunctionsTable& gl);
// ProbeImageWriteReadCoherencyResidual(), evaluated at most once per process.
const ImageCoherencyResidualMeasurement& ImageWriteReadCoherencyResidual(
const MG_External::GLESFunctionsTable& gl);
// Every known driver bug this GLES driver actually has. Bugs it does not have are absent,
// so an unaffected device renders an empty section rather than a wall of "not affected".
Vector<DriverBugFinding> CollectGlesKnownDriverBugs(const MG_External::GLESFunctionsTable& gl);