mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 17:09:57 -04:00
Add the rule that pairs a skinned draw with last frame's draw of the same mesh, and its check.
A draw is known by its mesh and program and by its number among the frame's draws with that key, counted in the order of the command stream. It is paired with none when the key is new, was drawn another number of times, has a palette of another size, or has its palette at neither of the two addresses that place last had. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
121
runtime/skin_pair.h
Normal file
121
runtime/skin_pair.h
Normal file
@@ -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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
161
tests/skin_pair_check.c
Normal file
161
tests/skin_pair_check.c
Normal file
@@ -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 <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
41
tests/skin_pair_check.py
Normal file
41
tests/skin_pair_check.py
Normal file
@@ -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())
|
||||
Reference in New Issue
Block a user