mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
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:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user