mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Test] (SelfTest): model the vertex-input-location and layered-blit defects in the fake driver and pin both probes' controls
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
#include <MG_Util/SelfTest/DriverBugProbes.h>
|
#include <MG_Util/SelfTest/DriverBugProbes.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <map>
|
#include <map>
|
||||||
@@ -24,6 +25,8 @@ using MobileGL::MG_Util::SelfTest::ProbeCrossStageImageQualifierMergeDropsWrites
|
|||||||
using MobileGL::MG_Util::SelfTest::ProbeGeometryStageSsboWriteAfterEmitDropped;
|
using MobileGL::MG_Util::SelfTest::ProbeGeometryStageSsboWriteAfterEmitDropped;
|
||||||
using MobileGL::MG_Util::SelfTest::ProbeImageLocationPerNameBudget;
|
using MobileGL::MG_Util::SelfTest::ProbeImageLocationPerNameBudget;
|
||||||
using MobileGL::MG_Util::SelfTest::ProbeImageWriteReadCoherencyResidual;
|
using MobileGL::MG_Util::SelfTest::ProbeImageWriteReadCoherencyResidual;
|
||||||
|
using MobileGL::MG_Util::SelfTest::ProbeBlitIgnoresDestinationArrayLayer;
|
||||||
|
using MobileGL::MG_Util::SelfTest::ProbeExplicitVertexInputLocationCeiling;
|
||||||
using MobileGL::MG_Util::SelfTest::ProbeR32FMultisampleSwizzleCorruption;
|
using MobileGL::MG_Util::SelfTest::ProbeR32FMultisampleSwizzleCorruption;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -46,6 +49,13 @@ namespace {
|
|||||||
// CollectGlesKnownDriverBugs(): the collector goes through the once-per-process memos, and a
|
// CollectGlesKnownDriverBugs(): the collector goes through the once-per-process memos, and a
|
||||||
// memo latched by one test would decide the answer for every later one.
|
// memo latched by one test would decide the answer for every later one.
|
||||||
|
|
||||||
|
// The exact text an affected Adreno driver puts in the compile log when it refuses a
|
||||||
|
// vertex input's layout(location = N). Quoted rather than paraphrased for the same reason the
|
||||||
|
// link log below is: the report shows it to a human, so a probe that stopped capturing it
|
||||||
|
// would stop being useful long before it stopped detecting.
|
||||||
|
const char* const kAttributeRangeCompileLog =
|
||||||
|
"ERROR: 0:2: '' : the location is not within attribute range [0, MAX_ATTRIBUTES-1] \nERROR: 1 compilation errors. No code generated.";
|
||||||
|
|
||||||
// The exact text an affected Adreno driver puts in the info log for this refusal.
|
// The exact text an affected Adreno driver puts in the info log for this refusal.
|
||||||
const char* const kImageLocationLinkLog =
|
const char* const kImageLocationLinkLog =
|
||||||
"Error: Image Image location or component exceeds max allowed.\nError: Linking failed.";
|
"Error: Image Image location or component exceeds max allowed.\nError: Linking failed.";
|
||||||
@@ -82,6 +92,21 @@ namespace {
|
|||||||
int coherencyEmittedShapeFailedTexels = 0;
|
int coherencyEmittedShapeFailedTexels = 0;
|
||||||
int coherencyControlFailedTexels = 0;
|
int coherencyControlFailedTexels = 0;
|
||||||
|
|
||||||
|
// Probe 5: GL_MAX_VERTEX_ATTRIBS, and the two separate ceilings the probe has to tell
|
||||||
|
// apart - how high `layout(location = N)` may go in the ESSL compiler, and how high
|
||||||
|
// glBindAttribLocation may go at link. On an unaffected driver both are above the
|
||||||
|
// advertised count.
|
||||||
|
GLint maxVertexAttribs = 32;
|
||||||
|
int explicitLocationCeiling = 1000;
|
||||||
|
int bindAttribLocationCeiling = 1000;
|
||||||
|
// Probe 5's inconclusive path: nothing compiles, including the location-0 control.
|
||||||
|
bool everyCompileFails = false;
|
||||||
|
// Probe 6: a blit writes the destination array layer the framebuffer names, or always
|
||||||
|
// layer 0. The second knob is the inconclusive path - a driver that does not honour the
|
||||||
|
// SOURCE layer either fails the probe's control.
|
||||||
|
bool blitIgnoresDestinationLayer = false;
|
||||||
|
bool blitIgnoresSourceLayer = false;
|
||||||
|
|
||||||
// ---- object bookkeeping ---------------------------------------------
|
// ---- object bookkeeping ---------------------------------------------
|
||||||
GLenum pendingError = GL_NO_ERROR;
|
GLenum pendingError = GL_NO_ERROR;
|
||||||
GLuint nextShaderId = 1;
|
GLuint nextShaderId = 1;
|
||||||
@@ -102,6 +127,19 @@ namespace {
|
|||||||
// texture id -> GL_TEXTURE_SWIZZLE_A
|
// texture id -> GL_TEXTURE_SWIZZLE_A
|
||||||
std::map<GLuint, GLenum> multisampleAlphaSwizzle;
|
std::map<GLuint, GLenum> multisampleAlphaSwizzle;
|
||||||
|
|
||||||
|
std::map<GLuint, bool> shaderCompiled;
|
||||||
|
std::map<GLuint, std::string> shaderInfoLogs;
|
||||||
|
// program -> (attribute name -> location) as glBindAttribLocation left it.
|
||||||
|
std::map<GLuint, std::map<std::string, GLint>> boundAttribLocations;
|
||||||
|
// 2D array texture id -> the byte every texel of each layer holds. Two layers is all the
|
||||||
|
// layered-blit probe uses, and one byte per layer is all it distinguishes.
|
||||||
|
std::map<GLuint, std::array<GLubyte, 2>> arrayLayerFill;
|
||||||
|
// framebuffer id -> the (2D array texture, layer) glFramebufferTextureLayer attached.
|
||||||
|
std::map<GLuint, std::pair<GLuint, GLint>> framebufferLayerAttachment;
|
||||||
|
GLuint boundArrayTexture = 0;
|
||||||
|
GLuint boundDrawFramebuffer = 0;
|
||||||
|
GLuint boundReadFramebuffer = 0;
|
||||||
|
|
||||||
GLuint boundMultisampleTexture = 0;
|
GLuint boundMultisampleTexture = 0;
|
||||||
GLuint currentProgram = 0;
|
GLuint currentProgram = 0;
|
||||||
// How many programs that sample a multisample texture have been linked so far. The
|
// How many programs that sample a multisample texture have been linked so far. The
|
||||||
@@ -149,6 +187,22 @@ namespace {
|
|||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The N in `layout(location = N) in ...`, or -1 when the source declares no such input.
|
||||||
|
// Read off the text the probe actually submitted, so a probe that stopped emitting the
|
||||||
|
// qualifier would stop being detected here too.
|
||||||
|
int ExplicitVertexInputLocationIn(const std::string& source) {
|
||||||
|
const std::size_t at = source.find("layout(location = ");
|
||||||
|
if (at == std::string::npos) return -1;
|
||||||
|
const std::size_t start = at + std::strlen("layout(location = ");
|
||||||
|
const std::size_t close = source.find(')', start);
|
||||||
|
if (close == std::string::npos) return -1;
|
||||||
|
// Only a VERTEX INPUT counts: `layout(location = 0) out vec4` is a different declaration
|
||||||
|
// and no driver caps it against GL_MAX_VERTEX_ATTRIBS.
|
||||||
|
const std::size_t declaration = source.find_first_not_of(" \t", close + 1);
|
||||||
|
if (declaration == std::string::npos || source.compare(declaration, 3, "in ") != 0) return -1;
|
||||||
|
return std::atoi(source.c_str() + start);
|
||||||
|
}
|
||||||
|
|
||||||
std::string StageSourceContaining(GLuint program, const char* needle) {
|
std::string StageSourceContaining(GLuint program, const char* needle) {
|
||||||
const auto attached = g_fake.programShaders.find(program);
|
const auto attached = g_fake.programShaders.find(program);
|
||||||
if (attached == g_fake.programShaders.end()) return {};
|
if (attached == g_fake.programShaders.end()) return {};
|
||||||
@@ -217,6 +271,9 @@ namespace {
|
|||||||
case GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS:
|
case GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS:
|
||||||
*data = g_fake.maxGeometrySsboBlocks;
|
*data = g_fake.maxGeometrySsboBlocks;
|
||||||
break;
|
break;
|
||||||
|
case GL_MAX_VERTEX_ATTRIBS:
|
||||||
|
*data = g_fake.maxVertexAttribs;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -252,16 +309,41 @@ namespace {
|
|||||||
}
|
}
|
||||||
g_fake.shaderSources[shader] = std::move(source);
|
g_fake.shaderSources[shader] = std::move(source);
|
||||||
};
|
};
|
||||||
funcs.glCompileShader = [](GLuint) {};
|
funcs.glCompileShader = [](GLuint shader) {
|
||||||
funcs.glGetShaderiv = [](GLuint, GLenum pname, GLint* params) {
|
const std::string& source = SourceOf(shader);
|
||||||
if (pname == GL_COMPILE_STATUS) *params = GL_TRUE;
|
const int location = ExplicitVertexInputLocationIn(source);
|
||||||
|
const bool refused =
|
||||||
|
g_fake.everyCompileFails || (location >= 0 && location >= g_fake.explicitLocationCeiling);
|
||||||
|
g_fake.shaderCompiled[shader] = !refused;
|
||||||
|
g_fake.shaderInfoLogs[shader] = refused ? kAttributeRangeCompileLog : "";
|
||||||
};
|
};
|
||||||
funcs.glGetShaderInfoLog = [](GLuint, GLsizei bufSize, GLsizei*, GLchar* infoLog) {
|
funcs.glGetShaderiv = [](GLuint shader, GLenum pname, GLint* params) {
|
||||||
if (bufSize > 0) infoLog[0] = '\0';
|
if (pname != GL_COMPILE_STATUS) return;
|
||||||
|
const auto it = g_fake.shaderCompiled.find(shader);
|
||||||
|
*params = (it == g_fake.shaderCompiled.end() || it->second) ? GL_TRUE : GL_FALSE;
|
||||||
|
};
|
||||||
|
funcs.glGetShaderInfoLog = [](GLuint shader, GLsizei bufSize, GLsizei*, GLchar* infoLog) {
|
||||||
|
if (bufSize <= 0) return;
|
||||||
|
const auto it = g_fake.shaderInfoLogs.find(shader);
|
||||||
|
const std::string& log = it == g_fake.shaderInfoLogs.end() ? std::string() : it->second;
|
||||||
|
const GLsizei copied = static_cast<GLsizei>(
|
||||||
|
std::min<std::size_t>(log.size(), static_cast<std::size_t>(bufSize - 1)));
|
||||||
|
std::memcpy(infoLog, log.data(), static_cast<std::size_t>(copied));
|
||||||
|
infoLog[copied] = '\0';
|
||||||
};
|
};
|
||||||
funcs.glDeleteShader = [](GLuint shader) {
|
funcs.glDeleteShader = [](GLuint shader) {
|
||||||
if (shader != 0) --g_fake.aliveShaders;
|
if (shader != 0) --g_fake.aliveShaders;
|
||||||
};
|
};
|
||||||
|
funcs.glBindAttribLocation = [](GLuint program, GLuint index, const GLchar* name) {
|
||||||
|
g_fake.boundAttribLocations[program][name] = static_cast<GLint>(index);
|
||||||
|
};
|
||||||
|
funcs.glGetAttribLocation = [](GLuint program, const GLchar* name) -> GLint {
|
||||||
|
const auto programEntry = g_fake.boundAttribLocations.find(program);
|
||||||
|
if (programEntry == g_fake.boundAttribLocations.end()) return -1;
|
||||||
|
const auto nameEntry = programEntry->second.find(name);
|
||||||
|
if (nameEntry == programEntry->second.end()) return -1;
|
||||||
|
return nameEntry->second >= g_fake.bindAttribLocationCeiling ? -1 : nameEntry->second;
|
||||||
|
};
|
||||||
funcs.glCreateProgram = []() -> GLuint {
|
funcs.glCreateProgram = []() -> GLuint {
|
||||||
++g_fake.alivePrograms;
|
++g_fake.alivePrograms;
|
||||||
return g_fake.nextProgramId++;
|
return g_fake.nextProgramId++;
|
||||||
@@ -272,7 +354,17 @@ namespace {
|
|||||||
funcs.glLinkProgram = [](GLuint program) {
|
funcs.glLinkProgram = [](GLuint program) {
|
||||||
const std::vector<std::string> names = DeclaredImageNames(program);
|
const std::vector<std::string> names = DeclaredImageNames(program);
|
||||||
const bool overBudget = static_cast<int>(names.size()) > g_fake.distinctImageNameBudget;
|
const bool overBudget = static_cast<int>(names.size()) > g_fake.distinctImageNameBudget;
|
||||||
g_fake.programLinked[program] = !overBudget;
|
// A driver whose glBindAttribLocation ceiling is lower than the location asked for
|
||||||
|
// refuses the LINK rather than the compile - which is the half of the vertex-input
|
||||||
|
// probe that decides whether the attribute is reachable another way at all.
|
||||||
|
bool attributeOutOfRange = false;
|
||||||
|
if (const auto it = g_fake.boundAttribLocations.find(program);
|
||||||
|
it != g_fake.boundAttribLocations.end()) {
|
||||||
|
for (const auto& [attributeName, location] : it->second) {
|
||||||
|
if (location >= g_fake.bindAttribLocationCeiling) attributeOutOfRange = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_fake.programLinked[program] = !overBudget && !attributeOutOfRange;
|
||||||
g_fake.programInfoLogs[program] = overBudget ? kImageLocationLinkLog : "";
|
g_fake.programInfoLogs[program] = overBudget ? kImageLocationLinkLog : "";
|
||||||
if (!overBudget && !StageSourceContaining(program, "texelFetch(mg_probeSampler").empty()) {
|
if (!overBudget && !StageSourceContaining(program, "texelFetch(mg_probeSampler").empty()) {
|
||||||
++g_fake.sampledMultisampleProgramCount;
|
++g_fake.sampledMultisampleProgramCount;
|
||||||
@@ -308,6 +400,20 @@ namespace {
|
|||||||
};
|
};
|
||||||
funcs.glBindTexture = [](GLenum target, GLuint texture) {
|
funcs.glBindTexture = [](GLenum target, GLuint texture) {
|
||||||
if (target == GL_TEXTURE_2D_MULTISAMPLE) g_fake.boundMultisampleTexture = texture;
|
if (target == GL_TEXTURE_2D_MULTISAMPLE) g_fake.boundMultisampleTexture = texture;
|
||||||
|
if (target == GL_TEXTURE_2D_ARRAY) g_fake.boundArrayTexture = texture;
|
||||||
|
};
|
||||||
|
funcs.glTexStorage3D = [](GLenum target, GLsizei, GLenum, GLsizei, GLsizei, GLsizei) {
|
||||||
|
if (target == GL_TEXTURE_2D_ARRAY) g_fake.arrayLayerFill[g_fake.boundArrayTexture] = {0, 0};
|
||||||
|
};
|
||||||
|
// One byte per layer: the layered-blit probe fills every texel of a layer with the same
|
||||||
|
// value and only ever asks which layer a value ended up on.
|
||||||
|
funcs.glTexSubImage3D = [](GLenum target, GLint, GLint, GLint, GLint zoffset, GLsizei, GLsizei,
|
||||||
|
GLsizei, GLenum, GLenum, const void* pixels) {
|
||||||
|
if (target != GL_TEXTURE_2D_ARRAY || pixels == nullptr) return;
|
||||||
|
auto& fill = g_fake.arrayLayerFill[g_fake.boundArrayTexture];
|
||||||
|
if (zoffset >= 0 && static_cast<std::size_t>(zoffset) < fill.size()) {
|
||||||
|
fill[static_cast<std::size_t>(zoffset)] = static_cast<const GLubyte*>(pixels)[0];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
funcs.glDeleteTextures = [](GLsizei n, const GLuint* textures) {
|
funcs.glDeleteTextures = [](GLsizei n, const GLuint* textures) {
|
||||||
for (GLsizei i = 0; i < n; ++i) {
|
for (GLsizei i = 0; i < n; ++i) {
|
||||||
@@ -332,12 +438,50 @@ namespace {
|
|||||||
++g_fake.aliveFramebuffers;
|
++g_fake.aliveFramebuffers;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
funcs.glBindFramebuffer = [](GLenum, GLuint) {};
|
funcs.glBindFramebuffer = [](GLenum target, GLuint framebuffer) {
|
||||||
|
if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) {
|
||||||
|
g_fake.boundDrawFramebuffer = framebuffer;
|
||||||
|
}
|
||||||
|
if (target == GL_FRAMEBUFFER || target == GL_READ_FRAMEBUFFER) {
|
||||||
|
g_fake.boundReadFramebuffer = framebuffer;
|
||||||
|
}
|
||||||
|
};
|
||||||
funcs.glFramebufferTexture2D = [](GLenum, GLenum, GLenum, GLuint, GLint) {};
|
funcs.glFramebufferTexture2D = [](GLenum, GLenum, GLenum, GLuint, GLint) {};
|
||||||
|
funcs.glFramebufferTextureLayer = [](GLenum target, GLenum, GLuint texture, GLint, GLint layer) {
|
||||||
|
const GLuint framebuffer = (target == GL_READ_FRAMEBUFFER) ? g_fake.boundReadFramebuffer
|
||||||
|
: g_fake.boundDrawFramebuffer;
|
||||||
|
g_fake.framebufferLayerAttachment[framebuffer] = {texture, layer};
|
||||||
|
};
|
||||||
|
funcs.glReadBuffer = [](GLenum) {};
|
||||||
|
// The defect itself: the source layer is read from where the READ framebuffer says (unless
|
||||||
|
// that knob is on too), and the result is written to the layer the DRAW framebuffer names -
|
||||||
|
// or to layer 0 regardless, which is what an affected driver does.
|
||||||
|
funcs.glBlitFramebuffer = [](GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield,
|
||||||
|
GLenum) {
|
||||||
|
const auto source = g_fake.framebufferLayerAttachment.find(g_fake.boundReadFramebuffer);
|
||||||
|
const auto destination = g_fake.framebufferLayerAttachment.find(g_fake.boundDrawFramebuffer);
|
||||||
|
if (source == g_fake.framebufferLayerAttachment.end() ||
|
||||||
|
destination == g_fake.framebufferLayerAttachment.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const GLint sourceLayer = g_fake.blitIgnoresSourceLayer ? 0 : source->second.second;
|
||||||
|
const GLint destinationLayer =
|
||||||
|
g_fake.blitIgnoresDestinationLayer ? 0 : destination->second.second;
|
||||||
|
auto& sourceFill = g_fake.arrayLayerFill[source->second.first];
|
||||||
|
auto& destinationFill = g_fake.arrayLayerFill[destination->second.first];
|
||||||
|
if (sourceLayer < 0 || static_cast<std::size_t>(sourceLayer) >= sourceFill.size()) return;
|
||||||
|
if (destinationLayer < 0 ||
|
||||||
|
static_cast<std::size_t>(destinationLayer) >= destinationFill.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
destinationFill[static_cast<std::size_t>(destinationLayer)] =
|
||||||
|
sourceFill[static_cast<std::size_t>(sourceLayer)];
|
||||||
|
};
|
||||||
funcs.glCheckFramebufferStatus = [](GLenum) -> GLenum { return GL_FRAMEBUFFER_COMPLETE; };
|
funcs.glCheckFramebufferStatus = [](GLenum) -> GLenum { return GL_FRAMEBUFFER_COMPLETE; };
|
||||||
funcs.glDeleteFramebuffers = [](GLsizei n, const GLuint* framebuffers) {
|
funcs.glDeleteFramebuffers = [](GLsizei n, const GLuint* framebuffers) {
|
||||||
for (GLsizei i = 0; i < n; ++i) {
|
for (GLsizei i = 0; i < n; ++i) {
|
||||||
if (framebuffers[i] != 0) --g_fake.aliveFramebuffers;
|
if (framebuffers[i] != 0) --g_fake.aliveFramebuffers;
|
||||||
|
g_fake.framebufferLayerAttachment.erase(framebuffers[i]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
funcs.glGenVertexArrays = [](GLsizei n, GLuint* arrays) {
|
funcs.glGenVertexArrays = [](GLsizei n, GLuint* arrays) {
|
||||||
@@ -417,6 +561,26 @@ namespace {
|
|||||||
funcs.glReadPixels = [](GLint, GLint, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
funcs.glReadPixels = [](GLint, GLint, GLsizei width, GLsizei height, GLenum format, GLenum type,
|
||||||
void* pixels) {
|
void* pixels) {
|
||||||
const std::size_t texels = static_cast<std::size_t>(width) * static_cast<std::size_t>(height);
|
const std::size_t texels = static_cast<std::size_t>(width) * static_cast<std::size_t>(height);
|
||||||
|
// Answered before anything else: a read framebuffer that names an array LAYER is the
|
||||||
|
// layered-blit probe asking what that layer holds, and its bytes have nothing to do
|
||||||
|
// with the pass/fail texel encoding the image probes below share.
|
||||||
|
if (const auto layered = g_fake.framebufferLayerAttachment.find(g_fake.boundReadFramebuffer);
|
||||||
|
layered != g_fake.framebufferLayerAttachment.end()) {
|
||||||
|
const auto& fill = g_fake.arrayLayerFill[layered->second.first];
|
||||||
|
const GLint layer = layered->second.second;
|
||||||
|
const GLubyte value =
|
||||||
|
(layer >= 0 && static_cast<std::size_t>(layer) < fill.size())
|
||||||
|
? fill[static_cast<std::size_t>(layer)]
|
||||||
|
: 0;
|
||||||
|
GLubyte* out = static_cast<GLubyte*>(pixels);
|
||||||
|
for (std::size_t i = 0; i < texels; ++i) {
|
||||||
|
out[i * 4 + 0] = value;
|
||||||
|
out[i * 4 + 1] = value;
|
||||||
|
out[i * 4 + 2] = value;
|
||||||
|
out[i * 4 + 3] = 255;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (format == GL_RED && type == GL_FLOAT) {
|
if (format == GL_RED && type == GL_FLOAT) {
|
||||||
GLfloat* out = static_cast<GLfloat*>(pixels);
|
GLfloat* out = static_cast<GLfloat*>(pixels);
|
||||||
for (std::size_t i = 0; i < texels; ++i) out[i] = g_fake.lastSampledValue;
|
for (std::size_t i = 0; i < texels; ++i) out[i] = g_fake.lastSampledValue;
|
||||||
@@ -452,6 +616,10 @@ namespace {
|
|||||||
// section would stop meaning "this device has these bugs".
|
// section would stop meaning "this device has these bugs".
|
||||||
TEST(DriverBugProbes, AProbeThatCannotRunReportsNoBug) {
|
TEST(DriverBugProbes, AProbeThatCannotRunReportsNoBug) {
|
||||||
const MG_External::GLESFunctionsTable gl = EmptyFunctionTable();
|
const MG_External::GLESFunctionsTable gl = EmptyFunctionTable();
|
||||||
|
EXPECT_FALSE(ProbeBlitIgnoresDestinationArrayLayer(gl))
|
||||||
|
<< "a probe with no entry points has measured nothing";
|
||||||
|
EXPECT_FALSE(ProbeExplicitVertexInputLocationCeiling(gl).detected)
|
||||||
|
<< "a probe with no entry points has measured nothing";
|
||||||
EXPECT_FALSE(ProbeGeometryStageSsboWriteAfterEmitDropped(gl))
|
EXPECT_FALSE(ProbeGeometryStageSsboWriteAfterEmitDropped(gl))
|
||||||
<< "a probe with no entry points to call must not claim the driver is affected";
|
<< "a probe with no entry points to call must not claim the driver is affected";
|
||||||
EXPECT_FALSE(ProbeR32FMultisampleSwizzleCorruption(gl));
|
EXPECT_FALSE(ProbeR32FMultisampleSwizzleCorruption(gl));
|
||||||
@@ -651,6 +819,125 @@ TEST(DriverBugProbes, ImageCoherencyReportsNothingWhenTheFinishSeparatedControlI
|
|||||||
EXPECT_FALSE(ProbeImageWriteReadCoherencyResidual(gl).detected);
|
EXPECT_FALSE(ProbeImageWriteReadCoherencyResidual(gl).detected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===================== EXPLICIT VERTEX INPUT LOCATION CEILING =====================
|
||||||
|
|
||||||
|
// The clean case, and the one that has to stay cheap: a driver whose compiler accepts the
|
||||||
|
// highest location it advertises is measured in a single compile and withdraws nothing.
|
||||||
|
TEST(DriverBugProbes, VertexInputLocationCeilingIsCleanWhenTheAdvertisedMaximumCompiles) {
|
||||||
|
ResetFakeDriver();
|
||||||
|
g_fake.maxVertexAttribs = 32;
|
||||||
|
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
|
||||||
|
const auto measurement = ProbeExplicitVertexInputLocationCeiling(gl);
|
||||||
|
EXPECT_FALSE(measurement.detected);
|
||||||
|
EXPECT_EQ(measurement.advertisedMaxVertexAttribs, 32);
|
||||||
|
EXPECT_EQ(measurement.usableLocations, 32) << "an unaffected driver must be taken at its word";
|
||||||
|
ExpectProbeReleasedEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The defect: 32 advertised, the qualifier refused from 16 up. The bisection has to land on the
|
||||||
|
// boundary exactly - one off in either direction advertises an attribute that cannot be declared,
|
||||||
|
// or withdraws one that can.
|
||||||
|
TEST(DriverBugProbes, VertexInputLocationCeilingIsMeasuredWhenTheQualifierIsCapped) {
|
||||||
|
ResetFakeDriver();
|
||||||
|
g_fake.maxVertexAttribs = 32;
|
||||||
|
g_fake.explicitLocationCeiling = 16;
|
||||||
|
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
|
||||||
|
const auto measurement = ProbeExplicitVertexInputLocationCeiling(gl);
|
||||||
|
EXPECT_TRUE(measurement.detected);
|
||||||
|
EXPECT_EQ(measurement.advertisedMaxVertexAttribs, 32);
|
||||||
|
EXPECT_EQ(measurement.usableLocations, 16);
|
||||||
|
EXPECT_TRUE(measurement.bindAttribLocationReachesAdvertisedMax)
|
||||||
|
<< "this driver caps only the qualifier, so the report may say the attribute is still reachable";
|
||||||
|
EXPECT_NE(measurement.driverMessage.find("attribute range"), std::string::npos)
|
||||||
|
<< "the driver's own wording is what makes the row evidence rather than an assertion";
|
||||||
|
ExpectProbeReleasedEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A ceiling that is not a power of two, so a bisection that happened to land on 16 by arithmetic
|
||||||
|
// rather than by measurement fails here.
|
||||||
|
TEST(DriverBugProbes, VertexInputLocationCeilingBisectsToAnAwkwardBoundary) {
|
||||||
|
ResetFakeDriver();
|
||||||
|
g_fake.maxVertexAttribs = 32;
|
||||||
|
g_fake.explicitLocationCeiling = 23;
|
||||||
|
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
|
||||||
|
const auto measurement = ProbeExplicitVertexInputLocationCeiling(gl);
|
||||||
|
EXPECT_TRUE(measurement.detected);
|
||||||
|
EXPECT_EQ(measurement.usableLocations, 23);
|
||||||
|
ExpectProbeReleasedEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
// THE FIRST CONTROL. A compiler that refuses location 0 refuses everything, and a probe that
|
||||||
|
// read that as "only one location is usable" would withdraw every vertex attribute the device has.
|
||||||
|
TEST(DriverBugProbes, VertexInputLocationCeilingReportsNothingWhenTheLocationZeroControlFails) {
|
||||||
|
ResetFakeDriver();
|
||||||
|
g_fake.maxVertexAttribs = 32;
|
||||||
|
g_fake.everyCompileFails = true;
|
||||||
|
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
|
||||||
|
const auto measurement = ProbeExplicitVertexInputLocationCeiling(gl);
|
||||||
|
EXPECT_FALSE(measurement.detected);
|
||||||
|
EXPECT_EQ(measurement.usableLocations, 32)
|
||||||
|
<< "an inconclusive probe has to leave the advertised count exactly where it found it";
|
||||||
|
ExpectProbeReleasedEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
// THE SECOND CONTROL, which does not change the clamp but does change what the report may claim:
|
||||||
|
// a driver that cannot reach the location through glBindAttribLocation either has fewer
|
||||||
|
// attributes than it advertises, rather than merely an unspellable half.
|
||||||
|
TEST(DriverBugProbes, VertexInputLocationCeilingSaysWhenTheAttributeIsUnreachableAnyWay) {
|
||||||
|
ResetFakeDriver();
|
||||||
|
g_fake.maxVertexAttribs = 32;
|
||||||
|
g_fake.explicitLocationCeiling = 16;
|
||||||
|
g_fake.bindAttribLocationCeiling = 16;
|
||||||
|
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
|
||||||
|
const auto measurement = ProbeExplicitVertexInputLocationCeiling(gl);
|
||||||
|
EXPECT_TRUE(measurement.detected);
|
||||||
|
EXPECT_EQ(measurement.usableLocations, 16);
|
||||||
|
EXPECT_FALSE(measurement.bindAttribLocationReachesAdvertisedMax);
|
||||||
|
ExpectProbeReleasedEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== LAYERED BLIT DESTINATION =====================
|
||||||
|
|
||||||
|
TEST(DriverBugProbes, LayeredBlitDestinationIsCleanWhenTheLayerIsHonoured) {
|
||||||
|
ResetFakeDriver();
|
||||||
|
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
|
||||||
|
EXPECT_FALSE(ProbeBlitIgnoresDestinationArrayLayer(gl));
|
||||||
|
ExpectProbeReleasedEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DriverBugProbes, LayeredBlitDestinationIsDetectedWhenTheCopyLandsOnLayerZero) {
|
||||||
|
ResetFakeDriver();
|
||||||
|
g_fake.blitIgnoresDestinationLayer = true;
|
||||||
|
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
|
||||||
|
EXPECT_TRUE(ProbeBlitIgnoresDestinationArrayLayer(gl));
|
||||||
|
ExpectProbeReleasedEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
// THE CONTROL. A driver that ignores the SOURCE layer too cannot address array layers through a
|
||||||
|
// framebuffer at all - a bigger defect, and one this probe is not entitled to report as its own.
|
||||||
|
// The control blit onto destination layer 0 is what catches it: the value it looks for lives only
|
||||||
|
// on the source's layer 1, so a source read pinned to layer 0 never produces it.
|
||||||
|
TEST(DriverBugProbes, LayeredBlitDestinationReportsNothingWhenTheSourceLayerIsIgnoredToo) {
|
||||||
|
ResetFakeDriver();
|
||||||
|
g_fake.blitIgnoresDestinationLayer = true;
|
||||||
|
g_fake.blitIgnoresSourceLayer = true;
|
||||||
|
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
|
||||||
|
EXPECT_FALSE(ProbeBlitIgnoresDestinationArrayLayer(gl));
|
||||||
|
ExpectProbeReleasedEverything();
|
||||||
|
}
|
||||||
|
|
||||||
|
// And the shape that is not this bug at all: a blit that moves nothing anywhere. The probe's
|
||||||
|
// subject then finds its magic byte on no layer, which is "reached no verdict", not "landed on 0".
|
||||||
|
TEST(DriverBugProbes, LayeredBlitDestinationReportsNothingWhenTheBlitMovesNothing) {
|
||||||
|
ResetFakeDriver();
|
||||||
|
const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions();
|
||||||
|
MG_External::GLESFunctionsTable inert = gl;
|
||||||
|
inert.glBlitFramebuffer = [](GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield,
|
||||||
|
GLenum) {};
|
||||||
|
EXPECT_FALSE(ProbeBlitIgnoresDestinationArrayLayer(inert));
|
||||||
|
ExpectProbeReleasedEverything();
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DriverBugProbes, ImageCoherencyNeedsBothHalvesOfTheSplitPairInOneStage) {
|
TEST(DriverBugProbes, ImageCoherencyNeedsBothHalvesOfTheSplitPairInOneStage) {
|
||||||
ResetFakeDriver();
|
ResetFakeDriver();
|
||||||
g_fake.coherencyStrongestShapeFailedTexels = 376;
|
g_fake.coherencyStrongestShapeFailedTexels = 376;
|
||||||
|
|||||||
@@ -1516,7 +1516,11 @@ namespace MobileGL::MG_Util::BackendLoader {
|
|||||||
// lowers the number, and only on evidence.
|
// lowers the number, and only on evidence.
|
||||||
const SelfTest::VertexInputLocationCeilingMeasurement& locationCeiling =
|
const SelfTest::VertexInputLocationCeilingMeasurement& locationCeiling =
|
||||||
SelfTest::ExplicitVertexInputLocationCeiling(glesFuncs);
|
SelfTest::ExplicitVertexInputLocationCeiling(glesFuncs);
|
||||||
caps.MaxVertexAttribs = std::min(maxVertexAttribs, locationCeiling.usableLocations);
|
// Guarded on `detected` rather than on the number alone: a probe that reached no verdict
|
||||||
|
// has measured nothing, and the clamp must be driven by evidence or not applied at all.
|
||||||
|
caps.MaxVertexAttribs = locationCeiling.detected
|
||||||
|
? std::min(maxVertexAttribs, locationCeiling.usableLocations)
|
||||||
|
: maxVertexAttribs;
|
||||||
if (locationCeiling.detected) {
|
if (locationCeiling.detected) {
|
||||||
MGLOG_I(" GL_MAX_VERTEX_ATTRIBS reduced from the driver's %d to %d: "
|
MGLOG_I(" GL_MAX_VERTEX_ATTRIBS reduced from the driver's %d to %d: "
|
||||||
"layout(location = N) on a vertex input is refused from N = %d upward",
|
"layout(location = N) on a vertex input is refused from N = %d upward",
|
||||||
|
|||||||
@@ -1515,14 +1515,16 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
"glBlitFramebuffer ignores the destination array layer",
|
"glBlitFramebuffer ignores the destination array layer",
|
||||||
DriverBugVerdict::Fixed,
|
DriverBugVerdict::Fixed,
|
||||||
"a glBlitFramebuffer whose DRAW framebuffer attaches a non-zero array layer with "
|
"a glBlitFramebuffer whose DRAW framebuffer attaches a non-zero array layer with "
|
||||||
"glFramebufferTextureLayer writes to layer 0 instead, for colour and depth alike, "
|
"glFramebufferTextureLayer writes to layer 0 instead, and raises no error doing "
|
||||||
"and raises no error doing it. The layer is honoured everywhere else on the same "
|
"it. Measured here on the colour aspect; the depth aspect behaves the same way on "
|
||||||
"driver - rendering into it works, glReadPixels off it works, and the blit's own "
|
"the device this was characterised on. The layer is honoured everywhere else on "
|
||||||
"SOURCE layer is read correctly - so neither layered attachments nor blitting is "
|
"the same driver - the blit's own SOURCE layer is read correctly, which is this "
|
||||||
"withdrawn. MobileGL performs such a blit with glCopyImageSubData instead, which "
|
"probe's control - so neither layered attachments nor blitting is withdrawn. "
|
||||||
"takes the destination layer explicitly and honours it here; a blit that scales, "
|
"MobileGL performs such a blit with glCopyImageSubData instead, which takes the "
|
||||||
"flips, changes format, resolves samples or is clipped by the scissor cannot be "
|
"destination layer explicitly and honours it here, and applies that substitute to "
|
||||||
"expressed that way and is still handed to the driver"};
|
"the depth and stencil aspects as well; a blit that scales, flips, changes format, "
|
||||||
|
"resolves samples or is clipped by the scissor cannot be expressed as a copy and "
|
||||||
|
"is still handed to the driver"};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== EXPLICIT VERTEX INPUT LOCATION CEILING =====================
|
// ===================== EXPLICIT VERTEX INPUT LOCATION CEILING =====================
|
||||||
@@ -1690,9 +1692,8 @@ namespace MobileGL::MG_Util::SelfTest {
|
|||||||
if (!measurement.detected) return std::nullopt;
|
if (!measurement.detected) return std::nullopt;
|
||||||
String detail =
|
String detail =
|
||||||
format("GL_MAX_VERTEX_ATTRIBS is {} but the ESSL compiler refuses "
|
format("GL_MAX_VERTEX_ATTRIBS is {} but the ESSL compiler refuses "
|
||||||
"layout(location = N) on a vertex input for every N at or above {}, for float "
|
"layout(location = N) on a vertex input for every N at or above {} - so {} of "
|
||||||
"and integer inputs alike - so {} of the {} attributes advertised cannot be "
|
"the {} attributes advertised cannot be declared at all",
|
||||||
"declared at all",
|
|
||||||
measurement.advertisedMaxVertexAttribs, measurement.usableLocations,
|
measurement.advertisedMaxVertexAttribs, measurement.usableLocations,
|
||||||
measurement.advertisedMaxVertexAttribs - measurement.usableLocations,
|
measurement.advertisedMaxVertexAttribs - measurement.usableLocations,
|
||||||
measurement.advertisedMaxVertexAttribs);
|
measurement.advertisedMaxVertexAttribs);
|
||||||
|
|||||||
Reference in New Issue
Block a user