Files
NX2navi/runtime/flight.h
chrg6 202fd47ceb Let the flight recorder dump on its own when one frame flashes.
With flight = 1 and its thumbnails kept, the recorder now also writes a dump when a frame's picture differs strongly from the frame before and the frame after while those two agree, which is what a one-frame flicker looks like. Each thumbnail is reduced to 16x9 cells of mean brightness where the present thread already sees it complete, and a frame is a flash when at least three cells are more than 24 of 255 brighter or darker than in both neighbours, the neighbours being alike in those cells, around them and over the picture as a whole. A camera cut, a fade, a pan and shapes crossing the picture are no flash, and a frame beside one without a thumbnail is not judged. The dump is the one F8 writes, named "flash" with the frame's number in its header line, at most one every 30 seconds. flight_flash = 0 turns it off; flight_flash_diff and flight_flash_cells set the two thresholds.

On the thumbnails of earlier dumps the defaults flag the frames whose scene was white once and nothing in the runs without that fault. tests/flight_flash_check.py checks the test itself on drawn pictures.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 15:16:30 -05:00

61 lines
4.7 KiB
C

// NX2NAVI_FLIGHT=1 flight recorder (flight.c): the last frames' timing and event counts plus a thumbnail each, written to
// disk on F8, after a slow frame or after a one-frame flash, so a glitch or stutter seen in play can be read after the
// fact.
#pragma once
#include <stdint.h>
#define FLIGHT_THUMB_W 640
#define FLIGHT_THUMB_H 360
extern int flight_on;
extern int vk_dump_white; // vk_dump.c, NX2NAVI_DUMP_WHITE // NX2NAVI_FLIGHT=1, set once by flight_init before the first present
void flight_init(void); // prof_start calls it
// perf_present, once per present on the render thread: the interval since the previous present, the render thread's
// blocked time per PERF_* kind in this frame, and the running PERF_CNT_* totals.
void flight_frame(uint64_t frame, uint64_t now_ns, uint64_t interval_ns, const uint64_t blocked_ns[],
const volatile int64_t counts[]);
// Texture events of the recorded frames (vk_texture.c), written with each dump as events.tsv: a texture made (with how
// its content started), uploaded, uploaded again, released by the game, taken back after a release, or destroyed. Each
// carries the frame whose counts in frames.tsv it is part of. Any thread: the ring takes no lock. A no-op until
// flight_init turned the recorder on.
enum { FLIGHT_TEX_NEW, FLIGHT_TEX_UP, FLIGHT_TEX_REUP, FLIGHT_TEX_RELEASE, FLIGHT_TEX_TAKEBACK, FLIGHT_TEX_DROP,
FLIGHT_TEX_BYTES, // after UP and REUP: what the upload read (detail: a hash of 64 probes of its storage; us:
// how many of the probes were all zero), so a second upload tells new bytes from the same
FLIGHT_TEX_REG, // a slot registered with a CPU-written texture (us: the slot; detail: what it named before:
// 0 nothing, 1 another descriptor at this storage, 2 other storage, 3 a finalized texture)
FLIGHT_TEX_BLANK, // a shader sampled a slot that has no image of its own and follows none: its stand-in was
// on screen (us: the slot; detail: bit 0 the slot is finalized, bit 1 it is held out)
FLIGHT_TEX_TOBLANK, // the pool's entry of a slot went from a texture's view to the stand-in (us: the slot;
// detail: vk_texture_slot_why_locked's bits, what the slot's own texture was then)
FLIGHT_TEX_BLANKSUB,// a slot that lost its picture during a submission still had none when the next one began:
// the submission's draws read its stand-in (us and detail as TOBLANK)
FLIGHT_TEX_DIRECT, // a draw's own texture binding (not the pool's) named a slot and got the stand-in: that draw
// samples it whatever the slot resolves to later (us and detail as TOBLANK; once a frame)
FLIGHT_TEX_UNREG, // a shader sampled a pool slot that is not registered when its marks are read (us: the slot)
FLIGHT_TEX_N };
// The texture's flags: a depth or stencil image, mirrored (the CPU writes its storage), bound into pool memory, an ASTC
// texture, written by the GPU (VkTexImage.gpu_owned).
enum { FLIGHT_TF_DEPTH = 1, FLIGHT_TF_STENCIL = 2, FLIGHT_TF_MIRRORED = 4, FLIGHT_TF_BOUND = 8, FLIGHT_TF_ASTC = 16,
FLIGHT_TF_GPU = 32 };
// detail: for NEW how the content started (vk_texture_start_name), for UP and REUP the WriteTexels calls since the last
// upload, for DROP why (vk_texture_drop_name). us: an upload's time, else 0.
void flight_tex(int kind, uint64_t addr, uint32_t format, uint32_t w, uint32_t h, uint32_t layers, uint32_t flags,
uint32_t detail, uint32_t us);
const char *vk_texture_start_name(int start); // vk_texture.c: the names of Tex.og_start and of drop_tex's reasons
const char *vk_texture_drop_name(int why);
// gfx_present.c: the present thread blits every presented frame into a host-visible ring of n thumbnails.
void vk_thumbs_enable(int n); // before the first present; n slots of FLIGHT_THUMB_W x FLIGHT_THUMB_H
void vk_thumbs_frame(uint64_t frame); // the present thread names the frame of the vk_present_image call that follows
// Copies frame's thumbnail as RGBA when the ring still holds it complete; 0 when it was never written, is still in
// flight or was overwritten. Takes the present lock for one slot copy (about 20 us); any thread.
int vk_thumbs_read(uint64_t frame, uint8_t *rgba);
// The flash trigger (flight_flash.h): while flight_flash_on (set once by flight_init), gfx_present.c hands in the cell
// means (flash_cells, FLASH_CELLS bytes) of each thumbnail when it completes, on the present thread, in frame order.
// flight_cells judges the frame before and leaves a flash for flight_frame to dump.
extern int flight_flash_on;
void flight_cells(uint64_t frame, const uint8_t *cells);