From 327fd81c4f4c916b242fc664a514535491d0c877 Mon Sep 17 00:00:00 2001 From: NotRexed Date: Tue, 10 Nov 2026 17:13:50 -0500 Subject: [PATCH 1/2] feat(menu): a Mesh shaders row: Automatic, Native or Compute (#1001) The game's task and mesh programs ran on the GPU's own mesh shaders where it has them, and as compute shaders (issue 640's fallback) where not, with the Navi 22 cards' task + mesh draws on the fallback too (issue 996). The choice was only a test switch's (RX_VK_FORCE_MESH_FALLBACK, RX_VK_TASK_MESH_FALLBACK). The Graphics tab now has it, after Robust buffers, read as the device is made (mesh_shaders= in hookshot_options.ini; applies after a restart): - Automatic (0): as before. - Native (1): the card's own mesh shaders for every draw, the Navi 22 task + mesh draws included. A GPU without mesh shaders still uses the compute path. - Compute (2): the compute path on every GPU, for a card that crashes or leaves objects out with native mesh shaders. RX_VK_FORCE_MESH_FALLBACK=1 is Compute over the row, and RX_VK_TASK_MESH_FALLBACK still decides the task + mesh draws. The log says which: "vk: mesh shaders row: ...". Co-Authored-By: Claude Opus 5.5 --- gpu/vk/vk_main.cpp | 31 ++++++++++++++++++++++++++++--- gpu/vk/vk_menu.cpp | 11 +++++++++++ gpu/vk/vk_menu.h | 2 ++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/gpu/vk/vk_main.cpp b/gpu/vk/vk_main.cpp index 01e28f557..7c387dad1 100644 --- a/gpu/vk/vk_main.cpp +++ b/gpu/vk/vk_main.cpp @@ -223,9 +223,30 @@ bool create_device() { flag = has_extension(exts, name); if (flag) want.push_back(name); }; - // (RX_VK_FORCE_MESH_FALLBACK=1, a test: mesh shaders not used where they are, so the no-mesh fallback the GTX 10xx and RX 5000 - // cards run (issue 640) can be tried on any GPU) - if (getenv("RX_VK_FORCE_MESH_FALLBACK") && atoi(getenv("RX_VK_FORCE_MESH_FALLBACK")) != 0) dev.mesh_shader = false; + // (the Graphics tab's Mesh shaders row, mesh_shaders= in hookshot_options.ini, #1001: 0 Automatic (native where there are mesh + // shaders, the compute fallback where not and for the task + mesh draws of the cards below), 1 Native (no fallback where + // there are mesh shaders), 2 Compute (the fallback on every GPU). RX_VK_FORCE_MESH_FALLBACK=1, a test, is Compute over it: + // mesh shaders not used where they are, so the no-mesh fallback the GTX 10xx and RX 5000 cards run (issue 640) can be tried + // on any GPU) + static const int mesh_mode = [] { + char exe[MAX_PATH]; + const DWORD len = game_module_path(exe, MAX_PATH); // (the game folder, not bin beside the launcher: vk_paths.h) + std::string ini(exe, len); + ini = ini.substr(0, ini.find_last_of("\\/") + 1) + "hookshot_options.ini"; + int v = 0; + if (FILE* fl = fopen(ini.c_str(), "r")) { + char line[80]; + while (fgets(line, sizeof line, fl)) + if (!strncmp(line, "mesh_shaders=", 13)) { + v = atoi(line + 13); + break; + } + fclose(fl); + } + if (const char* e = getenv("RX_VK_FORCE_MESH_FALLBACK")) v = atoi(e) != 0 ? 2 : v; + return v >= 0 && v <= 2 ? v : 0; + }(); + if (mesh_mode == 2) dev.mesh_shader = false; else optional(VK_EXT_MESH_SHADER_EXTENSION_NAME, dev.mesh_shader); optional(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, dev.external_memory_host); optional(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, dev.external_memory_fd); // (guest memory as udmabufs: vk_memory.cpp import_dma_buf) @@ -434,9 +455,13 @@ bool create_device() { // Navi 22 can time out on task + mesh draws in the AMD Windows driver (issue 996). task_mesh_fallback = dev.props.vendorID == 0x1002 && dev.props.deviceID == 0x73df; #endif + if (mesh_mode == 1) task_mesh_fallback = false; // (Native: the card's own mesh shaders for every draw) if (const char* e = getenv("RX_VK_TASK_MESH_FALLBACK")) task_mesh_fallback = atoi(e) != 0; dev.task_mesh_fallback = task_mesh_fallback && dev.mesh_shader && f12.bufferDeviceAddress && !getenv("RX_VK_NO_MESH_FALLBACK"); if (dev.task_mesh_fallback) logf("vk: task + mesh programs use the compute fallback (RX_VK_TASK_MESH_FALLBACK=0: native)"); + logf("vk: mesh shaders row: %s (%s)", mesh_mode == 2 ? "Compute" : mesh_mode == 1 ? "Native" : "Automatic", + dev.mesh_shader ? (dev.task_mesh_fallback ? "native, task + mesh draws on the compute path" : "native") + : dev.mesh_fallback ? "the compute path" : "none"); if (robust) logf("vk: robust buffer access %s", robust_ok ? "on" : "not supported"); f.features.robustBufferAccess = robust_ok ? VK_TRUE : VK_FALSE; // (off by default: the game's accesses stay in their buffers) dev.features = f.features; diff --git a/gpu/vk/vk_menu.cpp b/gpu/vk/vk_menu.cpp index 9cb3ead0f..0e7d20127 100644 --- a/gpu/vk/vk_menu.cpp +++ b/gpu/vk/vk_menu.cpp @@ -505,6 +505,10 @@ const char* tx_robust(int v) { static const char* t[] = {"Auto", "Off", "On"}; return (unsigned)v < 3 ? t[v] : t[0]; } +const char* tx_mesh_mode(int v) { + static const char* t[] = {"Automatic", "Native", "Compute"}; + return (unsigned)v < 3 ? t[v] : t[0]; +} const char* tx_preset(int v) { static const char* t[] = {"Custom", "Minimum (4GB)", "Low (6GB)", "Balanced (8GB)", "High (12GB)", "Ultra (16GB+)"}; return t[std::min(std::max(v, 0), 5)]; @@ -607,6 +611,9 @@ MENU_HINTS(hn_wide_menus, 0, "The menus stretched over the whole screen, as befo "The menus in the middle 16:9; beside them, what the game draws there, out of focus.", "The menus in the middle 16:9; black beside them.") MENU_HINTS(hn_robust, 0, "On, on every card.", "Can be faster on some NVIDIA cards.", "Always on.") +MENU_HINTS(hn_mesh_mode, 0, "The GPU's own mesh shaders where it has them, the compute path where it has not.", + "The GPU's own mesh shaders for every draw. A GPU without them still uses the compute path.", + "The compute path on every GPU: try it if your card crashes or leaves objects out. Usually a little slower.") MENU_HINTS(hn_fps, 0, nullptr, "With frame generation on, the game's own rate too.") MENU_HINTS(hn_menu_size, 0, "Large in fullscreen on a screen of 1080 lines or more while a controller is in use, else Medium.", "Smaller, for a desk.", "The usual size.", "Larger, for a TV across the room.") @@ -690,6 +697,9 @@ Row rows[] = { {TAB_GRAPHICS, "Robust buffers", &opts.robust, 0, 2, 1, tx_robust, "Bounds-checked buffer reads: a read past a buffer's end is dropped instead of stopping the GPU. Applies after a " "restart.", hn_robust, true}, + {TAB_GRAPHICS, "Mesh shaders", &opts.mesh_mode, 0, 2, 1, tx_mesh_mode, + "How the game's mesh programs (much of the scenery) run: on the GPU's own mesh shaders, or as compute shaders, which " + "every GPU has. Applies after a restart.", hn_mesh_mode, true}, // Audio {TAB_AUDIO, "Volume", &opts.volume, 0, 100, 5, tx_percent, @@ -1077,6 +1087,7 @@ const char* ini_key(const Row& r) { if (r.value == &opts.upscale) return "upscaling"; if (r.value == &opts.dlss) return "dlss"; // (as opts.dlss and RX_VK_DLSS) if (r.value == &opts.handheld) return "handheld_profile"; // (the Profile row; host/nn_applet.c reads it at the start) + if (r.value == &opts.mesh_mode) return "mesh_shaders"; // (gpu/vk/vk_main.cpp reads it as the device is made) static char k[32]; size_t n = 0; for (const char* p = r.name; *p && n + 1 < sizeof k; p++) k[n++] = *p == ' ' ? '_' : (char)tolower(*p); diff --git a/gpu/vk/vk_menu.h b/gpu/vk/vk_menu.h index 64bcaa25e..a6bbd717c 100644 --- a/gpu/vk/vk_menu.h +++ b/gpu/vk/vk_menu.h @@ -60,6 +60,8 @@ struct Options { int gyro_deadzone = 1; // the motion's deadzone: 0 tight, 1 standard (the console's default), 2 loose int prompts = 0; // the button icons: 0 Auto (the controller), 1 Switch, 2 Xbox, 3 PS4, 4 PS5, 5 Steam Deck (host/prompts.c) int robust = 0; // bounds-checked buffer access: 0 Auto (on), 1 Off, 2 On (gpu/vk/vk_main.cpp; RX_VK_ROBUST) + int mesh_mode = 0; // task and mesh programs: 0 Automatic, 1 Native, 2 Compute (gpu/vk/vk_main.cpp: mesh_shaders=; + // RX_VK_FORCE_MESH_FALLBACK and RX_VK_TASK_MESH_FALLBACK over it) int descriptor_reuse = 1; // (on by default since options_version 4) a draw keeps its descriptors through other slots' registrations (gpu/vk/vk_draw.cpp; RX_VK_DESC_SLOTGEN) int sampler_cache = 0; // sampler ids answer their parameters without the registrations' lock (gpu/vk/vk_draw.cpp; RX_VK_SAMPLER_CACHE) int preset = 0; // quality preset: 0 Custom, 1 Minimum (4GB), 2 Low (6GB), 3 Balanced (8GB), 4 High (12GB), 5 Ultra (16GB+) From abb7d1736c794e79837ccd2cbc1bf623973e8348 Mon Sep 17 00:00:00 2001 From: NotRexed Date: Tue, 10 Nov 2026 18:11:10 -0500 Subject: [PATCH 2/2] tests: the menu stubs' Options carry mesh_mode (ini_key names it) Co-Authored-By: Claude Opus 5.5 --- tests/test_audio_output_menu.py | 2 +- tests/test_stick_dead_zone.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_audio_output_menu.py b/tests/test_audio_output_menu.py index 846674539..2b02cf264 100644 --- a/tests/test_audio_output_menu.py +++ b/tests/test_audio_output_menu.py @@ -41,7 +41,7 @@ static int au_device_channels(void) { device_asked++; return device_channels; } HOST struct Row { int tab; const char* name; int* value; }; -struct { int output_res, resolution, fps_cap, upscale, dlss, handheld, audio_output; } opts; +struct { int output_res, resolution, fps_cap, upscale, dlss, handheld, mesh_mode, audio_output; } opts; MENU int main(int argc, char **argv) { assert(argc == 7); diff --git a/tests/test_stick_dead_zone.py b/tests/test_stick_dead_zone.py index 26e6cfae6..53a386763 100644 --- a/tests/test_stick_dead_zone.py +++ b/tests/test_stick_dead_zone.py @@ -127,7 +127,7 @@ def main(): #include #define CHECK(x) do { if (!(x)) { fprintf(stderr, "FAIL:%d %s\n", __LINE__, #x); exit(1); } } while (0) enum MenuTab { TAB_CONTROLS }; -struct Options { int output_res = 0, resolution = 0, fps_cap = 0, upscale = 0, dlss = 0, handheld = 0, stick_dead_zone = 0; } opts; +struct Options { int output_res = 0, resolution = 0, fps_cap = 0, upscale = 0, dlss = 0, handheld = 0, mesh_mode = 0, stick_dead_zone = 0; } opts; static int parse_output(const char* text) { return atoi(text); } // (the Output row's own parse: not what this checks) ''' + block(menu, "struct Row {") + ";\n" + block(menu, "const char* tx_stick_dead_zone(") + "\n" + \ "Row rows[] = {" + row.group(0) + "};\n" + block(menu, "const char* ini_key(") + r'''