diff --git a/runtime/input.c b/runtime/input.c index e1208877..071ac258 100644 --- a/runtime/input.c +++ b/runtime/input.c @@ -638,9 +638,67 @@ static int pad_physical(uint8_t pd[PAD_COUNT]) { // Under lock, state 1: the open gamepad's buttons (Switch bits after NX2NAVI_PAD_LAYOUT and the NX2NAVI_BIND_* lines) and // sticks; 0 without a connected gamepad. +#ifdef _WIN32 +// Without a gamepad SDL can open, Xbox controllers are read through XInput directly (xinput1_4, else xinput9_1_0): +// SDL may miss a pad another program or driver presents only as XInput. Slots 0-3, the first connected one; a slot +// found empty is asked again once a second, as XInputGetState on an empty slot takes milliseconds. +typedef struct { uint16_t buttons; uint8_t lt, rt; int16_t lx, ly, rx, ry; } XiPad; +typedef struct { uint32_t packet; XiPad pad; } XiState; +typedef uint32_t(__stdcall *XiGetState)(uint32_t, XiState *); +__declspec(dllimport) void *__stdcall LoadLibraryA(const char *); +__declspec(dllimport) void *__stdcall GetProcAddress(void *, const char *); + +// Under lock: the first connected XInput pad's inputs by position (input_map.h PAD_*) and sticks (y up); 0 without one. +static int xinput_read(uint8_t pd[PAD_COUNT], int32_t *raw) { + static XiGetState get; + static int tried, slot = -1; + static uint64_t next_try; + if (!tried) { + tried = 1; + void *m = LoadLibraryA("xinput1_4.dll"); + if (!m) m = LoadLibraryA("xinput9_1_0.dll"); + if (m) get = (XiGetState)GetProcAddress(m, "XInputGetState"); + } + if (!get) return 0; + XiState st; + if (slot >= 0 && get((uint32_t)slot, &st) != 0) { + rt_log("input: XInput controller %d disconnected", slot); + slot = -1; + } + if (slot < 0) { + uint64_t now = plat_now_ns(); + if (now < next_try) return 0; + next_try = now + 1000000000ull; + for (int i = 0; i < 4 && slot < 0; i++) + if (get((uint32_t)i, &st) == 0) slot = i, rt_log("input: XInput controller %d (SDL has no gamepad)", i); + if (slot < 0) return 0; + } + uint16_t w = st.pad.buttons; + static const struct { uint16_t bit; uint8_t pad; } map[] = { + {0x1000, PAD_SOUTH}, {0x2000, PAD_EAST}, {0x4000, PAD_WEST}, {0x8000, PAD_NORTH}, {0x0040, PAD_LSTICK}, + {0x0080, PAD_RSTICK}, {0x0100, PAD_L}, {0x0200, PAD_R}, {0x0010, PAD_START}, {0x0020, PAD_BACK}, + {0x0004, PAD_DPAD_LEFT}, {0x0001, PAD_DPAD_UP}, {0x0008, PAD_DPAD_RIGHT}, {0x0002, PAD_DPAD_DOWN}}; + for (size_t i = 0; i < sizeof map / sizeof *map; i++) pd[map[i].pad] = (w & map[i].bit) != 0; + pd[PAD_ZL] = st.pad.lt > 128, pd[PAD_ZR] = st.pad.rt > 128; + raw[0] = st.pad.lx, raw[1] = st.pad.ly, raw[2] = st.pad.rx, raw[3] = st.pad.ry; + return 1; +} +#endif + static int pad_read(uint64_t *b, int32_t sl[2], int32_t sr[2]) { uint8_t pd[PAD_COUNT] = {0}; +#ifdef _WIN32 + if (!pad_physical(pd)) { + int32_t raw[4]; + if (!xinput_read(pd, raw)) return 0; + *b |= input_map_pad_buttons(pd, pad_layout_get()); + input_pad_stick(raw[0], raw[1], stick_dead_get(0), sl); + input_pad_stick(raw[2], raw[3], stick_dead_get(1), sr); + return 1; + } +#else if (!pad_physical(pd)) return 0; +#endif *b |= input_map_pad_buttons(pd, pad_layout_get()); stick(SDL_GAMEPAD_AXIS_LEFTX, SDL_GAMEPAD_AXIS_LEFTY, stick_dead_get(0), sl); stick(SDL_GAMEPAD_AXIS_RIGHTX, SDL_GAMEPAD_AXIS_RIGHTY, stick_dead_get(1), sr); diff --git a/tools/build_xlate.py b/tools/build_xlate.py index f72b82da..e34ca46f 100644 --- a/tools/build_xlate.py +++ b/tools/build_xlate.py @@ -99,8 +99,8 @@ def prepare_sources() -> Path: for junk in list(src.glob("src/*/obj")) + list(src.glob("src/*/bin")): shutil.rmtree(junk) patches = sorted((XLATE / "patches").glob("*.patch")) - if len(patches) != 25: - sys.exit(f"expected 25 patches, found {len(patches)}") + if len(patches) != 26: + sys.exit(f"expected 26 patches, found {len(patches)}") for patch in patches: # git apply works outside a repository when given a directory; --check first so a failure names the patch. for extra in (["--check"], []): diff --git a/tools/xlate/MeshSpirv.cs b/tools/xlate/MeshSpirv.cs index db507d2a..2018715f 100644 --- a/tools/xlate/MeshSpirv.cs +++ b/tools/xlate/MeshSpirv.cs @@ -252,13 +252,20 @@ static class MeshSpirv body.Add(new Insn(OpFunction, new[] { tVoid, entryFn, 0u, tFnVoid })); Label(); uint tid = Emit(OpLoad, tUint, lii); - if (m.Stage == 7 && m.HasTask) { CopyPayload(tid, true); Barrier(); } + // The counts start at 0: a task that culls everything never writes its mesh count, and the word then held what + // the workgroup's shared memory last had, up to tens of thousands of mesh groups and a lost device; a mesh + // shader that emits nothing leaves its primitive count the same way. + uint countWord = C((uint)(m.Stage == 6 ? (m.Pay + 4) / 4 : m.Map / 4)); + If(tid, C(1), () => Do(OpStore, Emit(OpAccessChain, pSharedUint, sharedVar, countWord), C(0))); + if (m.Stage == 7 && m.HasTask) CopyPayload(tid, true); + Barrier(); Emit(OpFunctionCall, tVoid, mainFn); Barrier(); var modes = new List(); if (m.Stage == 6) { - uint count = SharedWord(C((uint)(m.Pay + 4) / 4)); + // one mesh group per task thread at most: a garbage count cannot launch more than the task could fill + uint count = UMin(SharedWord(C((uint)(m.Pay + 4) / 4)), C((uint)Math.Max(1, m.Threads))); CopyPayload(tid, false); Do(OpEmitMeshTasksEXT, count, C(1), C(1), payloadVar); } diff --git a/tools/xlate/patches/0027-fragment-output-nan-guard.patch b/tools/xlate/patches/0027-fragment-output-nan-guard.patch new file mode 100644 index 00000000..46fb5e66 --- /dev/null +++ b/tools/xlate/patches/0027-fragment-output-nan-guard.patch @@ -0,0 +1,18 @@ +--- a/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs ++++ b/src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs +@@ -495,6 +495,15 @@ + + Operand src = Register(regIndexBase + component, RegisterType.Gpr); + ++ // NX2NAVI: a float colour output carries no NaN or infinity on to its target: one NaN texel spreads ++ // through the averages of bloom, blur and downsample passes into blocks of black or white. NaN ++ // becomes 0 and the rest is clamped to +-1e30; integer targets keep their bits. ++ if ((TranslatorContext.Definitions.GetFragmentOutputColorType(rtIndex) & AggregateType.ElementTypeMask) == AggregateType.FP32) ++ { ++ Operand clamped = this.FPMinimum(this.FPMaximum(src, ConstF(-1e30f)), ConstF(1e30f)); ++ src = this.ConditionalSelect(this.IsNan(src), ConstF(0f), clamped); ++ } ++ + // Perform B <-> R swap if needed, for BGRA formats (not supported on OpenGL). + if (!supportsBgra && (component == 0 || component == 2)) + {