mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Guard float colour outputs against NaN, start mesh counts at zero, and read XInput pads SDL misses.
Patch 0027: a float colour output turns NaN into 0 and clamps the rest to +-1e30 before it reaches its target, so one bad texel cannot spread through bloom, blur and downsample passes as black or white blocks; integer targets keep their bits. Task and mesh shaders start their mesh and primitive counts at 0 behind a barrier, and a task emits at most one mesh group per thread, so a task that culls everything no longer launches what the shared memory last held. Without a gamepad SDL can open, the first connected XInput controller (slots 0-3, empty slots asked once a second) is read directly. Kokiri Forest: 60 fps, GPU 5.5 ms a frame, unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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"], []):
|
||||
|
||||
@@ -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<Insn>();
|
||||
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);
|
||||
}
|
||||
|
||||
18
tools/xlate/patches/0027-fragment-output-nan-guard.patch
Normal file
18
tools/xlate/patches/0027-fragment-output-nan-guard.patch
Normal file
@@ -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))
|
||||
{
|
||||
Reference in New Issue
Block a user