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 cd65f3d92..ce1aaa678 100644 --- a/gpu/vk/vk_menu.cpp +++ b/gpu/vk/vk_menu.cpp @@ -501,6 +501,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)]; @@ -603,6 +607,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, 1, "Smaller, for a desk.", "The usual size.", "Larger, for a TV across the room.") MENU_HINTS(hn_speed, 0, "A step each frame: the game slows down whenever it runs below 60 fps.", @@ -685,6 +692,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, @@ -1072,6 +1082,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 d9fd6569a..9c669b172 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+) 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'''