[Fix] (SelfTest): stop optional-capability failures from declaring the whole backend unsupported, and log the POST report in chunks

This commit is contained in:
2026-08-22 09:08:13 -04:00
parent 2f62970dd5
commit 666f150202
2 changed files with 40 additions and 5 deletions
+18 -4
View File
@@ -702,8 +702,11 @@ namespace MobileGL::MG_Util::SelfTest {
// Every emit carries the extension-presence fact the old standalone
// GL_EXT_disjoint_timer_query row showed, plus the probe outcome.
const String extensionPresent = "GL_EXT_disjoint_timer_query extension present";
// FailOptional: a driver that advertises the extension and then cannot serve a
// query is broken in a way nothing substitutes for, but timing GPU work is not
// something the backend needs in order to run.
const auto fail = [&](const String& detail) {
builder.Fail("Timer queries", extensionPresent + "; but " + detail + disabledNote);
builder.FailOptional("Timer queries", extensionPresent + "; but " + detail + disabledNote);
};
if (!glesFuncs.glGenQueries || !glesFuncs.glDeleteQueries || !glesFuncs.glBeginQuery ||
@@ -837,8 +840,11 @@ namespace MobileGL::MG_Util::SelfTest {
const String pathNote = native ? "GL_NV_shader_noperspective_interpolation present (native path)"
: "GL_NV_shader_noperspective_interpolation absent (gl_Position.w / "
"gl_FragCoord.w emulation path)";
// FailOptional: a shaderpack that declares a noperspective varying renders it wrong
// and nothing stands in for the interpolation, but everything that does not use one
// is unaffected, so the backend still runs.
const auto fail = [&](const String& detail) {
builder.Fail("noperspective interpolation", pathNote + "; " + detail);
builder.FailOptional("noperspective interpolation", pathNote + "; " + detail);
};
if (!g.glCreateShader || !g.glShaderSource || !g.glCompileShader || !g.glGetShaderiv ||
@@ -1336,8 +1342,10 @@ namespace MobileGL::MG_Util::SelfTest {
const String timestampFacts =
format("timestampValidBits = {} on the graphics queue family; timestampPeriod = {} ns per tick",
timestampValidBits, timestampPeriod);
// FailOptional, for the same reason as the GLES row: the backend does not need to
// time GPU work in order to run.
const auto fail = [&](const String& detail) {
builder.Fail("Timer queries", timestampFacts + "; but " + detail + disabledNote);
builder.FailOptional("Timer queries", timestampFacts + "; but " + detail + disabledNote);
};
const auto vkCreateDeviceFn =
reinterpret_cast<PFN_vkCreateDevice>(getInstanceProcAddr(instance, "vkCreateDevice"));
@@ -1557,7 +1565,13 @@ namespace MobileGL::MG_Util::SelfTest {
Bool subgroupPropertiesAvailable,
const VkPhysicalDeviceSubgroupProperties& subgroupProperties) {
constexpr const char* RowName = "Subgroup first-reduction witness";
const auto fail = [&](String detail) { builder.Fail(RowName, Move(detail)); };
// FailOptional, not Fail. The witness reports whether the NATIVE subgroup
// first-reduction works; when it does not, the renderer takes its non-subgroup
// iteration path and draws the same image. Both an Adreno 830 and Mesa lavapipe
// fail this row's topology check today while running the DirectVulkan backend
// perfectly well, so a fatal verdict here would have the screen announce that a
// backend the user is looking at through that very backend cannot run.
const auto fail = [&](String detail) { builder.FailOptional(RowName, Move(detail)); };
if (!subgroupPropertiesAvailable) {
fail("vkGetPhysicalDeviceProperties2 could not provide raw Vulkan subgroup properties");
@@ -239,13 +239,34 @@ public final class PostActivity extends Activity {
nativeLoaded = true;
}
/**
* Writes the whole report to logcat. The report is the only machine-readable form of the
* POST, and logcat drops everything past roughly 4000 bytes of a single entry - which is
* less than one backend section, so a one-call log silently truncated the report to about
* the first dozen rows. Each chunk is prefixed with its index so a reader can reassemble
* them in order (concatenate the payloads after the "] " separator).
*/
private static void logReport(String json) {
if (json == null) {
Log.i(TAG, "<null report>");
return;
}
final int chunkSize = 3000;
final int chunks = (json.length() + chunkSize - 1) / chunkSize;
for (int index = 0; index < chunks; ++index) {
final int start = index * chunkSize;
final int end = Math.min(start + chunkSize, json.length());
Log.i(TAG, "[" + (index + 1) + "/" + chunks + "] " + json.substring(start, end));
}
}
private static void runDriverPost() {
String json = null;
Throwable failure = null;
try {
ensureNativeLoaded();
json = nativeRunDriverPost();
Log.i(TAG, json == null ? "<null report>" : json);
logReport(json);
} catch (Throwable error) {
Log.e(TAG, "Driver POST failed", error);
failure = error;