Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dm/games/0400C76024710000/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Measured on the M4 Pro, in a 1920x1080 window: 60 fps at the title, 44 to 52 fps
- [Queue builder size getters](../../../docs/reference/nvn-objects.md#queue-builder-size-getters): read after `SetDefaults` as it makes its queue.
- [A sync never fenced since its Initialize is signalled](../../../docs/reference/nvn-objects.md#a-sync-never-fenced-since-its-initialize-is-signalled).
- [The provoking vertex is the first](../../../docs/reference/rendering.md#the-provoking-vertex-is-the-first): it sets mode 0.
- [An unbound uniform slot reads the zero bank](../../../docs/reference/rendering.md#an-unbound-uniform-slot-reads-the-zero-bank): compute shader `9d5de89b049f5915_b` declares cb1, which the game leaves unbound, and reads it only for a workgroup count of 2,048 or more. It was a `[RED]` line; now a plain one.
- [Touch gestures recognized from the touch points](../../../docs/reference/services.md#touch-gestures-recognized-from-the-touch-points).
- [SDF definitions in RE Engine PAKs](../../../docs/reference/shaders.md#sdf-definitions-in-re-engine-paks).
- [Integer outputs told from float bits](../../../docs/reference/shaders.md#integer-outputs-told-from-float-bits).
Expand Down Expand Up @@ -68,7 +69,6 @@ Measured on the M4 Pro, in a 1920x1080 window: 60 fps at the title, 44 to 52 fps
## Open

- `[RED] NVN unimplemented function called: nvnCommandBufferSetFloatBlendClamp`: the driver writes method 0x10b0, which NVIDIA's published class header (`clb197.h`) lists only as a reserved software method (read through a summary of the file, not verbatim), so what it selects is not known; the game passes 1.
- `[RED] stage 5 cb1 has no uniform buffer bound: shader 9d5de89b049f5915_b`: the shader reads slot 1 only for a workgroup count of 2,048 or more, so the unbound slot is not read; the line is still printed.
- `[RED] queue 4: an event wait has held it 8.1 s`, once in one run, as the game left the title: the wait sat inside a frame of 11.9 s, 11.6 s of it 29 pipelines compiled at first use (the run skipped the prepare step), so the producer's submission was late, not missing. Not seen in the two runs after it.
- The two shaders under [Shaders](#shaders).
- How long the prepare step's 48,957 pipelines take on a first start has not been measured. A `pso_manifest.tsv` has not been recorded. Combat, later areas, a long session and iOS are not tested; the alias region above has no iPhone answer.
13 changes: 13 additions & 0 deletions dm/gfx/nvn/nvn_cbmiss.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// A uniform (constant) buffer a shader declares that the replay cannot give it (pure; unit test tests/t_cbmiss.c). nvn_mtl.m cbuf_slot binds the all-zero bank in its place in
// every case, so the shader reads 0; what differs is whether that is the game's own state or a binding the host lost:
// CB_UNBOUND the game left the slot unbound (address 0) on this queue: its own state, the host missed no bind (docs/reference/rendering.md, "An unbound uniform slot reads
// the zero bank"). A plain log line for the first 8 shaders, no [RED]
// CB_LOST the game bound an address that is in no memory pool the host knows: the shader reads 0 where the game bound data. [RED], with the shader and the address
// CB_SLOT a slot past the 16 the replay keeps (a reflection the host cannot place). [RED]
#pragma once
#include <stdint.h>

enum { CB_BOUND = 0, CB_UNBOUND = 1, CB_LOST = 2, CB_SLOT = 3 };

static inline int nvn_cb_state(int reg, uint64_t addr, int found) { return reg < 0 || reg >= 16 ? CB_SLOT : !addr ? CB_UNBOUND : found ? CB_BOUND : CB_LOST; }
static inline int nvn_cb_red(int state) { return state == CB_LOST || state == CB_SLOT; }
5 changes: 4 additions & 1 deletion dm/gfx/nvn/nvn_mtl.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "nvn_border.h"
#include "nvn_primitive.h"
#include "nvn_timestamp.h"
#include "nvn_cbmiss.h"
#include "msl_image.h"
#include "dmsh_chain.h" // DMSH_CHAIN: the translation chain version of the B shader library expected by this source tree, one for every game (beside this file, tools/dmsh_chain.py --header)
#include "game_profile.h" // game profile constants (profile id, the profile's switches)
Expand Down Expand Up @@ -11075,7 +11076,9 @@ static uint64_t cbuf_slot(NvShader *sh, int nvn_stage, const nvn_rbind *b, __str
int reg = b->reg; const p_bind_buf *u = reg < 16 ? &S.ubo[nvn_stage][reg] : NULL;
void *mb; uint64_t mo, gpu;
if (!u || !u->addr || !mtl_gpu_addr(u->addr, u->size ? u->size : 1, &mb, &mo, &gpu)) { // reads 0 (the all-zero bank), as an unbound texture slot does, instead of through a null pointer
if (!g_red_once[16]) { char note[400] = ""; if (u && u->addr) dm_gfx_addr_note(u->addr, note, sizeof note); // which shader, and what the address is (unbound, or in no pool)
if (!nvn_cb_red(nvn_cb_state(u ? reg : -1, u ? u->addr : 0, 0))) { // left unbound by the game (address 0): its own state, the host missed no bind (nvn_cbmiss.h)
if (!(sh->logged & 32)) { sh->logged |= 32; static int nlog; if (nlog++ < 8) MLOG("stage %d cb%d of shader %s is not bound (address 0): it reads the zero bank, the game's own state (first 8 shaders)", nvn_stage, reg, sh->key); } }
else if (!g_red_once[16]) { char note[400] = ""; if (u && u->addr) dm_gfx_addr_note(u->addr, note, sizeof note); // a binding the host lost: which shader, and where the address is
RED_ONCE(16, "stage %d cb%d has no uniform buffer bound: shader %s, address %#llx size %#llx (frame %llu)%s", nvn_stage, reg, sh->key, u ? (unsigned long long)u->addr : 0ull, u ? (unsigned long long)u->size : 0ull,
(unsigned long long)atomic_load(&g_cur_frame), note); }
*use = zero_bank(); return zero_bank_gpu(); }
Expand Down
2 changes: 2 additions & 0 deletions dm/gfx/tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ t_hmap: t_hmap.c ../nvn/nvn_hmap.h
clang -O1 -Wall -o $@ t_hmap.c
t_cover: t_cover.c ../nvn/nvn_cover.h
clang -O1 -Wall -o $@ t_cover.c
t_cbmiss: t_cbmiss.c ../nvn/nvn_cbmiss.h
clang -O1 -Wall -o $@ t_cbmiss.c
t_pace: t_pace.c ../nvn/nvn_pace.h
clang -O1 -Wall -o $@ t_pace.c
t_timestamp: t_timestamp.c ../nvn/nvn_timestamp.h
Expand Down
17 changes: 17 additions & 0 deletions dm/gfx/tests/t_cbmiss.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Uniform buffers the replay cannot give a shader (nvn_cbmiss.h): a slot the game left unbound reads the zero bank (its own state, no [RED]); an address in no memory pool, or a slot the
// replay does not keep, is a binding the host lost ([RED]). The host printed "[RED] stage 5 cb1 has no uniform buffer bound" for both, without the shader, on every PRAGMATA boot
#include "../nvn/nvn_cbmiss.h"
#include <stdio.h>

static int fail, checks;
static void check(int ok, const char *what) { checks++; fail += !ok; printf("%-78s %s\n", what, ok ? "✓" : "✗"); }

int main(void) {
check(nvn_cb_state(1, 0, 0) == CB_UNBOUND && !nvn_cb_red(CB_UNBOUND), "cb1 at address 0: the game's own state, no [RED]");
check(nvn_cb_state(1, 0x381f8c000ull, 0) == CB_LOST && nvn_cb_red(CB_LOST), "cb1 at an address in no pool: lost, [RED]");
check(nvn_cb_state(1, 0x381f8c000ull, 1) == CB_BOUND && !nvn_cb_red(CB_BOUND), "cb1 at a pool address: bound");
check(nvn_cb_state(16, 0, 0) == CB_SLOT && nvn_cb_red(CB_SLOT), "cb16 (past the replay's 16 slots): [RED]");
check(nvn_cb_state(-1, 0x1000, 1) == CB_SLOT, "a negative slot: [RED]");
printf("%d/%d checks passed\n", checks - fail, checks);
return fail != 0;
}
2 changes: 1 addition & 1 deletion docs/agents/symptoms.md
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,7 @@ One line per symptom in the [subsystem references](../reference/): what you see
- the `residency set` line shows `passes ended to land them for a read` in the tens of thousands, every one `refused … earlier submission running`. The slower the GPU, the more passes, and each pass makes it slower: a scene stays at a very low frame rate once it falls in. → [Inline updates of a running submission refused uniform shadows](../reference/rendering.md#inline-updates-of-a-running-submission-refused-uniform-shadows)
- a scene that holds 60 fps with the GPU at about 5 ms a frame drops for seconds at a time to 35–40 fps, with the GPU at 20–26 ms, after a slow frame such as a load. The `residency set` line shows tens of thousands of refusals per 300 frames, every one `a report still to land` (before this fix they were counted as `earlier submission running`). → [Counter reports of a running submission refused uniform shadows by page](../reference/rendering.md#counter-reports-of-a-running-submission-refused-uniform-shadows-by-page)
- one scene of a game drops to 1-2 fps and stays there, with the GPU busy 400-600 ms a frame and about ten times the render passes of the scenes before it, many of one draw each on the same target. The `residency set` line shows `passes ended to land them for a read` in the tens of thousands, nearly all `refused … earlier submission running`, with some `unreadable`. A pass dump (`DM_NVN_PASSDUMP`) names `draw_` → `ubd_conflict` as the end of those passes (`E <pass> pass ends in <function>:<line>`). → [A submission past 8,192 queued writes counted as writing everywhere](../reference/rendering.md#a-submission-past-8192-queued-writes-counted-as-writing-everywhere)
- `stage 0 cb5 has no uniform buffer bound` (or another stage and slot, such as `stage 0 cb0`), once per run. → [An unbound uniform slot reads the zero bank](../reference/rendering.md#an-unbound-uniform-slot-reads-the-zero-bank)
- `stage 0 cb5 of shader <key> is not bound (address 0): it reads the zero bank, the game's own state` (or another stage and slot, such as `stage 0 cb0`), a plain line for each of the first 8 shaders. Before, the same case printed `[RED] stage 0 cb5 has no uniform buffer bound`, once per run. → [An unbound uniform slot reads the zero bank](../reference/rendering.md#an-unbound-uniform-slot-reads-the-zero-bank)
- GPU time per frame doubles with many small dispatches (Age of Calamity: GPU 9 → 18 ms a frame on the Mac; the iPhone's 15 → 51 ms, 17 fps). → [Compute dispatches share the copy encoder](../reference/rendering.md#compute-dispatches-share-the-copy-encoder)
- with `DM_NVN_DISPATCH_OWN=1` the host crashes in the Metal driver (`CRASH sig=11 addr=0x98` in `AGX::ComputeContext<…>::prepareForEnqueue(bool)`) at a compute shader's first read through a texture view of another format; by default (dispatches sharing the copy encoder) that read can see the texture's old pixels. Metal validation reports `Command buffer already has an open command encoder` for the first. → [Compute bindings prepare texture copies before the dispatch encoder](../reference/rendering.md#compute-bindings-prepare-texture-copies-before-the-dispatch-encoder)
- dialog boxes with holes the size of their text, and white specks where text faded. → [Fragment-ordering barriers between draws that write images](../reference/rendering.md#fragment-ordering-barriers-between-draws-that-write-images)
Expand Down
8 changes: 4 additions & 4 deletions docs/reference/rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -1360,11 +1360,11 @@ The replay on Metal (`dm/gfx/nvn/nvn_mtl.m`) and the Vulkan and OpenGL layers. E

### An unbound uniform slot reads the zero bank

- **Symptom:** `stage 0 cb5 has no uniform buffer bound` (or another stage and slot, such as `stage 0 cb0`), once per run.
- **Symptom:** `stage 0 cb5 of shader <key> is not bound (address 0): it reads the zero bank, the game's own state` (or another stage and slot, such as `stage 0 cb0`), a plain line for each of the first 8 shaders. Before, the same case printed `[RED] stage 0 cb5 has no uniform buffer bound`, once per run.
- **Cause:** the argument buffer kept a null pointer for the unbound slot. The game draws with nothing bound there; the host misses no bind. What the Switch reads from an unbound slot is not known.
- **Fix:** the slot now reads the all-zero bank, as an unbound texture slot reads the zero texture.
- **Found on:** [Paper Mario: The Thousand-Year Door](../../dm/games/0100ECD018EBE000/README.md) (at the title); [Batman: Arkham Knight](../../dm/games/0100ACD0163D0000/README.md#files-and-shaders) (`stage 0 cb0`, at boot: the first command buffer draws before the game has bound any uniform buffer).
- **Check:** a pass dump of the frame (`DM_NVN_PASSDUMP=<frame>`, frame 0 for the boot) shows the draw (`j` line) and its stage's `K` line with no `B … cb` line, and the frame's `[nvn]` call counts show whether the game called `CommandBufferBindUniformBuffer` at all.
- **Fix:** the slot now reads the all-zero bank, as an unbound texture slot reads the zero texture. A slot at address 0 is the game's own state, so it gets a plain line naming the shader; `[RED] … has no uniform buffer bound` is kept for a binding the host lost: an address in no memory pool, or a slot past the 16 the replay keeps (`nvn_cbmiss.h`, `cbuf_slot`). The binding is the same in every case.
- **Found on:** [Paper Mario: The Thousand-Year Door](../../dm/games/0100ECD018EBE000/README.md) (at the title); [Batman: Arkham Knight](../../dm/games/0100ACD0163D0000/README.md#files-and-shaders) (`stage 0 cb0`, at boot: the first command buffer draws before the game has bound any uniform buffer); [PRAGMATA](../../dm/games/0400C76024710000/README.md) (`stage 5 cb1`, compute shader `9d5de89b049f5915_b`, which reads the slot only for a workgroup count of 2,048 or more).
- **Check:** `make -C dm/gfx/tests GAME=<id> t_cbmiss && dm/gfx/tests/t_cbmiss`. A pass dump of the frame (`DM_NVN_PASSDUMP=<frame>`, frame 0 for the boot) shows the draw (`j` line) and its stage's `K` line with no `B … cb` line, and the frame's `[nvn]` call counts show whether the game called `CommandBufferBindUniformBuffer` at all.

### Compute dispatches share the copy encoder

Expand Down
Loading