Optional (NX2NAVI_VEC_INDEX_FIX): uniform vector components at computed indices loaded whole and selected.

AMD's Windows drivers can read the wrong component through an access chain into a uniform block vector whose
component index is computed (the translator's c[n][i >> 2][i & 3] for an LDC with a register offset), the bug Ryujinx
works around with HasVectorIndexingBug. With NX2NAVI_VEC_INDEX_FIX=1 (or auto: AMD's own drivers) each shader is
patched in memory as it loads: the chain without its last index loads the whole vector, CompositeExtract takes each
component and OpSelect picks the indexed one. Off by default: it did not change the volumetric fog's teal blotches on an
RX 580, whose per-volume extinction reads its colour this way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Tobim6
2026-11-12 16:22:15 +01:00
parent 6169d82385
commit 3b90353c5b
2 changed files with 187 additions and 0 deletions

View File

@@ -1175,6 +1175,185 @@ out:
free(arrayed), free(need), free(bind), free(org), free(query);
}
// Every load of one component of a uniform block vector at a computed component index (OpAccessChain into a Uniform
// block whose last index, into a vector, is not a constant: the translator's c[n][i >> 2][i & 3] for an LDC with a
// register offset) becomes a load of the whole vector, its components extracted with constant indices and the one
// wanted picked with OpSelect. AMD's Windows drivers can read the wrong component through such an access chain, the bug
// Ryujinx works around with HasVectorIndexingBug (VectorComponentSelect). The volumetric fog's per-volume
// extinction reads its colour this way. It did not change the fog's teal blotches on an RX 580 (23.10.01.14), so it is
// off by default. NX2NAVI_VEC_INDEX_FIX: 1 everywhere, auto on AMD's own drivers, unset or 0 off. In memory only.
int vk_caps_vec_index_bug(void); // vk_caps.c
enum { VX_TYPE_BOOL = 20, VX_TYPE_INT = 21, VX_TYPE_FLOAT = 22, VX_TYPE_VECTOR = 23, VX_TYPE_ARRAY = 28,
VX_TYPE_RTARRAY = 29, VX_TYPE_STRUCT = 30, VX_TYPE_POINTER = 32, VX_CONSTANT = 43, VX_FUNCTION = 54,
VX_VARIABLE = 59, VX_LOAD = 61, VX_ACCESS_CHAIN = 65, VX_IN_BOUNDS_ACCESS_CHAIN = 66, VX_COMPOSITE_EXTRACT = 81,
VX_SELECT = 169, VX_IEQUAL = 170, VX_SC_UNIFORM = 2 };
static void patch_vec_index(GfxShader *s) {
static int on = -1;
static volatile int32_t n_logged;
if (on < 0) {
const char *e = cfg_get("NX2NAVI_VEC_INDEX_FIX");
on = e && *e ? (strcmp(e, "auto") ? *e != '0' : vk_caps_vec_index_bug()) : 0;
if (on) rt_log("gfx_shader: uniform vector components at computed indices are read whole and selected "
"(NX2NAVI_VEC_INDEX_FIX=0 turns it off)");
}
uint32_t *w = s->spirv;
uint64_t n = s->words;
if (!on || n < 5) return;
uint32_t bound = w[3];
if (bound > (1u << 22)) return;
uint64_t *def = calloc(bound, sizeof *def); // word index of the instruction defining each id
uint32_t *ptr_v = calloc(bound, sizeof *ptr_v); // per vector type: its Uniform pointer type, if any
uint32_t *target = calloc(bound, sizeof *target); // per access chain id to rewrite: the vector access chain's id
uint32_t *acv = calloc(bound, sizeof *acv); // per access chain id to rewrite: the vector type
if (!def || !ptr_v || !target || !acv) goto out;
uint64_t first_fn = 0;
uint32_t bool_t = 0;
for (uint64_t i = 5; i < n;) {
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
if (!len || i + len > n) goto out;
if (op == VX_FUNCTION && !first_fn) first_fn = i;
uint32_t rid = 0;
switch (op) {
// no result id (or none at word 2): debug, annotations, mode setting, stores, barriers, control flow
case 3: case 4: case 5: case 6: case 8: case 10: case 14: case 15: case 16: case 17: case 56: case 62: case 63:
case 71: case 72: case 73: case 74: case 75: case 99: case 224: case 225: case 228: case 246: case 247: case 248:
case 249: case 250: case 251: case 252: case 253: case 254: case 255: case 317: case 330: case 331: case 332:
case 4416: case 5294: case 5295: case 5632: case 5633: break;
case 7: case 11: // OpString, OpExtInstImport: result at word 1
case VX_TYPE_BOOL: case VX_TYPE_INT: case VX_TYPE_FLOAT: case VX_TYPE_VECTOR: case VX_TYPE_ARRAY:
case VX_TYPE_RTARRAY: case VX_TYPE_STRUCT: case VX_TYPE_POINTER: rid = len >= 2 ? w[i + 1] : 0; break;
default:
if (op >= 19 && op <= 39) rid = len >= 2 ? w[i + 1] : 0; // the other OpType*
else if (len >= 3) rid = w[i + 2];
}
if (op == VX_TYPE_BOOL && !bool_t) bool_t = w[i + 1];
if (op == VX_TYPE_POINTER && len == 4 && w[i + 2] == VX_SC_UNIFORM && w[i + 3] < bound && !ptr_v[w[i + 3]])
ptr_v[w[i + 3]] = w[i + 1];
if (rid && rid < bound && !def[rid]) def[rid] = i;
i += len;
}
if (!first_fn) goto out;
#define VX_OP(id) ((id) < bound && def[id] ? w[def[id]] & 0xffff : 0)
// the access chains to rewrite and what they need
int found = 0;
uint32_t next = bound;
uint32_t need_ptr[64], need_ptr_v[64], nneed = 0; // new Uniform pointer types: id, vector type
uint32_t cint[8][4] = {{0}}, cint_t[8] = {0}, ncint = 0; // constants 0..3 per index type
for (uint64_t i = first_fn; i < n;) {
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
if ((op == VX_ACCESS_CHAIN || op == VX_IN_BOUNDS_ACCESS_CHAIN) && len >= 6) {
uint32_t rt = w[i + 1], id = w[i + 2], base = w[i + 3], nidx = len - 4, last = w[i + len - 1];
uint32_t vt = 0;
if (VX_OP(rt) == VX_TYPE_POINTER && w[def[rt] + 2] == VX_SC_UNIFORM && VX_OP(base) == VX_VARIABLE &&
VX_OP(last) != VX_CONSTANT && VX_OP(w[def[base] + 1]) == VX_TYPE_POINTER) {
uint32_t t = w[def[w[def[base] + 1]] + 3]; // the variable's pointee
int ok = 1;
for (uint32_t k = 0; k + 1 < nidx && ok; k++) {
uint32_t top = VX_OP(t), ix = w[i + 4 + k];
if (top == VX_TYPE_STRUCT) {
if (VX_OP(ix) != VX_CONSTANT) { ok = 0; break; }
uint32_t m = w[def[ix] + 3], slen = w[def[t]] >> 16;
if (2 + m >= slen) { ok = 0; break; }
t = w[def[t] + 2 + m];
} else if (top == VX_TYPE_ARRAY || top == VX_TYPE_RTARRAY) {
t = w[def[t] + 2];
} else ok = 0;
}
uint32_t sc = w[def[rt] + 3];
if (ok && VX_OP(t) == VX_TYPE_VECTOR && w[def[t] + 2] == sc && w[def[t] + 3] >= 2 && w[def[t] + 3] <= 4 &&
def[last] && (w[def[last]] >> 16) >= 3) {
uint32_t it = w[def[last] + 1]; // the index's type
if (VX_OP(it) == VX_TYPE_INT) vt = t;
if (vt && !ptr_v[vt]) {
if (nneed == 64) vt = 0;
else need_ptr[nneed] = next++, need_ptr_v[nneed++] = vt, ptr_v[vt] = need_ptr[nneed - 1];
}
if (vt) {
uint32_t c = 0;
while (c < ncint && cint_t[c] != it) c++;
if (c == ncint) {
if (ncint == 8) vt = 0;
else cint_t[ncint++] = it;
}
}
}
}
if (vt && id < bound) target[id] = next++, acv[id] = vt, found++;
}
i += len;
}
if (!found) goto out;
// constants 0..3 of each index type: existing ones where the module has them
for (uint64_t i = 5; i < first_fn;) {
uint32_t len = w[i] >> 16;
if ((w[i] & 0xffff) == VX_CONSTANT && len == 4 && w[i + 3] < 4)
for (uint32_t c = 0; c < ncint; c++)
if (cint_t[c] == w[i + 1] && !cint[c][w[i + 3]]) cint[c][w[i + 3]] = w[i + 2];
i += len;
}
uint32_t newc[8][4] = {{0}};
for (uint32_t c = 0; c < ncint; c++)
for (int v = 0; v < 4; v++)
if (!cint[c][v]) cint[c][v] = newc[c][v] = next++;
uint32_t new_bool = bool_t ? 0 : next++;
if (!bool_t) bool_t = new_bool;
// count the loads to size the output: each becomes 1 + 4 + 3 + 3 instructions of up to 5 words
uint64_t loads = 0;
for (uint64_t i = first_fn; i < n;) {
uint32_t len = w[i] >> 16;
if ((w[i] & 0xffff) == VX_LOAD && len >= 4 && w[i + 3] < bound && target[w[i + 3]]) loads++;
i += len;
}
uint32_t *o = malloc((n + 64 * 4 + 32 * 4 + 4 + (uint64_t)found * 16 + loads * 64) * sizeof *o);
if (!o) goto out;
uint64_t k = 0;
for (uint64_t i = 0; i < 5; i++) o[k++] = w[i];
for (uint64_t i = 5; i < n;) {
uint32_t op = w[i] & 0xffff, len = w[i] >> 16;
if (i == first_fn) { // the new declarations, last in the global section
for (uint32_t j = 0; j < nneed; j++)
o[k++] = 4u << 16 | VX_TYPE_POINTER, o[k++] = need_ptr[j], o[k++] = VX_SC_UNIFORM, o[k++] = need_ptr_v[j];
if (new_bool) o[k++] = 2u << 16 | VX_TYPE_BOOL, o[k++] = new_bool;
for (uint32_t c = 0; c < ncint; c++)
for (uint32_t v = 0; v < 4; v++)
if (newc[c][v]) o[k++] = 4u << 16 | VX_CONSTANT, o[k++] = cint_t[c], o[k++] = newc[c][v], o[k++] = v;
}
for (uint32_t j = 0; j < len; j++) o[k++] = w[i + j];
if ((op == VX_ACCESS_CHAIN || op == VX_IN_BOUNDS_ACCESS_CHAIN) && len >= 6 && w[i + 2] < bound && target[w[i + 2]]) {
// the same chain without its last index: a pointer to the vector
o[k++] = (len - 1) << 16 | op, o[k++] = ptr_v[acv[w[i + 2]]], o[k++] = target[w[i + 2]];
for (uint32_t j = 3; j < len - 1; j++) o[k++] = w[i + j];
} else if (op == VX_LOAD && len >= 4 && w[i + 3] < bound && target[w[i + 3]]) {
k -= len; // replaced
uint32_t st = w[i + 1], res = w[i + 2], ac = w[i + 3], vt = acv[ac], nc = w[def[vt] + 3];
uint32_t idx = w[def[ac] + (w[def[ac]] >> 16) - 1], it = w[def[idx] + 1], c = 0;
while (cint_t[c] != it) c++;
uint32_t vec = next++, e[4], sel = 0;
o[k++] = 4u << 16 | VX_LOAD, o[k++] = vt, o[k++] = vec, o[k++] = target[ac];
for (uint32_t m = 0; m < nc; m++)
e[m] = next++, o[k++] = 5u << 16 | VX_COMPOSITE_EXTRACT, o[k++] = st, o[k++] = e[m], o[k++] = vec, o[k++] = m;
sel = e[0];
for (uint32_t m = 1; m < nc; m++) {
uint32_t eq = next++, r = m + 1 == nc ? res : next++;
o[k++] = 5u << 16 | VX_IEQUAL, o[k++] = bool_t, o[k++] = eq, o[k++] = idx, o[k++] = cint[c][m];
o[k++] = 6u << 16 | VX_SELECT, o[k++] = st, o[k++] = r, o[k++] = eq, o[k++] = e[m], o[k++] = sel;
sel = r;
}
}
i += len;
}
o[3] = next;
free(s->spirv);
s->spirv = o, s->words = k;
if (plat_inc32(&n_logged) <= 4)
rt_log("gfx_shader: %d uniform vector component reads at computed indices now select the component", found);
#undef VX_OP
out:
free(def), free(ptr_v), free(target), free(acv);
}
static void reflect_stage(GfxShader *s) {
const char *js = s->reflection;
char stage[8];
@@ -1197,6 +1376,7 @@ static void reflect(GfxShader *s) {
reflect_spirv(s);
reflect_exact(s);
patch_txd_transpose(s);
patch_vec_index(s);
}
// ---- use masks (see the cache key at the top)

View File

@@ -41,6 +41,12 @@ void vk_caps_features(VkPhysicalDeviceFeatures *f) {
vk_caps.float64 = f->shaderFloat64;
}
static int amd_proprietary; // AMD's own Windows driver (any branch): gfx_shader.c patch_vec_index
// gfx_shader.c patch_vec_index: the driver reads uniform vector components at computed indices wrongly (Ryujinx's
// HasVectorIndexingBug, which it sets for AMD's Windows drivers).
int vk_caps_vec_index_bug(void) { return amd_proprietary; }
void vk_caps_driver(VkPhysicalDevice p) {
VkPhysicalDeviceDriverProperties drv = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
VkPhysicalDeviceProperties2 p2 = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = &drv};
@@ -51,6 +57,7 @@ void vk_caps_driver(VkPhysicalDevice p) {
// "26.10.1 (AMD proprietary shader compiler)" where the RDNA 3 and 4 driver names LLPC.
vk_caps.amd_sc = drv.driverID == VK_DRIVER_ID_AMD_PROPRIETARY && strstr(drv.driverInfo, "shader compiler");
vk_caps.amd_llpc = drv.driverID == VK_DRIVER_ID_AMD_PROPRIETARY && !vk_caps.amd_sc;
amd_proprietary = drv.driverID == VK_DRIVER_ID_AMD_PROPRIETARY;
vk_caps.mesa = drv.driverID == VK_DRIVER_ID_MESA_RADV || drv.driverID == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA ||
drv.driverID == VK_DRIVER_ID_MESA_NVK;
}