mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
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>
190 lines
13 KiB
C
190 lines
13 KiB
C
// What the runtime branches on, logged once at device creation, and the fallbacks for GPUs other than the RX 9070.
|
|
//
|
|
// Fallbacks (each also forced on any GPU by an environment switch, to test it where the GPU does not need it):
|
|
// - no shaderFloat64 (NX2NAVI_VK_NO_FLOAT64=1 clears the feature before vkCreateDevice): the translator lowers FP64 to
|
|
// FP32 ("softfloat64"), so no shader declares Float64.
|
|
// - minStorageBufferOffsetAlignment above 4 (NX2NAVI_VK_SSBO_ALIGN=<power of two> raises it): the translator computes
|
|
// storage offsets from the guest address rounded down to that alignment (the translator's unaligned storage path), and
|
|
// vk_exec_draw.c ssbo_info binds the rounded-down address, so shader writes reach guest memory instead of a copy.
|
|
// - no graphics pipeline library with fast linking (NX2NAVI_GPL=0 forces it; it also defaults off on AMD's Windows
|
|
// driver for RDNA 2 and older, see vk_caps_gpl): vk_caps.gpl = 0, the extension stays disabled, and pipelines are built whole.
|
|
// - no 32-lane pin (no subgroupSizeControl, 32 outside min..max, or NX2NAVI_VK_SUBGROUP_PIN=0): stages run at the
|
|
// device's subgroupSize (SPIR-V 1.3 without a required size) and the translator is told that size.
|
|
// NX2NAVI_VK_SUBGROUP=<n> pins n instead of 32 where the driver allows it.
|
|
// The defaults (Float64, no storage rounding, 32 everywhere) leave the shader cache keys as they were (gfx_shader.c).
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "cfg.h"
|
|
#include "gfx.h"
|
|
#include "host.h"
|
|
#include "vk.h"
|
|
#include "vk_gpa.h"
|
|
|
|
VkCaps vk_caps = {.float64 = 1, .sg_size = 32};
|
|
static int f64_supported;
|
|
static VkBool32 gpl_lib, gpl_fast; // the driver's graphicsPipelineLibrary (feature), ...FastLinking (property)
|
|
static const char *gpl_off; // why gpl is off, for the boot report; NULL when it is on
|
|
static char summary[832]; // vk_caps_summary: written once, in vk_caps_init, before any other thread reads it
|
|
|
|
const char *vk_caps_summary(void) { return summary; }
|
|
|
|
static uint32_t env_u32(const char *name, uint32_t def) {
|
|
const char *e = cfg_get(name);
|
|
return e && *e ? (uint32_t)strtoul(e, NULL, 0) : def;
|
|
}
|
|
|
|
void vk_caps_features(VkPhysicalDeviceFeatures *f) {
|
|
f64_supported = f->shaderFloat64;
|
|
if (env_u32("NX2NAVI_VK_NO_FLOAT64", 0)) f->shaderFloat64 = VK_FALSE;
|
|
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};
|
|
vkGetPhysicalDeviceProperties2(p, &p2);
|
|
vk_caps.amd = p2.properties.vendorID == 0x1002;
|
|
vk_caps.driver_id = (uint32_t)drv.driverID;
|
|
// AMD split RDNA 1 and 2 off into a driver branch of their own; its driverInfo reads
|
|
// "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;
|
|
}
|
|
|
|
// NX2NAVI_GPL=0 turns graphics pipeline libraries off and 1 turns them on; unset, they are on wherever the driver has
|
|
// fast linking, except on AMD's Windows driver for RDNA 2 and older (vk_caps.amd_sc: RX 400 to RX 6000). On an
|
|
// RX 6700 XT the game ended with code 71 at every enemy killed, from 0.5 on, and no longer with gpl = 0; an RX 5700 XT
|
|
// showed the same ending (issue #418). Where in the driver it faults is not established: a kill draws its effects
|
|
// for the first time, which asks for a burst of new pipelines linked from stage libraries, and building them whole
|
|
// avoids it. The price there: with libraries off a pipeline the game needs for the first time is built whole by a
|
|
// worker while its draw is left out (vk_exec_draw.c, "pipeline still building"), so new things appear some frames
|
|
// late, and the start waits for every recorded pipeline instead of 20 s at most (vk_pipeline.c prewarm_run). The
|
|
// same driver had libraries off before, for device losses at boot (issue #5), whose cause was GPU writes past 3D BC
|
|
// images (vk_exec.c, "3D BC render targets"); it boots with them since that was fixed, so gpl = 1 is there to try on
|
|
// a newer driver. RDNA 4 under RADV is reported to hang the GPU at the title screen with GPL.
|
|
int vk_caps_gpl(const VkPhysicalDeviceGraphicsPipelineLibraryFeaturesEXT *f,
|
|
const VkPhysicalDeviceGraphicsPipelineLibraryPropertiesEXT *p) {
|
|
gpl_lib = f->graphicsPipelineLibrary;
|
|
gpl_fast = p->graphicsPipelineLibraryFastLinking;
|
|
const char *e = cfg_get("NX2NAVI_GPL");
|
|
int want = !e || !*e ? !vk_caps.amd_sc : strcmp(e, "0") != 0;
|
|
vk_caps.gpl = gpl_lib && gpl_fast && want;
|
|
gpl_off = vk_caps.gpl ? NULL
|
|
: !gpl_lib || !gpl_fast ? "the driver has no fast linking"
|
|
: e && *e ? "NX2NAVI_GPL=0"
|
|
: "default on AMD's driver for RDNA 2 and older (NX2NAVI_GPL=1 turns it on)";
|
|
return vk_caps.gpl;
|
|
}
|
|
|
|
static int has_ext(const VkExtensionProperties *ep, uint32_t n, const char *name) {
|
|
for (uint32_t i = 0; i < n; i++)
|
|
if (!strcmp(ep[i].extensionName, name)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static char depth_ok(VkPhysicalDevice p, VkFormat f) {
|
|
VkFormatProperties fp;
|
|
vkGetPhysicalDeviceFormatProperties(p, f, &fp);
|
|
return fp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT ? 'y' : 'n';
|
|
}
|
|
|
|
static void driver_version(char *o, size_t n, uint32_t vendor, uint32_t v) {
|
|
if (vendor == 0x10de) snprintf(o, n, "%u.%u.%u.%u", v >> 22, v >> 14 & 0xff, v >> 6 & 0xff, v & 0x3f);
|
|
else if (vendor == 0x8086) snprintf(o, n, "%u.%u", v >> 14, v & 0x3fff);
|
|
else snprintf(o, n, "%u.%u.%u", VK_API_VERSION_MAJOR(v), VK_API_VERSION_MINOR(v), VK_API_VERSION_PATCH(v));
|
|
}
|
|
|
|
void vk_caps_init(VkCtx *c) {
|
|
VkPhysicalDeviceLimits *l = &c->props.limits;
|
|
VkPhysicalDeviceDriverProperties drv = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES};
|
|
VkPhysicalDeviceSubgroupProperties sg = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES, .pNext = &drv};
|
|
VkPhysicalDeviceVulkan13Properties p13 = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_PROPERTIES, .pNext = &sg};
|
|
VkPhysicalDevicePushDescriptorPropertiesKHR pd = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PUSH_DESCRIPTOR_PROPERTIES_KHR,
|
|
.pNext = &p13};
|
|
VkPhysicalDeviceProperties2 p2 = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, .pNext = &pd};
|
|
vkGetPhysicalDeviceProperties2(c->phys, &p2);
|
|
VkPhysicalDeviceProvokingVertexFeaturesEXT pvf = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT};
|
|
VkPhysicalDeviceVulkan13Features f13 = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
|
|
.pNext = c->has_provoking_vertex ? &pvf : NULL};
|
|
VkPhysicalDeviceFeatures2 f2 = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = &f13};
|
|
vkGetPhysicalDeviceFeatures2(c->phys, &f2);
|
|
uint32_t ne = 0;
|
|
vkEnumerateDeviceExtensionProperties(c->phys, NULL, &ne, NULL);
|
|
VkExtensionProperties *ep = calloc(ne ? ne : 1, sizeof *ep);
|
|
vkEnumerateDeviceExtensionProperties(c->phys, NULL, &ne, ep);
|
|
|
|
uint32_t real_align = (uint32_t)l->minStorageBufferOffsetAlignment, a = env_u32("NX2NAVI_VK_SSBO_ALIGN", 0);
|
|
if (a && !(a & (a - 1)) && a > real_align) l->minStorageBufferOffsetAlignment = a;
|
|
vk_caps.sbuf_align = l->minStorageBufferOffsetAlignment > 4 ? (uint32_t)l->minStorageBufferOffsetAlignment : 0;
|
|
|
|
uint32_t want = env_u32("NX2NAVI_VK_SUBGROUP", 32);
|
|
int pin = f13.subgroupSizeControl && cfg_bool("NX2NAVI_VK_SUBGROUP_PIN") && p13.minSubgroupSize <= want &&
|
|
want <= p13.maxSubgroupSize;
|
|
vk_caps.sg_size = want;
|
|
vk_caps.sg_pin_stages = pin ? p13.requiredSubgroupSizeStages : 0;
|
|
static const VkShaderStageFlags stage[3] = {VK_SHADER_STAGE_VERTEX_BIT, VK_SHADER_STAGE_FRAGMENT_BIT, VK_SHADER_STAGE_COMPUTE_BIT};
|
|
uint32_t told[3];
|
|
for (int i = 0; i < 3; i++) told[i] = vk_caps.sg_pin_stages & stage[i] ? want : sg.subgroupSize;
|
|
// Gathers (TLD4) pick their four texels with Maxwell's rounding; AMD and Intel round a coordinate that sits between
|
|
// two texels the other way, which draws dark lines along object edges in shadow-map filtering (docs/maxwell.md 2.10;
|
|
// seen on AMD). The translator biases gather coordinates by the host's sub-texel precision; NVIDIA rounds
|
|
// as Maxwell does. NX2NAVI_GATHER_BIAS=<bits> forces it (0: off).
|
|
uint32_t gather = env_u32("NX2NAVI_GATHER_BIAS", c->props.vendorID == 0x10de ? 0 : l->subTexelPrecisionBits);
|
|
// A register read before any write is an undefined load in SPIR-V, so function-local variables start at 0
|
|
// (docs/maxwell.md 3.12). Mesa folds undefined values freely; NVIDIA's compiler gives such a read a value of another
|
|
// lane of the warp: Ocarina's clustered-light loop reads registers that only some paths write, and on an RTX 5090
|
|
// the Great Fairy Fountain floor took an extra light term in 4x8-pixel tiles (one 32-lane warp each) and Link went
|
|
// dark with green speckles. On with every setting: vk_dlaa.c knows the game's FXAA by its zeroed SPIR-V hash too,
|
|
// and the shader cache keys (which include this) stay the same whether DLSS is on or off, so shaders prepared
|
|
// with one setting serve the other. NX2NAVI_ZERO_LOCALS=0 turns it off.
|
|
uint32_t zero_locals = env_u32("NX2NAVI_ZERO_LOCALS", 1);
|
|
// bda: global accesses the translator finds no storage buffer for go through the device address table (vk_gpa.c)
|
|
gfx_shader_set_host(vk_caps.float64, vk_caps.sbuf_align, told, gather, zero_locals != 0, vk_gpa_wanted_ctx(c));
|
|
if (gather) rt_log("vk caps: gather coordinates biased for %u sub-texel bits (NX2NAVI_GATHER_BIAS)", gather);
|
|
|
|
char dv[32];
|
|
driver_version(dv, sizeof dv, c->props.vendorID, c->props.driverVersion);
|
|
snprintf(summary, sizeof summary, "%s, driver %s \"%s\" version %s", c->props.deviceName, drv.driverName,
|
|
drv.driverInfo, dv);
|
|
uint32_t v = c->props.apiVersion;
|
|
VkPhysicalDevice p = c->phys;
|
|
rt_log("vk caps: %s, vendor %#06x device %#06x, driver %s \"%s\" (id %d, version %s, raw %#x), api %u.%u.%u",
|
|
c->props.deviceName, c->props.vendorID, c->props.deviceID, drv.driverName, drv.driverInfo, (int)drv.driverID, dv,
|
|
c->props.driverVersion, VK_API_VERSION_MAJOR(v), VK_API_VERSION_MINOR(v), VK_API_VERSION_PATCH(v));
|
|
rt_log("vk caps: subgroup size %u, stages %#x, ops %#x; size control %d (%u-%u, required stages %#x)", sg.subgroupSize,
|
|
(unsigned)sg.supportedStages, (unsigned)sg.supportedOperations, (int)f13.subgroupSizeControl, p13.minSubgroupSize,
|
|
p13.maxSubgroupSize, (unsigned)p13.requiredSubgroupSizeStages);
|
|
rt_log("vk caps: shaderFloat64 %d (enabled %d), storage buffer offset alignment %u (device %u), uniform %llu, "
|
|
"maxPushDescriptors %u, provoking vertex ext %d last %d",
|
|
f64_supported, vk_caps.float64, (unsigned)l->minStorageBufferOffsetAlignment, real_align,
|
|
(unsigned long long)l->minUniformBufferOffsetAlignment, pd.maxPushDescriptors, c->has_provoking_vertex,
|
|
(int)pvf.provokingVertexLast);
|
|
rt_log("vk caps: ext index_type_uint8 khr %d ext %d, attachment_feedback_loop_layout %d dynamic_state %d, "
|
|
"graphics_pipeline_library %d; depth attachment D16 %c D24S8 %c X8D24 %c D32 %c D32S8 %c",
|
|
has_ext(ep, ne, "VK_KHR_index_type_uint8"), has_ext(ep, ne, "VK_EXT_index_type_uint8"),
|
|
has_ext(ep, ne, "VK_EXT_attachment_feedback_loop_layout"), has_ext(ep, ne, "VK_EXT_attachment_feedback_loop_dynamic_state"),
|
|
has_ext(ep, ne, "VK_EXT_graphics_pipeline_library"), depth_ok(p, VK_FORMAT_D16_UNORM),
|
|
depth_ok(p, VK_FORMAT_D24_UNORM_S8_UINT), depth_ok(p, VK_FORMAT_X8_D24_UNORM_PACK32), depth_ok(p, VK_FORMAT_D32_SFLOAT),
|
|
depth_ok(p, VK_FORMAT_D32_SFLOAT_S8_UINT));
|
|
char gpl_note[128];
|
|
if (gpl_off) snprintf(gpl_note, sizeof gpl_note, ", off by %s", gpl_off);
|
|
else gpl_note[0] = '\0';
|
|
rt_log("vk caps: gpl %s fast-link %s (graphicsPipelineLibrary %u%s)", vk_caps.gpl ? "yes" : "no",
|
|
gpl_fast ? "yes" : "no", (unsigned)gpl_lib, gpl_note);
|
|
rt_log("vk caps: fallbacks: float64 %s, storage buffers %s %u, subgroup pin %u on stages %#x, translator subgroup vs/fs/cs %u/%u/%u",
|
|
vk_caps.float64 ? "native" : "softfloat64", vk_caps.sbuf_align ? "rounded down to" : "exact, alignment",
|
|
(unsigned)l->minStorageBufferOffsetAlignment, want, (unsigned)vk_caps.sg_pin_stages, told[0], told[1], told[2]);
|
|
free(ep);
|
|
}
|