diff --git a/runtime/skin_pair.h b/runtime/skin_pair.h new file mode 100644 index 00000000..eb53e1da --- /dev/null +++ b/runtime/skin_pair.h @@ -0,0 +1,121 @@ +// Which draw of last frame a skinned draw of this frame continues (vk_skin_motion.inc), for the motion vectors of +// skinned characters. The player's meshes are known by ownership records (motion_owner.c); every other character's +// draws have no such record, so a draw is paired with last frame's by what it binds: +// key the mesh and the program: the vertex buffers its shader reads, the index buffer, the index count, width +// and base vertex, and the vertex and fragment shader (the caller hashes them, skin_mix); +// number the draw's place among this frame's draws with the same key, counted in the order of the command stream +// (order), not the order the replay's threads reached them in: two characters of one model share the key. +// A draw is paired with last frame's draw of the same key and number, and with none when anything is in doubt, as a +// vector from another character's pose is worse than the camera's: +// SKIN_NEW there was no last frame's list, or the key was not in it (the character appeared); +// SKIN_COUNT the key was drawn a different number of times last frame (one appeared or left: the numbers shifted); +// SKIN_SIZE the palette had another size; +// SKIN_ADDRESS the palette is at neither of the two addresses this key and number last had. The game keeps two +// copies of an object's block that take turns, so one object's palette is at last frame's address +// (not written again) or at the other copy's, where it was the frame before that. A third address is +// another object in this place: two characters of one model changed places in the draw order, or one +// left as another came. A new character therefore gets its first vector in its third frame. The two +// addresses are remembered across frames without a list, so the frame after such a gap pairs again. +// Own header so tests/skin_pair_check.c checks it without a GPU. +#pragma once +#include +#include +#include + +enum { SKIN_PAIRED, SKIN_NEW, SKIN_COUNT, SKIN_SIZE, SKIN_ADDRESS, SKIN_PAIR_WHYS }; + +typedef struct { + uint64_t key; // the mesh and program + uint64_t order; // place in the frame's command stream; unique within a frame + uint64_t palette; // address of the palette block + uint32_t bytes; // its size + uint32_t tag; // the caller's (its index of the draw); the entries are sorted + // set by skin_number + uint32_t nth, of; // number among the frame's draws with this key, and how many there are + // set by skin_pair + uint64_t before; // the last palette address of this key and number other than palette; 0 not known + int32_t prev; // index of the paired entry of last frame; -1 none + uint8_t why; // SKIN_PAIRED or why not +} SkinEntry; + +static inline uint64_t skin_mix(uint64_t h, uint64_t v) { + h ^= v + 0x9E3779B97F4A7C15ull + (h << 6) + (h >> 2); + return h * 0xff51afd7ed558ccdull; +} + +static int skin_entry_cmp(const void *a, const void *b) { + const SkinEntry *x = a, *y = b; + if (x->key != y->key) return x->key < y->key ? -1 : 1; + return x->order < y->order ? -1 : x->order > y->order; +} + +// Sorts a frame's entries by key and stream order and numbers the draws of each key. +static inline void skin_number(SkinEntry *e, int n) { + qsort(e, (size_t)n, sizeof *e, skin_entry_cmp); + for (int i = 0; i < n;) { + int j = i; + while (j < n && e[j].key == e[i].key) j++; + for (int k = i; k < j; k++) e[k].nth = (uint32_t)(k - i), e[k].of = (uint32_t)(j - i); + i = j; + } +} + +// The first entry with key in a numbered list, -1 when there is none; its .of entries follow each other. +static inline int skin_find(const SkinEntry *e, int n, uint64_t key) { + int lo = 0, hi = n; + while (lo < hi) { + int mid = lo + (hi - lo) / 2; + if (e[mid].key < key) lo = mid + 1; + else hi = mid; + } + return lo < n && e[lo].key == key ? lo : -1; +} + +// Pairs this frame's numbered entries with those of the last list made (numbered and paired in its own frame). +// consecutive: that list is of the frame right before this one; of an older one nothing is paired, and only the +// addresses are carried on. +static inline void skin_pair(SkinEntry *cur, int ncur, const SkinEntry *last, int nlast, int consecutive) { + for (int i = 0; i < ncur; i++) { + SkinEntry *c = &cur[i]; + c->prev = -1, c->before = 0, c->why = SKIN_NEW; + int p = skin_find(last, nlast, c->key); + if (p < 0) continue; + if (last[p].of != c->of) { + if (consecutive) c->why = SKIN_COUNT; + continue; + } + const SkinEntry *l = &last[p + (int)c->nth]; + if (l->bytes != c->bytes) { + if (consecutive) c->why = SKIN_SIZE; + continue; + } + c->before = c->palette != l->palette ? l->palette : l->before; + if (!consecutive) continue; + if (c->palette != l->palette && c->palette != l->before) { + c->why = SKIN_ADDRESS; + continue; + } + c->prev = p + (int)c->nth, c->why = SKIN_PAIRED; + } +} + +// Whether a palette is last frame's a little moved: every bone's translation within limit units of its last one. The +// matrices are rows of four floats with the translation last in each row (floats 3, 7 and 11); a 4x4 may hold it in +// its fourth row instead (12 to 14), so both places are compared, and the one that is not the translation holds +// constants. For keys drawn more than once a frame: two characters of one model whose draw order alternates frame by +// frame pass the address rule of skin_pair, and stand apart. +static inline int skin_near(const void *now, const void *then, uint32_t bytes, uint32_t stride, float limit) { + static const uint8_t at[6] = {3, 7, 11, 12, 13, 14}; + int places = stride >= 64 ? 6 : 3; + if (stride < 48) return 0; + for (uint32_t o = 0; o + stride <= bytes; o += stride) { + float a[16], b[16]; + memcpy(a, (const uint8_t *)now + o, stride < 64 ? 48 : 64); + memcpy(b, (const uint8_t *)then + o, stride < 64 ? 48 : 64); + for (int k = 0; k < places; k++) { + float d = a[at[k]] - b[at[k]]; + if (!(d <= limit && d >= -limit)) return 0; // a NaN is not near + } + } + return 1; +} diff --git a/tests/skin_pair_check.c b/tests/skin_pair_check.c new file mode 100644 index 00000000..de7564df --- /dev/null +++ b/tests/skin_pair_check.c @@ -0,0 +1,161 @@ +// runtime/skin_pair.h: which draw of last frame a skinned draw continues, for the motion vectors of skinned +// characters. Built and run by tests/skin_pair_check.py; prints each failed check and exits 1 on any. +#include +#include +#include + +#include "skin_pair.h" + +static int fails; +#define CHECK(c, ...) do { if (!(c)) { fails++; printf("FAIL line %d: ", __LINE__); printf(__VA_ARGS__); printf("\n"); } } while (0) + +// A character of the scene: its mesh and program (key), its place in the frame's command stream, and the two copies +// of its palette block, which the game writes in turn. +typedef struct { uint64_t key, order, copy[2]; uint32_t bytes; } Actor; + +static SkinEntry lists[2][16]; +static int counts[2], cur_list; + +// Draws the actors as frame number f (reached by the replay's threads in the order of `reached`, NULL: as given), pairs +// them with the frame before when `consecutive`, and returns the frame's entries; the entry of actor i is found by tag. +static SkinEntry *frame(const Actor *a, int n, int f, const int *reached, int consecutive) { + cur_list ^= 1; + SkinEntry *e = lists[cur_list]; + for (int k = 0; k < n; k++) { + int i = reached ? reached[k] : k; + e[k] = (SkinEntry){.key = a[i].key, .order = a[i].order, .palette = a[i].copy[f & 1], .bytes = a[i].bytes, + .tag = (uint32_t)i}; + } + counts[cur_list] = n; + skin_number(e, n); + skin_pair(e, n, lists[cur_list ^ 1], counts[cur_list ^ 1], consecutive); + return e; +} + +static const SkinEntry *of_actor(const SkinEntry *e, int n, int i) { + for (int k = 0; k < n; k++) + if (e[k].tag == (uint32_t)i) return &e[k]; + return NULL; +} + +// The actor the paired entry of last frame belongs to, -1 when the entry is not paired. +static int paired_actor(const SkinEntry *e) { + return e->why == SKIN_PAIRED ? (int)lists[cur_list ^ 1][e->prev].tag : -1; +} + +int main(void) { + // one character whose palette copies take turns: nothing in its first frame, its place's address is not known + // in the second, paired from the third on + Actor one[1] = {{.key = 7, .order = 10, .copy = {0x1000, 0x1400}, .bytes = 960}}; + const SkinEntry *e = frame(one, 1, 1, NULL, 1); + CHECK(e[0].why == SKIN_NEW && e[0].prev == -1, "a character's first frame is not paired (%d)", e[0].why); + e = frame(one, 1, 2, NULL, 1); + CHECK(e[0].why == SKIN_ADDRESS, "its second frame has no address to go by (%d)", e[0].why); + e = frame(one, 1, 3, NULL, 1); + CHECK(e[0].why == SKIN_PAIRED && paired_actor(&e[0]) == 0, "its third frame is paired (%d)", e[0].why); + e = frame(one, 1, 4, NULL, 1); + CHECK(e[0].why == SKIN_PAIRED, "and stays paired (%d)", e[0].why); + // a frame without a list in between: last frame's list is not of the frame before + e = frame(one, 1, 6, NULL, 0); + CHECK(e[0].why == SKIN_NEW && e[0].prev == -1, "nothing is paired across a gap of a frame (%d)", e[0].why); + // its two addresses are still known, so the frame after the gap is paired + e = frame(one, 1, 7, NULL, 1); + CHECK(e[0].why == SKIN_PAIRED, "the frame after a gap is paired (%d)", e[0].why); + // written every second frame only: the copy it stays on and the one it moves to are both its own + Actor slow[1] = {one[0]}; + int ok = 1; + for (int f = 8; f <= 15; f++) { + slow[0].copy[f & 1] = one[0].copy[(f >> 1) & 1]; + e = frame(slow, 1, f, NULL, 1); + ok &= e[0].why == SKIN_PAIRED; + } + CHECK(ok, "a block written every second frame stays paired"); + + // a block the game does not write again keeps its address: paired in the second frame + Actor still[1] = {{.key = 9, .order = 1, .copy = {0x9000, 0x9000}, .bytes = 480}}; + memset(lists, 0, sizeof lists), counts[0] = counts[1] = 0; + frame(still, 1, 1, NULL, 1); + e = frame(still, 1, 2, NULL, 1); + CHECK(e[0].why == SKIN_PAIRED, "an unmoved block is paired in its second frame (%d)", e[0].why); + + // two characters of one model: the same key twice in a frame. The numbers follow the command stream, whatever + // order the replay's threads reached the draws in + Actor two[2] = {{.key = 5, .order = 20, .copy = {0x2000, 0x2400}, .bytes = 960}, + {.key = 5, .order = 30, .copy = {0x3000, 0x3400}, .bytes = 960}}; + const int straight[2] = {0, 1}, turned[2] = {1, 0}; + memset(lists, 0, sizeof lists), counts[0] = counts[1] = 0; + for (int f = 1; f <= 6; f++) { + e = frame(two, 2, f, f & 1 ? turned : straight, 1); + const SkinEntry *a = of_actor(e, 2, 0), *b = of_actor(e, 2, 1); + CHECK(a->nth == 0 && b->nth == 1 && a->of == 2 && b->of == 2, "frame %d: numbered by place in the stream", f); + if (f >= 3) + CHECK(paired_actor(a) == 0 && paired_actor(b) == 1, "frame %d: each paired with itself (%d, %d)", f, + paired_actor(a), paired_actor(b)); + else + CHECK(a->why != SKIN_PAIRED && b->why != SKIN_PAIRED, "frame %d: not paired yet", f); + } + // they change places in the draw order: each place now holds the other's palette, at an address the place never + // had, for two frames; then both are paired with themselves again + two[0].order = 40; + for (int f = 7; f <= 10; f++) { + e = frame(two, 2, f, NULL, 1); + const SkinEntry *a = of_actor(e, 2, 0), *b = of_actor(e, 2, 1); + if (f <= 8) + CHECK(a->why == SKIN_ADDRESS && b->why == SKIN_ADDRESS, "frame %d: changed places, not paired (%d, %d)", f, + a->why, b->why); + else + CHECK(paired_actor(a) == 0 && paired_actor(b) == 1, "frame %d: paired with themselves again", f); + } + + // a third one of the model appears: every draw of the key is in doubt that frame, and the old two are paired + // again once the count has held for a frame + Actor three[3] = {two[0], two[1], {.key = 5, .order = 25, .copy = {0x5000, 0x5400}, .bytes = 960}}; + e = frame(three, 3, 11, NULL, 1); + for (int i = 0; i < 3; i++) CHECK(of_actor(e, 3, i)->why == SKIN_COUNT, "one appeared: draw %d is not paired", i); + e = frame(three, 3, 12, NULL, 1); + for (int i = 0; i < 3; i++) CHECK(of_actor(e, 3, i)->why == SKIN_ADDRESS, "the frame after: draw %d has no address", i); + e = frame(three, 3, 13, NULL, 1); + e = frame(three, 3, 14, NULL, 1); + for (int i = 0; i < 3; i++) CHECK(paired_actor(of_actor(e, 3, i)) == i, "then draw %d is paired with itself", i); + // one of them leaves + e = frame(three, 2, 15, NULL, 1); + CHECK(e[0].why == SKIN_COUNT && e[1].why == SKIN_COUNT, "one left: the others are not paired (%d, %d)", e[0].why, e[1].why); + // the last one leaves in the frame another one of the model comes: one draw as before, but not the same character + Actor other[1] = {{.key = 5, .order = 20, .copy = {0x7000, 0x7400}, .bytes = 960}}; + memset(lists, 0, sizeof lists), counts[0] = counts[1] = 0; + for (int f = 1; f <= 4; f++) frame(one, 1, f, NULL, 1); + other[0].key = one[0].key; + e = frame(other, 1, 5, NULL, 1); + CHECK(e[0].why == SKIN_ADDRESS, "another character in a character's place is not paired (%d)", e[0].why); + + // the palette changes size + memset(lists, 0, sizeof lists), counts[0] = counts[1] = 0; + for (int f = 1; f <= 4; f++) frame(one, 1, f, NULL, 1); + Actor grown[1] = {one[0]}; + grown[0].bytes = 1920; + e = frame(grown, 1, 5, NULL, 1); + CHECK(e[0].why == SKIN_SIZE && e[0].prev == -1, "a palette of another size is not paired (%d)", e[0].why); + + // a key that was not drawn, and one in an empty list + CHECK(skin_find(e, 1, 1234) == -1 && skin_find(e, 0, 7) == -1, "a key not in the list is not found"); + + // skin_near: every bone's translation within the limit + float now[2][12] = {{1, 0, 0, 10, 0, 1, 0, 20, 0, 0, 1, 30}, {1, 0, 0, 11, 0, 1, 0, 21, 0, 0, 1, 31}}, then[2][12]; + memcpy(then, now, sizeof now); + then[1][7] += 0.5f; + CHECK(skin_near(now, then, sizeof now, 48, 1.0f), "half a unit is near"); + then[1][7] += 1.0f; + CHECK(!skin_near(now, then, sizeof now, 48, 1.0f), "a unit and a half is not"); + then[1][7] = NAN; + CHECK(!skin_near(now, then, sizeof now, 48, 1.0f), "a NaN is not near"); + then[1][7] = now[1][7], then[0][0] = -1; // a turned bone at the same place + CHECK(skin_near(now, then, sizeof now, 48, 1.0f), "a rotation alone does not count"); + float m4[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 6, 7, 1}, n4[16]; + memcpy(n4, m4, sizeof m4); + n4[13] += 3; + CHECK(skin_near(m4, m4, sizeof m4, 64, 1.0f) && !skin_near(m4, n4, sizeof m4, 64, 1.0f), "a 4x4 with its translation in the fourth row"); + CHECK(!skin_near(now, now, sizeof now, 16, 1.0f), "a size that is no matrix is not near"); + + printf(fails ? "skin_pair_check: %d failed\n" : "skin_pair_check: ok\n", fails); + return fails != 0; +} diff --git a/tests/skin_pair_check.py b/tests/skin_pair_check.py new file mode 100644 index 00000000..ed8b0fce --- /dev/null +++ b/tests/skin_pair_check.py @@ -0,0 +1,41 @@ +"""Build and run tests/skin_pair_check.c: runtime/skin_pair.h, which decides the draw of last frame a skinned +character's draw continues (the same key twice in a frame, a character that appears, one that leaves, two that change +places, a palette that changes size). + +No GPU, no game files, any Python. + +Usage: skin_pair_check.py +""" +import os +import shutil +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +MSYS_BIN = Path(r'C:\msys64\ucrt64\bin') + + +def compiler(): + """$CC, else MSYS2 UCRT64's clang, else clang on PATH (as tests/fg_reset_check.py picks it).""" + if os.environ.get('CC'): + return os.environ['CC'] + if os.name == 'nt' and (MSYS_BIN / 'clang.exe').exists(): + return str(MSYS_BIN / 'clang.exe') + return shutil.which('clang') or 'clang' + + +def main(): + work = ROOT / 'build' / 'test' / 'skin_pair' + work.mkdir(parents=True, exist_ok=True) + exe = work / ('skin_pair_check.exe' if os.name == 'nt' else 'skin_pair_check') + env = dict(os.environ) + if os.name == 'nt' and MSYS_BIN.exists(): + env['PATH'] = str(MSYS_BIN) + os.pathsep + env.get('PATH', '') + subprocess.check_call([compiler(), '-std=gnu11', '-O1', '-Wall', '-Werror', '-I', str(ROOT / 'runtime'), + str(ROOT / 'tests' / 'skin_pair_check.c'), '-o', str(exe), '-lm'], env=env) + return subprocess.run([str(exe)], env=env).returncode + + +if __name__ == '__main__': + sys.exit(main())