Files
DizzyMan1337andClaude Opus 5.5 ad3ecd8f3c Per-object motion vectors for DLSS, camera motion in double precision, Ultra Performance and DLSS presets.
Solid scene draws whose position reads an object's own uniform blocks (bones c5, place c7, c6) are drawn a second time
with a vertex shader variant (translator patch 0012) that computes last frame's position from last frame's blocks; the
game writes those into two copies that take turns, and the uniform snapshot keeps both (vk_mvprev.c). The draws are
listed during the replay and recorded through the recorder thread with repeated state skipped. Blocks whose last-frame
copies measured wrong (c9 and c11 of walls and cliffs) keep this frame's; positions without an object block (terrain,
grass) keep the camera's motion, which measured as good.

The camera motion is reprojected in double precision on the CPU: the float inverse made noise on far surfaces and a
still camera that DLSS followed into a wobble. DLSS gets Ultra Performance (a third of the output, also in the F1 menu)
and NVIDIA's recommended preset per mode (K, M for Performance, L for Ultra Performance; TOTK_DLSS_PRESET overrides).

Test switches: TOTK_MV_CHECK measures the motion vectors and DLSS's output against the picture, TOTK_DLAA_TIMES times
DLSS and the motion passes on the GPU. A reactive mask pass for transparents is included but off (TOTK_DLSS_REACTIVE):
its mask covers sky and fog too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:31:58 +08:00

475 lines
26 KiB
C

