mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Fix] (Review): rebuild the packed16 probe on the CTS's three-level chains and let the POST row answer for the widening knob actually in force
This commit is contained in:
@@ -20,10 +20,12 @@
|
||||
// copy path ever crossed the two layouts and why the CTS's "source image was not modified"
|
||||
// checks always passed.
|
||||
//
|
||||
// The array is 30x30x12 with two levels because that is the shape the failures pin: the same
|
||||
// suite's level-0 copies and a 14x14 base's level 1 measured clean on the same driver, so a
|
||||
// smaller array might sit on the clean side of whatever allocation threshold picks the
|
||||
// driver's layout.
|
||||
// The array is 30x30x12 with THREE levels and the flat endpoint is 7x7 with three levels
|
||||
// (7/3/1) because that is the allocation the failures pin - the CTS builds every functional
|
||||
// texture with FUNCTIONAL_TEST_N_LEVELS = 3 (makeTextureComplete(0, 2)) - and the same
|
||||
// suite's level-0 copies and a 14x14 base's level 1 measured clean on the same driver, so
|
||||
// any deviation from the measured shape (a smaller array, a shorter chain) might sit on the
|
||||
// clean side of whatever allocation threshold picks the driver's layout.
|
||||
//
|
||||
// The repair under test is the packed16 storage widening
|
||||
// (PixelFormatNormalizeOptionBit::WidenPacked16Norm): where the POST probe
|
||||
@@ -48,6 +50,7 @@
|
||||
// failure on both backends means the scenario is wrong, and a failure on DirectGLES alone
|
||||
// means the widening (or the narrow path it replaces) is.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -160,8 +163,9 @@ namespace MGITest {
|
||||
return usable;
|
||||
}
|
||||
|
||||
// The CTS's own mutable shape: glTexImage3D per level, filter NEAREST, the chain
|
||||
// clamped to the two levels it has.
|
||||
// The CTS's own mutable shape: glTexImage3D per level, filter NEAREST, THREE levels
|
||||
// (30/15/7) with the chain clamped to them. Level 2 carries its own fill so nothing
|
||||
// below can pass by reading a level that was never written.
|
||||
GLuint MakeArrayTexture(const PackedFormatCase& format, const std::vector<GLushort>& level0,
|
||||
const std::vector<GLushort>& level1) {
|
||||
GLuint texture = 0;
|
||||
@@ -170,15 +174,21 @@ namespace MGITest {
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1);
|
||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 2);
|
||||
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, static_cast<GLint>(format.internalFormat), kBaseSize, kBaseSize,
|
||||
kLayers, 0, format.transferFormat, format.transferType, level0.data());
|
||||
glTexImage3D(GL_TEXTURE_2D_ARRAY, 1, static_cast<GLint>(format.internalFormat), kLevel1Size,
|
||||
kLevel1Size, kLayers, 0, format.transferFormat, format.transferType, level1.data());
|
||||
const int level2Size = kLevel1Size / 2;
|
||||
const auto level2 = MakeWords(format.transferType, level2Size * level2Size * kLayers, 211);
|
||||
glTexImage3D(GL_TEXTURE_2D_ARRAY, 2, static_cast<GLint>(format.internalFormat), level2Size,
|
||||
level2Size, kLayers, 0, format.transferFormat, format.transferType, level2.data());
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
||||
return texture;
|
||||
}
|
||||
|
||||
// Three levels (7/3/1) like the CTS's plain endpoints; `texels` is level 0, the one
|
||||
// every assertion reads.
|
||||
GLuint MakeFlatTexture(const PackedFormatCase& format, const std::vector<GLushort>& texels) {
|
||||
GLuint texture = 0;
|
||||
glGenTextures(1, &texture);
|
||||
@@ -186,9 +196,15 @@ namespace MGITest {
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, static_cast<GLint>(format.internalFormat), kFlatSize, kFlatSize, 0,
|
||||
format.transferFormat, format.transferType, texels.data());
|
||||
for (int level = 1; level <= 2; ++level) {
|
||||
const int size = std::max(kFlatSize >> level, 1);
|
||||
const auto fill = MakeWords(format.transferType, size * size, 97 + level);
|
||||
glTexImage2D(GL_TEXTURE_2D, level, static_cast<GLint>(format.internalFormat), size, size, 0,
|
||||
format.transferFormat, format.transferType, fill.data());
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return texture;
|
||||
}
|
||||
|
||||
@@ -110,11 +110,15 @@ namespace {
|
||||
|
||||
// Probe 7: the driver's PHYSICAL field order for a 16-bit packed texel at a non-zero
|
||||
// mip level of a 2D array is the *_REV mirror of the plain-image order. Modelled at
|
||||
// upload, which is where the real defect lives: the words such a level stores are the
|
||||
// mirrored re-encoding, the raw copy moves them verbatim, and the plain 2D readback
|
||||
// decodes them with the non-REV order - exactly the 0x0047 -> 0x8C20 arithmetic the
|
||||
// affected Mali hands back. Uploads and readbacks of the SAME image stay consistent,
|
||||
// which is why only the copy path can observe the knob.
|
||||
// the raw copy, which is the only path that can observe it (uploads and readbacks of
|
||||
// the same image decode the driver's own layout consistently): a copy whose SOURCE is
|
||||
// such a level delivers the mirrored re-encoding, which the plain 2D readback then
|
||||
// decodes with the non-REV order - exactly the 0x0047 -> 0x8C20 arithmetic the
|
||||
// affected Mali hands back. The mirror only engages for the allocation the CTS
|
||||
// failures were measured on - a THREE-level 30x30x12 array - so a probe that stopped
|
||||
// building the triggering shape (fewer levels, other dimensions) stops detecting,
|
||||
// which is exactly what these tests are for: on the real driver a 14x14 base's level 1
|
||||
// measured clean, and nobody has measured a two-level chain at all.
|
||||
bool packed16ArrayMipFieldOrderMirrored = false;
|
||||
// The inconclusive path: EVERY array level stores mirrored words, level 0 included, so
|
||||
// the probe's level-0 control copy is dirty too and no verdict may be reached.
|
||||
@@ -156,6 +160,16 @@ namespace {
|
||||
// One word per level is all the packed16 probe distinguishes: it uploads a uniform
|
||||
// fill and reads one texel.
|
||||
std::map<std::pair<GLuint, GLint>, GLushort> packedTexelWords;
|
||||
// 2D-array texture id -> its allocation shape, as glTexImage3D built it. What the
|
||||
// packed16 mirror is gated on: level-0 dimensions plus a mask of the levels actually
|
||||
// allocated, so only the measured three-level 30x30x12 chain diverges.
|
||||
struct FakeArrayAllocation {
|
||||
GLsizei width = 0;
|
||||
GLsizei height = 0;
|
||||
GLsizei layers = 0;
|
||||
unsigned levelMask = 0;
|
||||
};
|
||||
std::map<GLuint, FakeArrayAllocation> packedArrayAllocations;
|
||||
// framebuffer id -> the plain 2D texture glFramebufferTexture2D attached.
|
||||
std::map<GLuint, GLuint> framebuffer2DAttachment;
|
||||
GLuint boundArrayTexture = 0;
|
||||
@@ -460,6 +474,7 @@ namespace {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
g_fake.packedArrayAllocations.erase(textures[i]);
|
||||
}
|
||||
};
|
||||
funcs.glTexParameteri = [](GLenum target, GLenum pname, GLint param) {
|
||||
@@ -479,27 +494,41 @@ namespace {
|
||||
std::memcpy(&word, pixels, sizeof(word));
|
||||
g_fake.packedTexelWords[{g_fake.boundTexture2D, level}] = word;
|
||||
};
|
||||
// The defect itself, at the upload where the physical layout is chosen: a mirrored
|
||||
// array level stores the re-encoded word. Its own readback would decode it back
|
||||
// consistently - only the raw copy below ever leaks the layout.
|
||||
funcs.glTexImage3D = [](GLenum target, GLint level, GLint, GLsizei, GLsizei, GLsizei, GLint,
|
||||
GLenum, GLenum type, const void* pixels) {
|
||||
// Records the allocation shape the mirror below is gated on, and the uploaded word.
|
||||
funcs.glTexImage3D = [](GLenum target, GLint level, GLint, GLsizei width, GLsizei height,
|
||||
GLsizei depth, GLint, GLenum, GLenum type, const void* pixels) {
|
||||
if (target != GL_TEXTURE_2D_ARRAY || type != GL_UNSIGNED_SHORT_5_5_5_1 || pixels == nullptr) return;
|
||||
GLushort word = 0;
|
||||
std::memcpy(&word, pixels, sizeof(word));
|
||||
const bool mirrored = g_fake.packed16EveryArrayLevelMirrored ||
|
||||
(g_fake.packed16ArrayMipFieldOrderMirrored && level >= 1);
|
||||
g_fake.packedTexelWords[{g_fake.boundArrayTexture, level}] =
|
||||
mirrored ? MirrorPacked5551(word) : word;
|
||||
g_fake.packedTexelWords[{g_fake.boundArrayTexture, level}] = word;
|
||||
auto& allocation = g_fake.packedArrayAllocations[g_fake.boundArrayTexture];
|
||||
if (level == 0) {
|
||||
allocation.width = width;
|
||||
allocation.height = height;
|
||||
allocation.layers = depth;
|
||||
}
|
||||
if (level >= 0 && level < 8) allocation.levelMask |= 1u << level;
|
||||
};
|
||||
// A raw texel-block move: the PHYSICAL word travels, whichever layout wrote it.
|
||||
// A raw texel-block move: the PHYSICAL word travels. The defect lives here - a source
|
||||
// level whose storage keeps the mirrored layout delivers the re-encoded word - and it
|
||||
// only exists for the allocation it was measured on: three levels of a 30x30x12 array.
|
||||
funcs.glCopyImageSubData = [](GLuint srcName, GLenum, GLint srcLevel, GLint, GLint, GLint,
|
||||
GLuint dstName, GLenum, GLint dstLevel, GLint, GLint, GLint,
|
||||
GLsizei, GLsizei, GLsizei) {
|
||||
if (g_fake.packed16CopyDoesNothing) return;
|
||||
const auto source = g_fake.packedTexelWords.find({srcName, srcLevel});
|
||||
if (source == g_fake.packedTexelWords.end()) return;
|
||||
g_fake.packedTexelWords[{dstName, dstLevel}] = source->second;
|
||||
GLushort word = source->second;
|
||||
const auto allocation = g_fake.packedArrayAllocations.find(srcName);
|
||||
const bool measuredShape = allocation != g_fake.packedArrayAllocations.end() &&
|
||||
allocation->second.width == 30 && allocation->second.height == 30 &&
|
||||
allocation->second.layers == 12 &&
|
||||
allocation->second.levelMask == 0b111u;
|
||||
if (measuredShape && (g_fake.packed16EveryArrayLevelMirrored ||
|
||||
(g_fake.packed16ArrayMipFieldOrderMirrored && srcLevel >= 1))) {
|
||||
word = MirrorPacked5551(word);
|
||||
}
|
||||
g_fake.packedTexelWords[{dstName, dstLevel}] = word;
|
||||
};
|
||||
funcs.glTexSubImage2D = [](GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum,
|
||||
const void*) {};
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <Config.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -1932,10 +1933,17 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
// 2D array whose base level is 30x30x12 showed the divergence at LEVEL 1 (the same
|
||||
// suite's level-0 copies and a 14x14 base's level 1 round-trip clean), so the probe
|
||||
// reproduces those dimensions rather than a minimal shape that might sit on the clean
|
||||
// side of whatever allocation threshold picks the driver's layout.
|
||||
// side of whatever allocation threshold picks the driver's layout. VERBATIM INCLUDES
|
||||
// THE LEVEL COUNT: the CTS allocates THREE-level chains on BOTH endpoints
|
||||
// (FUNCTIONAL_TEST_N_LEVELS = 3, makeTextureComplete(0, 2): 30/15/7 x12 for the array,
|
||||
// 7/3/1 for the plain image), and every device data point above came from those
|
||||
// allocations - a chain one level shorter has never been measured on the affected
|
||||
// driver, and a probe miss here is not a red anything, it is the widening silently
|
||||
// staying inert with all 18 bodies red.
|
||||
constexpr GLsizei kPacked16BaseSize = 30;
|
||||
constexpr GLsizei kPacked16Layers = 12;
|
||||
constexpr GLsizei kPacked16DstSize = 7;
|
||||
constexpr GLint kPacked16Levels = 3;
|
||||
|
||||
// One GL_RGB5_A1 texel, as the client word the probe uploads everywhere:
|
||||
// (R, G, B, A) = (0, 1, 3, 1) under GL_UNSIGNED_SHORT_5_5_5_1. Chosen because 5551 is
|
||||
@@ -1955,9 +1963,10 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
// adjacent 5-bit values apart.
|
||||
constexpr Int kPacked16Tolerance = 4;
|
||||
|
||||
// A 2-level GL_RGB5_A1 2D array allocated the way MobileGL's own mutable-texture path
|
||||
// allocates one (glTexImage3D per level), with every texel of both levels holding
|
||||
// kPacked16Word. MAX_LEVEL is clamped so the two-level chain is complete - some
|
||||
// A three-level GL_RGB5_A1 2D array (30/15/7, twelve layers each) allocated the way
|
||||
// MobileGL's own mutable-texture path allocates one (glTexImage3D per level), with
|
||||
// every texel of every level holding kPacked16Word. MAX_LEVEL is clamped to the
|
||||
// CTS's makeTextureComplete(0, 2) shape, which also keeps the chain complete - some
|
||||
// drivers refuse glCopyImageSubData on an incomplete texture.
|
||||
GLuint MakePacked16ArrayTexture(const GLESFunctionsTable& gl) {
|
||||
GLuint texture = 0;
|
||||
@@ -1966,8 +1975,8 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
gl.glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 1);
|
||||
for (GLint level = 0; level < 2; ++level) {
|
||||
gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, kPacked16Levels - 1);
|
||||
for (GLint level = 0; level < kPacked16Levels; ++level) {
|
||||
const GLsizei size = kPacked16BaseSize >> level;
|
||||
const Vector<Uint16> words(
|
||||
static_cast<SizeT>(size) * static_cast<SizeT>(size) * kPacked16Layers, kPacked16Word);
|
||||
@@ -1978,9 +1987,9 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
return texture;
|
||||
}
|
||||
|
||||
// A one-level GL_RGB5_A1 2D destination filled with 0xFFFF - the CTS's own (1,1,1,1)
|
||||
// destination fill - so a copy that silently did nothing reads as "no verdict" rather
|
||||
// than as either prediction.
|
||||
// The plain-2D destination, three levels (7/3/1) like the CTS's, every level filled
|
||||
// with 0xFFFF - the CTS's own (1,1,1,1) destination fill - so a copy that silently
|
||||
// did nothing reads as "no verdict" rather than as either prediction.
|
||||
GLuint MakePacked16DstTexture(const GLESFunctionsTable& gl) {
|
||||
GLuint texture = 0;
|
||||
gl.glGenTextures(1, &texture);
|
||||
@@ -1988,10 +1997,13 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
gl.glBindTexture(GL_TEXTURE_2D, texture);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||
const Vector<Uint16> fill(static_cast<SizeT>(kPacked16DstSize) * kPacked16DstSize, Uint16{0xFFFF});
|
||||
gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, kPacked16DstSize, kPacked16DstSize, 0, GL_RGBA,
|
||||
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, kPacked16Levels - 1);
|
||||
for (GLint level = 0; level < kPacked16Levels; ++level) {
|
||||
const GLsizei size = std::max<GLsizei>(kPacked16DstSize >> level, 1);
|
||||
const Vector<Uint16> fill(static_cast<SizeT>(size) * size, Uint16{0xFFFF});
|
||||
gl.glTexImage2D(GL_TEXTURE_2D, level, GL_RGB5_A1, size, size, 0, GL_RGBA,
|
||||
GL_UNSIGNED_SHORT_5_5_5_1, fill.data());
|
||||
}
|
||||
gl.glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return texture;
|
||||
}
|
||||
@@ -2103,6 +2115,13 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
MGLOG_I("[driver-bug] %s probe reached no verdict (the level-1 copy read back "
|
||||
"(%d, %d, %d, %d), which is neither the word nor its mirror)",
|
||||
kPacked16CopyProbeName, subject[0], subject[1], subject[2], subject[3]);
|
||||
} else {
|
||||
// The clean verdict is logged too: on a device run the FIRST question is whether
|
||||
// this probe executed at all, and a silent clean path is indistinguishable from a
|
||||
// probe that never ran.
|
||||
MGLOG_I("[driver-bug] %s probe: a copy out of the array's level 1 delivered the word "
|
||||
"intact - the field order is consistent",
|
||||
kPacked16CopyProbeName);
|
||||
}
|
||||
if (array != 0) gl.glDeleteTextures(1, &array);
|
||||
Restore(gl, saved);
|
||||
@@ -2285,21 +2304,40 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
|
||||
Optional<DriverBugFinding> ProbeCopyImagePacked16FieldOrderBug(const GLESFunctionsTable& gl) {
|
||||
if (!CopyImageMirrorsPacked16FieldOrder(gl)) return std::nullopt;
|
||||
return DriverBugFinding{
|
||||
"glCopyImageSubData mirrors 16-bit packed texels at a non-zero array mip level",
|
||||
DriverBugVerdict::Fixed,
|
||||
// The mitigation is a knob (MOBILEGL_WIDEN_PACKED16_STORAGE), so the row consults
|
||||
// it: under ForceOff - the documented negative control - the corruption is
|
||||
// replayed verbatim, and a hardcoded "Fixed" would be exactly the kind of
|
||||
// reassurance this file exists to refuse. Auto and ForceOn both widen once this
|
||||
// probe has fired. Should POST ever run before env parsing, the field still holds
|
||||
// its Auto default - which is also what the widening itself consults, so the row
|
||||
// and the behaviour cannot disagree.
|
||||
const Bool widened = MG_Config::Features.EsprytWidenPacked16Storage !=
|
||||
MG_Config::QuirkOverride::ForceOff;
|
||||
String detail =
|
||||
"the driver's physical field order for a 16-bit packed texel (RGB565 / RGB5_A1 / "
|
||||
"RGBA4) at a non-zero mip level of a GL_TEXTURE_2D_ARRAY is the *_REV mirror of "
|
||||
"the order every other image uses, so a glCopyImageSubData - a raw texel-block "
|
||||
"move - between such a level and any other image lands the R/G/B/A fields "
|
||||
"reversed (a 5551 word 0x0047 arrives as 0x8C20). Uploads and readbacks of the "
|
||||
"same level are clean - the driver decodes its own layout consistently, which is "
|
||||
"this probe's control - so only the raw-copy path ever crosses the two layouts. "
|
||||
"this probe's control - so only the raw-copy path ever crosses the two layouts. ";
|
||||
if (widened) {
|
||||
detail +=
|
||||
"MobileGL stores these three formats as 8-bit-per-channel ES storage on this "
|
||||
"driver instead (GL_RGB8 / GL_RGBA8, the storage their canonical shadow already "
|
||||
"holds and the client word round-trips through exactly), so no 16-bit packed "
|
||||
"image is left for a copy to disagree about, at twice the memory for images of "
|
||||
"those formats"};
|
||||
"driver instead (GL_RGB8 / GL_RGBA8, the storage their canonical shadow "
|
||||
"already holds and the client word round-trips through exactly), so no "
|
||||
"16-bit packed image is left for a copy to disagree about, at twice the "
|
||||
"memory for images of those formats; override with "
|
||||
"MOBILEGL_WIDEN_PACKED16_STORAGE";
|
||||
return DriverBugFinding{
|
||||
"glCopyImageSubData mirrors 16-bit packed texels at a non-zero array mip level",
|
||||
DriverBugVerdict::Fixed, detail};
|
||||
}
|
||||
detail += "MOBILEGL_WIDEN_PACKED16_STORAGE=0 keeps the native narrow storage, so such "
|
||||
"copies are left exactly as the driver delivers them, mirrored words included";
|
||||
return DriverBugFinding{
|
||||
"glCopyImageSubData mirrors 16-bit packed texels at a non-zero array mip level",
|
||||
DriverBugVerdict::Unfixable, detail};
|
||||
}
|
||||
|
||||
// The table. One row per known driver bug; see the header for how to add a sibling.
|
||||
|
||||
@@ -309,9 +309,13 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
// Uploads and readbacks decode that layout consistently, so nothing but a raw texel-block
|
||||
// move can see it - which is exactly what glCopyImageSubData is defined to be, and why
|
||||
// the whole KHR-GL4x.copy_image rgb5/rgb5_a1/rgba4 x *2d_array* matrix fails there while
|
||||
// every other suite touching these formats passes. The texture reproduces the failing
|
||||
// shape verbatim (a 30x30x12 two-level array; a 14x14 base's level 1 measured clean on
|
||||
// the same driver, so a minimal shape might not manifest the layout).
|
||||
// every other suite touching these formats passes. The textures reproduce the failing
|
||||
// shape verbatim - THREE-level chains on both endpoints (30/15/7 x12 for the array,
|
||||
// 7/3/1 for the plain 2D image), exactly the CTS's FUNCTIONAL_TEST_N_LEVELS = 3
|
||||
// allocation - because a 14x14 base's level 1 measured clean on the same driver, so
|
||||
// every deviation from the measured shape risks landing on the clean side of whatever
|
||||
// allocation threshold picks the driver's layout, and a probe miss here is not a red
|
||||
// test: it is the widening silently staying inert with all 18 bodies still failing.
|
||||
//
|
||||
// THE CONTROL is the identical copy out of mip level 0, which is clean on the affected
|
||||
// driver too: it proves copy_image works between these images at all and that the
|
||||
|
||||
Reference in New Issue
Block a user