From bbeecce88c6ddf00f76e502e68bee566b5212895 Mon Sep 17 00:00:00 2001 From: chrg6 Date: Sat, 14 Nov 2026 05:27:59 -0500 Subject: [PATCH] Never take the mesh emulation on an NVIDIA card, where it ended the game inside the driver. The low-end GPU mode sets mesh_emulation = 1, and on a card without mesh shaders the emulation then draws the game's task and mesh programs in place of the mesh fallback. On NVIDIA's cards without mesh shaders that ended the game at the first mesh draws of an area with an access violation inside the driver, at nvoglv64.dll+0x10cdffe on a GTX 1050 Ti and on a GTX 1070 (both 0.10.1), and a Titan X (Pascal) report names the same mode. The GTX 1050 Ti's owner set mesh_emulation = 0 and played on through the fallback with the rest of the mode on. The decision (vk_mesh_path.h) takes the card's vendor: an NVIDIA card is drawn for as if the emulation were not asked for, and the boot line says so. The replay and the pipeline code read the decided path from the device context where each read the setting itself. tests/mesh_path_check.c has the new cases. Not run in the game: no GTX 10 card here. mesh_path_check passes and every file that includes the changed headers compiles. Co-Authored-By: Claude Fable 5.2 --- docs/releases/v0.10.2.md | 4 ++++ runtime/vk.h | 2 ++ runtime/vk_device.c | 3 ++- runtime/vk_exec_draw.c | 2 +- runtime/vk_mesh_path.h | 23 +++++++++++++++++------ runtime/vk_pipeline.c | 7 +++---- tests/mesh_path_check.c | 21 ++++++++++++++++++--- 7 files changed, 47 insertions(+), 15 deletions(-) diff --git a/docs/releases/v0.10.2.md b/docs/releases/v0.10.2.md index 7ce74ccd..03a2b4d5 100644 --- a/docs/releases/v0.10.2.md +++ b/docs/releases/v0.10.2.md @@ -40,6 +40,10 @@ cards). code 70, until nx2navi.ini was edited by hand): with DLSS Frame Generation on and the *Graphics card* setting on the card that is not NVIDIA's, the device could not be made. The game now starts on the NVIDIA card, and where there is none, or frame generation cannot be set up, it starts without frame generation and the log says why. +- **A crash on GeForce GTX 10 cards with the low-end GPU mode on** (the game closing inside NVIDIA's driver at the + first grass or foliage of an area, reported on a GTX 1050 Ti, a GTX 1070 and a Titan X): the mode turned on an + older way of drawing the game's mesh shaders that this driver does not survive. NVIDIA cards without mesh shaders + now always take the mesh shader fallback, which ran on the GTX 1050 Ti with the rest of the mode still on. - **Crashes and hangs found in a review of the whole runtime**; none was seen in our own play, and each fix has a check of its own: - a crash when the game read memory the lazy write-back was just handing to the GPU (NVIDIA and Intel cards); diff --git a/runtime/vk.h b/runtime/vk.h index d6e0d865..38b5ca24 100644 --- a/runtime/vk.h +++ b/runtime/vk.h @@ -74,6 +74,8 @@ typedef struct VkCtx { has_mesh_queries; // meshShaderQueries, kept while NX2NAVI_GPU_PASSES or NX2NAVI_DUMP is set int mesh_fallback; // task and mesh programs run as compute passes and are drawn from buffers // (mesh_fallback: auto as vk_mesh_path.h decides; vk_exec_draw.c "mesh fallback") + int mesh_emulation; // they are drawn by the mesh emulation (mesh_emulation = 1 where + // vk_mesh_path.h lets it act; vk_exec_draw.c, vk_pipeline.c) // Feedback loops (maxwell.md 1.9, vk_exec_draw.c feedback_prepare): a draw samples a target of its open pass that // an earlier draw of the pass wrote. NX2NAVI_FEEDBACK=0: VK_FEEDBACK_OFF (sampled in GENERAL without a barrier, the // old behaviour); VK_FEEDBACK_LAYOUT with VK_EXT_attachment_feedback_loop_layout and dynamicRenderingLocalRead diff --git a/runtime/vk_device.c b/runtime/vk_device.c index 494b03e9..79bfdc05 100644 --- a/runtime/vk_device.c +++ b/runtime/vk_device.c @@ -634,8 +634,9 @@ static int create_device(void) { // has_mesh_shader keeps saying what the hardware has: the recorder thread (vk_rec.c) and the low-end card // test (vk_low_end_card) go by it, not by how mesh programs are drawn. const char *mf = cfg_value("NX2NAVI_MESH_FALLBACK"), *me = cfg_value("NX2NAVI_MESH_EMULATION"), *why = ""; - int path = mesh_path_decide(g_vk.has_mesh_shader, vk_caps.amd_sc, mf, me, &why); + int path = mesh_path_decide(g_vk.has_mesh_shader, vk_caps.amd_sc, g_vk.props.vendorID == 0x10de, mf, me, &why); g_vk.mesh_fallback = path == MESH_PATH_FALLBACK; + g_vk.mesh_emulation = path == MESH_PATH_EMULATION; g_mesh_path = path == MESH_PATH_EMULATION ? "emulated (mesh_emulation)" : path == MESH_PATH_FALLBACK ? (g_vk.has_mesh_shader ? "through the mesh fallback on a device with mesh " "shaders (compute passes)" diff --git a/runtime/vk_exec_draw.c b/runtime/vk_exec_draw.c index ac156232..dad7ac6d 100644 --- a/runtime/vk_exec_draw.c +++ b/runtime/vk_exec_draw.c @@ -6186,7 +6186,7 @@ static int mesh_emu_draw_prim(VkExec *ex) { static int mesh_emu_on(void) { static int on = -1; if (on < 0) { - on = !vk_ctx()->has_mesh_shader && rt_knob("MESH_EMULATION", "0"); + on = vk_ctx()->mesh_emulation; // decided with the mesh fallback at device creation (vk_mesh_path.h) if (on) rt_log("vk: mesh shaders emulated with compute (NX2NAVI_MESH_EMULATION)"); } return on; diff --git a/runtime/vk_mesh_path.h b/runtime/vk_mesh_path.h index c64d5401..65e26fb8 100644 --- a/runtime/vk_mesh_path.h +++ b/runtime/vk_mesh_path.h @@ -4,7 +4,8 @@ // native VK_EXT_mesh_shader pipelines // fallback the mesh fallback: the programs run as compute passes and an ordinary draw reads what they wrote // (vk_exec_draw.c "mesh fallback", vk_mesh_fb.c) -// emulation the mesh emulation (mesh_emulation = 1 on a device without mesh shaders, vk_exec_draw.c) +// emulation the mesh emulation (mesh_emulation = 1 on a device without mesh shaders, vk_exec_draw.c); never on an +// NVIDIA card (below) // none a device without mesh shaders with both off: mesh draws are skipped // // mesh_fallback = auto (the default) takes the fallback on a device without mesh shaders, and on AMD's Windows driver @@ -18,26 +19,36 @@ // marks leave to try, at the cost of a compute pass and a pass break per mesh draw, and of mesh programs that are // left out of the picture until their compute passes are built, at their first sight in a run. mesh_fallback = 0 // draws them natively again; 1 takes the fallback on every device. +// +// The mesh emulation is the older of the two ways for a card without mesh shaders, and the low-end GPU mode turns it on +// (cfg.c LOWEND). On NVIDIA's cards without mesh shaders (GTX 10 and older) it ended the game inside the driver at the +// first mesh draws of an area: an access violation at nvoglv64.dll+0x10cdffe on a GTX 1050 Ti and on a GTX 1070, both +// on 0.10.1, and a Titan X (Pascal) report of the same mode. The GTX 1050 Ti ran on once mesh_emulation was set to 0, +// through the fallback, with the rest of the low-end mode still on. So an NVIDIA card never takes the emulation: with +// it asked for, the card is drawn for as if it were not. #pragma once #include enum { MESH_PATH_NATIVE, MESH_PATH_FALLBACK, MESH_PATH_EMULATION, MESH_PATH_NONE }; // has_mesh: the device has VK_EXT_mesh_shader with task and mesh shaders; amd_sc: AMD's Windows driver for RDNA 1 and -// 2; fallback: the mesh_fallback setting ("auto", "0", "1"; anything else reads as auto); emulation: mesh_emulation -// ("0" off). *why: the reason, for the boot line. -static inline int mesh_path_decide(int has_mesh, int amd_sc, const char *fallback, const char *emulation, +// 2; nvidia: an NVIDIA card; fallback: the mesh_fallback setting ("auto", "0", "1"; anything else reads as auto); +// emulation: mesh_emulation ("0" off). *why: the reason, for the boot line. +static inline int mesh_path_decide(int has_mesh, int amd_sc, int nvidia, const char *fallback, const char *emulation, const char **why) { const char *w; int path; - if (!has_mesh && strcmp(emulation, "0")) + if (!has_mesh && strcmp(emulation, "0") && !nvidia) path = MESH_PATH_EMULATION, w = "mesh_emulation = 1 on a device without mesh shaders"; else if (!strcmp(fallback, "1")) path = MESH_PATH_FALLBACK, w = "mesh_fallback = 1"; else if (!strcmp(fallback, "0")) path = has_mesh ? MESH_PATH_NATIVE : MESH_PATH_NONE, w = "mesh_fallback = 0"; else if (!has_mesh) - path = MESH_PATH_FALLBACK, w = "this device has no mesh shaders"; + path = MESH_PATH_FALLBACK, w = strcmp(emulation, "0") ? "this device has no mesh shaders; mesh_emulation is " + "not used on an NVIDIA card, where it ended the game " + "inside the driver" + : "this device has no mesh shaders"; else if (amd_sc) path = MESH_PATH_FALLBACK, w = "the default on AMD's driver for RDNA 1 and 2, where native mesh pipelines lost " "the device in play; mesh_fallback = 0 draws them natively"; diff --git a/runtime/vk_pipeline.c b/runtime/vk_pipeline.c index 139190a2..ba177650 100644 --- a/runtime/vk_pipeline.c +++ b/runtime/vk_pipeline.c @@ -3040,10 +3040,9 @@ static Layout *layout_get(Bindings *B) { // mesh stages' bindings are visible to the compute stage (they run as compute pipelines of this layout), the // fragment stage's stay; push constants (MESH_EMU_PC_BYTES) for the compute stages and the generated vertex shader; // set 0 gets a template per bind point. Caller holds P.lock. -static int mesh_emu_on(void) { - static int on = -1; - if (on < 0) on = rt_knob("MESH_EMULATION", "0"); - return on; +static int mesh_emu_on(void) { // decided with the mesh fallback at device creation (vk_mesh_path.h) + const VkCtx *c = vk_ctx(); + return c && c->mesh_emulation; } static Layout *layout_get_emu(Bindings *B) { diff --git a/tests/mesh_path_check.c b/tests/mesh_path_check.c index 6bc38a97..467b73f3 100644 --- a/tests/mesh_path_check.c +++ b/tests/mesh_path_check.c @@ -11,14 +11,18 @@ static int fails; static const char *const names[] = {"native", "fallback", "emulation", "none"}; -static void expect(int has_mesh, int amd_sc, const char *mf, const char *me, int want, const char *what) { +static void expect_nv(int has_mesh, int amd_sc, int nvidia, const char *mf, const char *me, int want, const char *what) { const char *why = NULL; - int got = mesh_path_decide(has_mesh, amd_sc, mf, me, &why); + int got = mesh_path_decide(has_mesh, amd_sc, nvidia, mf, me, &why); CHECK(got == want, "%s (mesh shaders %d, AMD RDNA 1/2 driver %d, mesh_fallback %s, mesh_emulation %s): %s, not %s", what, has_mesh, amd_sc, mf, me, names[got], names[want]); CHECK(why && *why, "%s: no reason for the boot line", what); } +static void expect(int has_mesh, int amd_sc, const char *mf, const char *me, int want, const char *what) { + expect_nv(has_mesh, amd_sc, 0, mf, me, want, what); +} + int main(void) { // ---- the default (auto, emulation off) by device expect(1, 0, "auto", "0", MESH_PATH_NATIVE, "NVIDIA, Intel, Mesa or AMD's RDNA 3/4 driver with mesh shaders"); @@ -37,14 +41,25 @@ int main(void) { expect(0, 1, "1", "1", MESH_PATH_EMULATION, "no mesh shaders, both set"); expect(1, 1, "auto", "1", MESH_PATH_FALLBACK, "an RX 6000 card with mesh_emulation = 1 (low_end_gpu)"); expect(1, 0, "auto", "1", MESH_PATH_NATIVE, "a card with mesh shaders and mesh_emulation = 1"); + // ---- an NVIDIA card never takes the emulation, which ended the game inside its driver (GTX 10, low_end_gpu) + expect_nv(0, 0, 1, "auto", "1", MESH_PATH_FALLBACK, "a GTX 10 card with mesh_emulation = 1 (low_end_gpu)"); + expect_nv(0, 0, 1, "1", "1", MESH_PATH_FALLBACK, "a GTX 10 card with both set"); + expect_nv(0, 0, 1, "0", "1", MESH_PATH_NONE, "a GTX 10 card with mesh_emulation = 1 and mesh_fallback = 0"); + expect_nv(0, 0, 1, "auto", "0", MESH_PATH_FALLBACK, "a GTX 10 card by default"); + expect_nv(1, 0, 1, "auto", "1", MESH_PATH_NATIVE, "an RTX card with mesh_emulation = 1"); + expect_nv(1, 0, 1, "auto", "0", MESH_PATH_NATIVE, "an RTX card by default"); // ---- a value the settings table does not list reads as auto expect(1, 1, "", "0", MESH_PATH_FALLBACK, "an RX 6000 card with an empty mesh_fallback"); expect(1, 0, "yes", "0", MESH_PATH_NATIVE, "a card with mesh shaders and mesh_fallback = yes"); // ---- the reason names the driver default, and how to turn it off const char *why = NULL; - mesh_path_decide(1, 1, "auto", "0", &why); + mesh_path_decide(1, 1, 0, "auto", "0", &why); CHECK(why && strstr(why, "mesh_fallback = 0"), "the RX 6000 default's reason does not say how to turn it off: %s", why ? why : "(none)"); + mesh_path_decide(0, 0, 1, "auto", "1", &why); + CHECK(why && strstr(why, "mesh_emulation"), "a GTX 10 card with mesh_emulation = 1: the reason does not name it: %s", + why ? why : "(none)"); + printf("mesh_path_check: %s\n", fails ? "FAILED" : "passed"); return fails ? 1 : 0; }