// TOTK_MV_CHECK=<frames> (test): measures how well DLSS's motion vectors explain the picture, without screenshots or
// judging by eye. Every <frames> DLAA frames, vk_dlaa.c mv_pass copies to host memory the scene light target of that
// frame and of the frame before (before DLSS, so jittered), and the motion image twice: after the camera pass (camera
// only) and after the per-object pass (what DLSS gets). Once the GPU is done, the completion thread compares them:
// warp error: the frame before, sampled bilinearly at p + mv + d, against this frame at p, as a relative luminance
// difference |a - b| / (a + b) averaged over pixels; d = last frame's jitter minus this frame's (the vectors are
// between unjittered positions, the pictures are jittered: content at x is drawn at x + jitter). Lower is better,
// and the correct vectors give the lowest. Over every pixel: zero motion, the camera's and the final vectors, and
// the final ones with d subtracted or left out (the jitter convention). Over the object pixels (final and camera
// differ by more than a quarter pixel): the camera's, the final ones and variants of the object part (final -
// camera) scaled by k = -1, 0.5, 1.5, 2 or with one axis negated. The variant with the lowest error names the
// correction the object vectors need, if any ("best").
// flow: on up to FLOW_MAX object pixels with some texture, the offset whose 5x5 luminance patch of the frame before
// matches best, searched over a window around both the camera and the final vector and refined to a sub-pixel by
// a parabola: the measured motion. The end-point error of the camera and final vectors (+ d) against it, median in
// render pixels, and how often the final vector is the nearer.
// output (DLSS's picture, which the vectors are for): on about OUT_SAMPLES pixels picked independently of the vectors,
// the picture's own motion is measured with a wide search (12 px around the camera vector); where it differs from
// the camera's by over 1.5 px the pixel moves on its own. At each sample, DLSS's output (at the same unjittered
// place) against this frame's input, as a relative luminance difference: on the moving samples it grows where
// DLSS drags old colour along wrong vectors (ghosting, smearing), on the static ones it is the plain difference
// anti-aliasing and upscaling make. Their ratio compares runs with the per-object pass on and off (it does not
// depend on the pass's own vectors) and modes.
// One "mv_check:" line per check. Disocclusion and changing light add error to every candidate alike, so the comparison
// between candidates counts, not the absolute values. Copies and analysis cost a few ms on check frames only.
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cfg.h"
#include "host.h"
#include "nvn_api.h"
#include "vk.h"
#include "vk_exec.h"
#include "vk_dlaa.h"
#define FLOW_MAX 20000
#define OBJ_MIN 0.25f // final and camera vectors differ by more than this (render pixels): an object pixel
#define OUT_SAMPLES 4000
enum { PREV, CUR, MV_CAM, MV_FIN, OUT, NBUF };
static struct {
int period; // 0 off, -1 not read yet
VkBuffer buf[NBUF];
VmaAllocation mem[NBUF];
uint8_t *map[NBUF];
uint64_t cap[NBUF];
uint32_t w, h, ow, oh; // ow, oh: DLSS's output in OUT, 0 none this check
int want_out; // the check frame's output is to be copied (vk_mvcheck_output)
char masks[400]; // the check frame's motion draws (vk_mvobj_frame_masks)
VkFormat cfmt;
uint64_t n; // camera-stage calls (DLAA frames with motion)
uint64_t prev_frame; // gfx frame the PREV colour is of, 0 none
float prev_u[2], u[2], uprev[2];
int check; // this frame is a check frame (camera stage copied MV_CAM)
int busy; // analysis pending: the buffers are not touched
uint64_t done;
} K = {.period = -1};
static int on(void) {
if (K.period < 0) {
const char *v = cfg_get("TOTK_MV_CHECK");
K.period = v && *v ? atoi(v) : 0;
if (K.period < 0) K.period = 0;
if (K.period && K.period < 4) K.period = 4;
if (K.period) rt_log("mv_check: motion vectors measured every %d frames", K.period);
}
return K.period > 0;
}
static int cbytes(VkFormat f) {
return f == VK_FORMAT_B10G11R11_UFLOAT_PACK32 ? 4 : f == VK_FORMAT_R16G16B16A16_SFLOAT ? 8 : 0;
}
// Host buffers of at least the size asked for; a larger size gets new ones and leaves the old (test only).
static int ensure(int i, uint64_t bytes) {
if (K.cap[i] >= bytes) return 1;
VkBufferCreateInfo bi = {.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = bytes,
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT};
VmaAllocationCreateInfo ai = {.usage = VMA_MEMORY_USAGE_AUTO,
.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT};
VmaAllocationInfo info;
if (vmaCreateBuffer(vk_ctx()->vma, &bi, &ai, &K.buf[i], &K.mem[i], &info) != VK_SUCCESS || !info.pMappedData) {
rt_log("mv_check: host buffer of %llu bytes failed: off", (unsigned long long)bytes);
K.period = 0;
return 0;
}
K.map[i] = info.pMappedData, K.cap[i] = bytes;
return 1;
}
static void barrier(VkCommandBuffer cb) {
VkMemoryBarrier2 m = {.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
.srcAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_2_HOST_BIT,
.dstAccessMask = VK_ACCESS_2_MEMORY_WRITE_BIT | VK_ACCESS_2_MEMORY_READ_BIT |
VK_ACCESS_2_HOST_READ_BIT};
VkDependencyInfo di = {.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .memoryBarrierCount = 1, .pMemoryBarriers = &m};
vkCmdPipelineBarrier2(cb, &di);
}
static void copy(VkCommandBuffer cb, int i, VkImage img, uint32_t level, uint32_t layer, uint32_t w, uint32_t h) {
barrier(cb);
VkBufferImageCopy r = {.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1}, .imageExtent = {w, h, 1}};
vkCmdCopyImageToBuffer(cb, img, VK_IMAGE_LAYOUT_GENERAL, K.buf[i], 1, &r);
barrier(cb);
}
// ---- decoding
static float half(uint16_t v) {
uint32_t s = (uint32_t)(v >> 15) << 31, e = v >> 10 & 31, m = v & 1023, f;
if (!e) return (s ? -1.0f : 1.0f) * ldexpf((float)m, -24);
f = e == 31 ? s | 0x7f800000u | m << 13 : s | (e + 112) << 23 | m << 13;
float r;
memcpy(&r, &f, 4);
return r;
}
static float ufloat(uint32_t v, int mbits) { // unsigned 5-bit exponent mini-float
uint32_t e = v >> mbits, m = v & ((1u << mbits) - 1);
if (e == 31) return 65504.0f;
return e ? ldexpf(1.0f + (float)m / (float)(1u << mbits), (int)e - 15) : ldexpf((float)m / (float)(1u << mbits), -14);
}
static void luminance(const uint8_t *src, VkFormat f, uint32_t n, float *out) {
for (uint32_t i = 0; i < n; i++) {
float r, g, b;
if (f == VK_FORMAT_B10G11R11_UFLOAT_PACK32) {
uint32_t v;
memcpy(&v, src + 4 * (size_t)i, 4);
r = ufloat(v & 0x7ff, 6), g = ufloat(v >> 11 & 0x7ff, 6), b = ufloat(v >> 22, 5);
} else {
uint16_t c[4];
memcpy(c, src + 8 * (size_t)i, 8);
r = half(c[0]), g = half(c[1]), b = half(c[2]);
}
float y = 0.2126f * r + 0.7152f * g + 0.0722f * b;
out[i] = isfinite(y) && y > 0 ? y : 0;
}
}
// ---- analysis
typedef struct { const float *cur, *prev; uint32_t w, h; } Pic;
static int sample(const Pic *p, float x, float y, float *out) { // bilinear, pixel index coordinates
if (!(x >= 0 && y >= 0 && x <= (float)(p->w - 1) && y <= (float)(p->h - 1))) return 0;
uint32_t x0 = (uint32_t)x, y0 = (uint32_t)y, x1 = x0 + 1 < p->w ? x0 + 1 : x0, y1 = y0 + 1 < p->h ? y0 + 1 : y0;
float fx = x - (float)x0, fy = y - (float)y0;
const float *a = p->prev + (size_t)y0 * p->w, *b = p->prev + (size_t)y1 * p->w;
*out = (a[x0] * (1 - fx) + a[x1] * fx) * (1 - fy) + (b[x0] * (1 - fx) + b[x1] * fx) * fy;
return 1;
}
typedef struct { double sum; uint64_t n; } Acc;
static void acc(Acc *a, const Pic *p, uint32_t x, uint32_t y, float mx, float my) {
float b;
if (!sample(p, (float)x + mx, (float)y + my, &b)) return;
float c = p->cur[(size_t)y * p->w + x];
a->sum += fabsf(c - b) / (c + b + 1e-4f), a->n++;
}
static double mean(const Acc *a) { return a->n ? a->sum / (double)a->n : NAN; }
static float sad5(const Pic *p, uint32_t x, uint32_t y, int dx, int dy) {
float s = 0;
for (int j = -2; j <= 2; j++)
for (int i = -2; i <= 2; i++) {
int px = (int)x + i + dx, py = (int)y + j + dy;
if (px < 0 || py < 0 || px >= (int)p->w || py >= (int)p->h) return INFINITY;
float c = p->cur[(size_t)(y + j) * p->w + x + i], b = p->prev[(size_t)py * p->w + px];
s += fabsf(c - b) / (c + b + 1e-4f);
}
return s;
}
static float parabola(float l, float c, float r) { // offset of the minimum of three samples at -1, 0, 1
float den = l - 2 * c + r;
if (!(den > 1e-9f) || !isfinite(l) || !isfinite(r)) return 0;
float o = 0.5f * (l - r) / den;
return o < -0.5f ? -0.5f : o > 0.5f ? 0.5f : o; // a flat curve says nothing past the neighbours
}
static int by_float(const void *a, const void *b) {
float x = *(const float *)a, y = *(const float *)b;
return x < y ? -1 : x > y;
}
// The object pixels' measured motion (sub-pixel), searched over the box around both vectors with a margin of 3;
// 0 when the patch is flat or the match is not clearly better than the box's average.
static int flow(const Pic *p, uint32_t x, uint32_t y, float ax, float ay, float bx, float by, float out[2]) {
float mu = 0, var = 0;
for (int j = -2; j <= 2; j++)
for (int i = -2; i <= 2; i++) mu += p->cur[(size_t)(y + j) * p->w + x + i];
mu /= 25;
for (int j = -2; j <= 2; j++)
for (int i = -2; i <= 2; i++) {
float d = p->cur[(size_t)(y + j) * p->w + x + i] - mu;
var += d * d;
}
if (mu <= 0 || sqrtf(var / 25) < 0.05f * mu) return 0;
int x0 = (int)floorf(fminf(ax, bx)) - 3, x1 = (int)ceilf(fmaxf(ax, bx)) + 3;
int y0 = (int)floorf(fminf(ay, by)) - 3, y1 = (int)ceilf(fmaxf(ay, by)) + 3;
if (x1 - x0 > 40 || y1 - y0 > 40) return 0;
float best = INFINITY, total = 0;
int bdx = 0, bdy = 0, n = 0;
for (int dy = y0; dy <= y1; dy++)
for (int dx = x0; dx <= x1; dx++) {
float s = sad5(p, x, y, dx, dy);
if (!isfinite(s)) continue;
total += s, n++;
if (s < best) best = s, bdx = dx, bdy = dy;
}
if (!n || !isfinite(best) || best > 0.5f * total / (float)n) return 0;
out[0] = (float)bdx + parabola(sad5(p, x, y, bdx - 1, bdy), best, sad5(p, x, y, bdx + 1, bdy));
out[1] = (float)bdy + parabola(sad5(p, x, y, bdx, bdy - 1), best, sad5(p, x, y, bdx, bdy + 1));
return 1;
}
// Totals over every check (logged every TOTAL_EVERY checks): end-point error histograms in HBIN px bins of the object
// samples (camera, final) and of the static samples (the measurement's own noise floor), and the object warp errors.
#define HBIN 0.05f
#define HN 640
#define TOTAL_EVERY 20
#define KN 120 // histogram of per-sample scale ratios, -2 to 4 in steps of 0.05
enum { H_CAM, H_FIN, H_STATIC, NH };
static struct { uint64_t h[NH][HN], n[NH], checks, wrong, kh[KN], kn; double obj[2], objn, knum, kden, out[2], outn[2]; uint64_t cal[3]; } T; // cal: checks where k=0.5, k=1 fit the static pixels best; checks
static void hist_add(int k, float e) {
int b = e >= 0 && e < HBIN * HN ? (int)(e / HBIN) : HN - 1; // NaN and huge errors in the last bin
T.h[k][b < HN ? b : HN - 1]++, T.n[k]++;
}
static float hist_q(int k, double q) { // quantile q of histogram k, bin centre
uint64_t want = (uint64_t)(q * (double)T.n[k]), s = 0;
for (int b = 0; b < HN; b++)
if ((s += T.h[k][b]) > want) return ((float)b + 0.5f) * HBIN;
return NAN;
}
static float kmedian(void) {
uint64_t s = 0;
for (int b = 0; b < KN; b++)
if ((s += T.kh[b]) * 2 > T.kn) return -2 + ((float)b + 0.5f) * 0.05f;
return NAN;
}
static uint32_t mix(uint32_t x, uint32_t y) { // a fixed pseudo-random pick of pixels, spread over the picture
uint32_t v = x * 0x9E3779B1u ^ y * 0x85EBCA77u;
v ^= v >> 15, v *= 0x2C1B3C6Du, v ^= v >> 12;
return v;
}
static void analyse(void *arg) {
(void)arg;
VmaAllocator vma = vk_ctx()->vma;
for (int i = 0; i < NBUF; i++) vmaInvalidateAllocation(vma, K.mem[i], 0, VK_WHOLE_SIZE);
uint32_t w = K.w, h = K.h, n = w * h;
float *cur = malloc(sizeof(float) * n), *prev = malloc(sizeof(float) * n), *e_cam = NULL, *e_fin = NULL,
*e_st = NULL;
if (!cur || !prev) goto out;
luminance(K.map[CUR], K.cfmt, n, cur);
luminance(K.map[PREV], K.cfmt, n, prev);
Pic p = {cur, prev, w, h};
const uint16_t *A = (const uint16_t *)K.map[MV_CAM], *B = (const uint16_t *)K.map[MV_FIN];
float dx = K.uprev[0] - K.u[0], dy = K.uprev[1] - K.u[1];
enum { ZERO, CAM, FIN, FIN_NEG_D, FIN_NO_D, NALL };
enum { O_CAM, O_FIN, O_KM1, O_K05, O_K15, O_K2, O_NX, O_NY, NOBJ };
static const char *const oname[NOBJ] = {"camera", "final", "k=-1", "k=0.5", "k=1.5", "k=2", "-x", "-y"};
Acc all[NALL] = {0}, obj[NOBJ] = {0}, cal[3] = {0};
// the object pixels first, for the sampling rates: about FLOW_MAX object and FLOW_MAX / 4 static samples
uint64_t nobj = 0, huge = 0; // huge: camera vectors over 256 px or not finite
double cam_len = 0;
for (uint32_t y = 2; y + 2 < h; y++)
for (uint32_t x = 2; x + 2 < w; x++) {
size_t i = (size_t)y * w + x;
float ax = half(A[2 * i]), ay = half(A[2 * i + 1]), bx = half(B[2 * i]), by = half(B[2 * i + 1]);
if (isfinite(ax) && isfinite(ay)) cam_len += hypotf(ax, ay);
huge += !isfinite(ax) || !isfinite(ay) || hypotf(ax, ay) > 256;
nobj += fabsf(bx - ax) > OBJ_MIN || fabsf(by - ay) > OBJ_MIN;
}
uint32_t rate_obj = nobj > FLOW_MAX ? (uint32_t)(0xffffffffull * FLOW_MAX / nobj) : 0xffffffffu;
uint64_t nst = (uint64_t)n - nobj;
uint32_t rate_st = nst > FLOW_MAX / 4 ? (uint32_t)(0xffffffffull * (FLOW_MAX / 4) / nst) : 0xffffffffu;
e_cam = malloc(sizeof(float) * FLOW_MAX * 2), e_fin = malloc(sizeof(float) * FLOW_MAX * 2),
e_st = malloc(sizeof(float) * FLOW_MAX);
if (!e_cam || !e_fin || !e_st) goto out;
int nflow = 0, nearer = 0, tried = 0, nst_f = 0;
double knum = 0, kden = 0;
// object samples the final vector misses by more than 1.5 px where the camera's fits better: where and how
struct { int n; uint32_t x0, y0, x1, y1; double ox, oy, mx, my; } bad = {0, UINT32_MAX, UINT32_MAX, 0, 0, 0, 0, 0, 0};
for (uint32_t y = 2; y + 2 < h; y++)
for (uint32_t x = 2; x + 2 < w; x++) {
size_t i = (size_t)y * w + x;
float ax = half(A[2 * i]), ay = half(A[2 * i + 1]), bx = half(B[2 * i]), by = half(B[2 * i + 1]);
if (!isfinite(ax) || !isfinite(ay) || !isfinite(bx) || !isfinite(by)) continue;
acc(&all[ZERO], &p, x, y, dx, dy);
acc(&all[CAM], &p, x, y, ax + dx, ay + dy);
acc(&all[FIN], &p, x, y, bx + dx, by + dy);
acc(&all[FIN_NEG_D], &p, x, y, bx - dx, by - dy);
acc(&all[FIN_NO_D], &p, x, y, bx, by);
float ox = bx - ax, oy = by - ay, f[2];
if (fabsf(ox) <= OBJ_MIN && fabsf(oy) <= OBJ_MIN) {
// calibration: static pixels with a small camera vector (known right), at k = 0.5, 1, 1.5 of it
float al = hypotf(ax, ay);
if (al > 0.25f && al < 2) {
acc(&cal[0], &p, x, y, 0.5f * ax + dx, 0.5f * ay + dy), acc(&cal[1], &p, x, y, ax + dx, ay + dy);
acc(&cal[2], &p, x, y, 1.5f * ax + dx, 1.5f * ay + dy);
}
if (mix(x, y) > rate_st || nst_f >= FLOW_MAX) continue;
if (flow(&p, x, y, ax + dx, ay + dy, ax + dx, ay + dy, f)) e_st[nst_f++] = hypotf(ax + dx - f[0], ay + dy - f[1]);
continue;
}
acc(&obj[O_CAM], &p, x, y, ax + dx, ay + dy);
acc(&obj[O_FIN], &p, x, y, bx + dx, by + dy);
acc(&obj[O_KM1], &p, x, y, ax - ox + dx, ay - oy + dy);
acc(&obj[O_K05], &p, x, y, ax + 0.5f * ox + dx, ay + 0.5f * oy + dy);
acc(&obj[O_K15], &p, x, y, ax + 1.5f * ox + dx, ay + 1.5f * oy + dy);
acc(&obj[O_K2], &p, x, y, ax + 2 * ox + dx, ay + 2 * oy + dy);
acc(&obj[O_NX], &p, x, y, ax - ox + dx, by + dy);
acc(&obj[O_NY], &p, x, y, bx + dx, ay - oy + dy);
if (mix(x, y) > rate_obj || nflow >= 2 * FLOW_MAX) continue;
tried++;
if (!flow(&p, x, y, ax + dx, ay + dy, bx + dx, by + dy, f)) continue;
float ec = hypotf(ax + dx - f[0], ay + dy - f[1]), ef = hypotf(bx + dx - f[0], by + dy - f[1]);
e_cam[nflow] = ec, e_fin[nflow] = ef, nflow++;
nearer += ef < ec;
if (fabsf(ox) > 0.5f || fabsf(oy) > 0.5f) { // the scale of the object part the measured motion shows
float mx = f[0] - ax - dx, my = f[1] - ay - dy, oo = ox * ox + oy * oy, kr = (mx * ox + my * oy) / oo;
knum += mx * ox + my * oy, kden += oo;
int kb = (int)floorf((kr + 2) / 0.05f);
T.kh[kb < 0 ? 0 : kb >= KN ? KN - 1 : kb]++, T.kn++;
}
if (ef > 1.5f && ef > ec) {
bad.n++, bad.ox += ox, bad.oy += oy, bad.mx += f[0] - ax - dx, bad.my += f[1] - ay - dy;
if (x < bad.x0) bad.x0 = x;
if (y < bad.y0) bad.y0 = y;
if (x > bad.x1) bad.x1 = x;
if (y > bad.y1) bad.y1 = y;
}
}
int best = 0;
for (int k = 1; k < NOBJ; k++)
if (obj[k].n && (!obj[best].n || mean(&obj[k]) < mean(&obj[best]))) best = k;
char line[512];
int o = snprintf(line, sizeof line, "object pixels %.1f%% (%llu):", 100.0 * (double)nobj / (double)n,
(unsigned long long)nobj);
for (int k = 0; k < NOBJ && o < (int)sizeof line; k++) o += snprintf(line + o, sizeof line - o, " %s %.4f", oname[k], mean(&obj[k]));
if (o < (int)sizeof line) snprintf(line + o, sizeof line - o, "; best %s", nobj ? oname[best] : "-");
// DLSS's output against this frame's input, on samples moving on their own and static ones (the picture's motion)
char outl[200] = "";
if (K.ow && K.oh) {
float *ol = malloc(sizeof(float) * (size_t)K.ow * K.oh);
if (ol) {
luminance(K.map[OUT], VK_FORMAT_R16G16B16A16_SFLOAT, K.ow * K.oh, ol);
Pic op = {NULL, ol, K.ow, K.oh}; // sample() reads prev
float sx = (float)K.ow / (float)w, sy = (float)K.oh / (float)h;
uint32_t rate = (uint32_t)(0xffffffffull * OUT_SAMPLES / n);
Acc mov = {0}, sta = {0};
for (uint32_t y = 2; y + 2 < h; y++)
for (uint32_t x = 2; x + 2 < w; x++) {
if ((mix(x, y) ^ 0x5bd1e995u) * 0x2C1B3C6Du > rate) continue;
size_t i = (size_t)y * w + x;
float ax = half(A[2 * i]) + dx, ay = half(A[2 * i + 1]) + dy, f[2], ov;
if (!isfinite(ax) || !isfinite(ay) || !flow(&p, x, y, ax - 12, ay - 12, ax + 12, ay + 12, f)) continue;
if (!sample(&op, ((float)x + 0.5f - K.u[0]) * sx - 0.5f, ((float)y + 0.5f - K.u[1]) * sy - 0.5f, &ov))
continue;
float c = cur[i], e = fabsf(ov - c) / (ov + c + 1e-4f);
Acc *a = hypotf(f[0] - ax, f[1] - ay) > 1.5f ? &mov : &sta;
a->sum += e, a->n++;
}
snprintf(outl, sizeof outl, "; output against input: moving on their own %.4f (%llu), static %.4f (%llu), "
"ratio %.3f", mean(&mov), (unsigned long long)mov.n, mean(&sta), (unsigned long long)sta.n,
mean(&mov) / mean(&sta));
if (mov.n) T.out[0] += mov.sum, T.outn[0] += (double)mov.n;
if (sta.n) T.out[1] += sta.sum, T.outn[1] += (double)sta.n;
free(ol);
}
}
float mc = NAN, mf = NAN, ms = NAN;
for (int k = 0; k < nflow; k++) hist_add(H_CAM, e_cam[k]), hist_add(H_FIN, e_fin[k]);
for (int k = 0; k < nst_f; k++) hist_add(H_STATIC, e_st[k]);
if (nflow) {
qsort(e_cam, (size_t)nflow, sizeof *e_cam, by_float), qsort(e_fin, (size_t)nflow, sizeof *e_fin, by_float);
mc = e_cam[nflow / 2], mf = e_fin[nflow / 2];
}
if (nst_f) qsort(e_st, (size_t)nst_f, sizeof *e_st, by_float), ms = e_st[nst_f / 2];
char badl[200] = "";
if (bad.n)
snprintf(badl, sizeof badl, "; %d missed (over 1.5 px, camera nearer) in x %u-%u y %u-%u, object part (%.2f %.2f), "
"measured (%.2f %.2f)", bad.n, bad.x0, bad.x1, bad.y0, bad.y1, bad.ox / bad.n, bad.oy / bad.n,
bad.mx / bad.n, bad.my / bad.n);
rt_log("mv_check: #%llu %ux%u, jitter (%.2f %.2f) after (%.2f %.2f), camera motion %.2f px; warp error, every pixel: "
"zero %.4f camera %.4f final %.4f (jitter subtracted %.4f, left out %.4f); camera vectors over 256 px %llu; %s; "
"flow on %d of %d object "
"samples: median error camera %.2f px, final %.2f px, final nearer %.0f%%; static %.2f px (%d); scale %.2f; calibration, static pixels under 2 px of camera motion (%llu): k=0.5 %.4f k=1 %.4f "
"k=1.5 %.4f%s%s; motion draws (position banks/changed banks:draws)%s",
(unsigned long long)++K.done, w, h, K.u[0], K.u[1], K.uprev[0], K.uprev[1], cam_len / (double)n,
mean(&all[ZERO]), mean(&all[CAM]), mean(&all[FIN]), mean(&all[FIN_NEG_D]), mean(&all[FIN_NO_D]), (unsigned long long)huge, line,
nflow, tried, mc, mf, nflow ? 100.0 * nearer / nflow : 0.0, ms, nst_f, kden > 0 ? knum / kden : NAN, (unsigned long long)cal[1].n, mean(&cal[0]), mean(&cal[1]), mean(&cal[2]), badl, outl, K.masks);
if (cal[1].n > 1000) T.cal[0] += mean(&cal[0]) < mean(&cal[1]) && mean(&cal[0]) < mean(&cal[2]), T.cal[1] += mean(&cal[1]) <= mean(&cal[0]) && mean(&cal[1]) <= mean(&cal[2]), T.cal[2]++;
T.knum += knum, T.kden += kden;
T.checks++;
if (obj[O_FIN].n) {
T.obj[0] += obj[O_CAM].sum, T.obj[1] += obj[O_FIN].sum, T.objn += (double)obj[O_FIN].n;
T.wrong += nobj * 200 > n && best != O_FIN; // over 0.5 % of the picture and the vectors are not the best fit
}
if (T.checks % TOTAL_EVERY == 0)
rt_log("mv_check total: %llu checks, %llu with over 0.5%% object pixels fit better by a variant; object warp error "
"camera %.4f final %.4f; end-point error (median / 90%% / 99%%) object samples camera %.2f / %.2f / %.2f "
"px, final %.2f / %.2f / %.2f px (%llu); static samples %.2f / %.2f / %.2f px (%llu); object scale %.3f (median %.2f of %llu); calibration: k=0.5 best in %llu, k=1 in %llu of %llu checks; "
"output against input: moving on their own %.4f (%.0f), static %.4f (%.0f), ratio %.3f",
(unsigned long long)T.checks, (unsigned long long)T.wrong, T.objn ? T.obj[0] / T.objn : NAN,
T.objn ? T.obj[1] / T.objn : NAN, hist_q(H_CAM, 0.5), hist_q(H_CAM, 0.9), hist_q(H_CAM, 0.99),
hist_q(H_FIN, 0.5), hist_q(H_FIN, 0.9), hist_q(H_FIN, 0.99), (unsigned long long)T.n[H_FIN],
hist_q(H_STATIC, 0.5), hist_q(H_STATIC, 0.9), hist_q(H_STATIC, 0.99), (unsigned long long)T.n[H_STATIC],
T.kden > 0 ? T.knum / T.kden : NAN, kmedian(), (unsigned long long)T.kn, (unsigned long long)T.cal[0], (unsigned long long)T.cal[1],
(unsigned long long)T.cal[2], T.outn[0] ? T.out[0] / T.outn[0] : NAN, T.outn[0],
T.outn[1] ? T.out[1] / T.outn[1] : NAN, T.outn[1],
T.outn[0] && T.outn[1] ? (T.out[0] / T.outn[0]) / (T.out[1] / T.outn[1]) : NAN);
out:
free(cur), free(prev), free(e_cam), free(e_fin), free(e_st);
__atomic_store_n(&K.busy, 0, __ATOMIC_RELEASE);
}
// ---- capture (vk_dlaa.c mv_pass)
void vk_mvcheck_camera(VkCommandBuffer cb, VkImage mv, uint32_t w, uint32_t h, float jx, float jy) {
K.check = 0;
if (!on()) return;
K.n++;
uint64_t frame = __atomic_load_n(&gfx_counters.frames, __ATOMIC_RELAXED);
if (K.n % (uint64_t)K.period || __atomic_load_n(&K.busy, __ATOMIC_ACQUIRE) || K.prev_frame + 1 != frame ||
K.w != w || K.h != h)
return;
if (!ensure(MV_CAM, (uint64_t)w * h * 4) || !ensure(MV_FIN, (uint64_t)w * h * 4)) return;
copy(cb, MV_CAM, mv, 0, 0, w, h);
K.check = 1, K.u[0] = jx, K.u[1] = jy, K.uprev[0] = K.prev_u[0], K.uprev[1] = K.prev_u[1];
}
void vk_mvcheck_final(VkCommandBuffer cb, uint64_t fence, VkImage mv, VkImage cimg, VkFormat cfmt, uint32_t level,
uint32_t layer, uint32_t w, uint32_t h, float jx, float jy) {
if (!on()) return;
uint64_t frame = __atomic_load_n(&gfx_counters.frames, __ATOMIC_RELAXED);
int b = cbytes(cfmt);
int want_prev = (K.n + 1) % (uint64_t)K.period == 0 && !__atomic_load_n(&K.busy, __ATOMIC_ACQUIRE);
if (!b) return (void)(K.check = 0, K.prev_frame = 0);
if (K.check) {
if (!ensure(CUR, (uint64_t)w * h * (uint64_t)b)) return;
copy(cb, MV_FIN, mv, 0, 0, w, h);
copy(cb, CUR, cimg, level, layer, w, h);
K.check = 0, K.prev_frame = 0, K.ow = K.oh = 0, K.want_out = 1;
snprintf(K.masks, sizeof K.masks, "%s", vk_mvobj_frame_masks());
__atomic_store_n(&K.busy, 1, __ATOMIC_RELEASE);
vk_completion_add(fence, analyse, NULL);
return;
}
if (!want_prev) return;
if (!ensure(PREV, (uint64_t)w * h * (uint64_t)b)) return;
copy(cb, PREV, cimg, level, layer, w, h);
K.prev_frame = frame, K.prev_u[0] = jx, K.prev_u[1] = jy, K.w = w, K.h = h, K.cfmt = cfmt;
}
// After DLSS ran on a check frame (vk_dlaa.c evaluate): its output (RGBA16F, ow x oh) for the analysis, which runs once
// the submission is done.
void vk_mvcheck_output(VkCommandBuffer cb, VkImage out, uint32_t ow, uint32_t oh) {
if (!K.want_out) return;
K.want_out = 0;
if (!ensure(OUT, (uint64_t)ow * oh * 8)) return;
copy(cb, OUT, out, 0, 0, ow, oh);
K.ow = ow, K.oh = oh;
}