Files
NX2navi/runtime/stutter_map.h
chrg6 c040ca6c5f Write the frame-time summary and the stutter map at a fatal error too, the map only when its lock is free.
dispatch.c fatal_end wrote only the profiler's samples, so a run that ended in a FATAL had no "frametime" or
stutter-map lines for its last period: the numbers that would show what the game was doing before it failed.

perf.c perf_summary_flush_try (weak in dispatch.c for builds without perf.c) runs after fs_save_crash_wait: ft_stop,
then the running period's "frametime: at a fatal error" lines and the stutter map. The failing thread may hold any
lock, so stutter_map.c gains stutter_report_as(tag, try_only), which only tries sm_lock and writes nothing when it is
taken (stutter_report keeps the blocking take); the back end's exit lines (gfx_exit_log) and the trace's segments
(seg_close) are left out, as they read state the failure may have left half made. The crash report keeps no summary:
its state may be damaged.

Not reproduced before the change (a missing diagnostic). tests/fatal_exit_check.py now wants the summary at
rt_fatal and checks statically that perf_summary_flush_try only tries the map's lock and leaves the back end out;
tests/stutter_hitch_check.c gets the trylock stand-in it needs. fatal_exit_check, stutter_hitch_check,
stutter_map_check and the compile checks of every runtime file pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 18:43:23 -05:00

32 lines
2.4 KiB
C

// The stutter map (NX2NAVI_STUTTER_MAP=1, stutter_map.c): for a play session, which frames hitched, in which area, what
// the frame waited for, and which pipelines and shaders draws met missing, so the next pipeline list and shader
// preparation can take them in.
#pragma once
#include <stdint.h>
extern int stutter_map_on; // set once at start (stutter_map_init); every hook below returns at once while it is 0
void stutter_map_init(void);
// An area's load (perf.c perf_scene, from romfs.c): hitches from here on are counted in that area, and those within
// SM_LOAD_NS of the load apart from play.
void stutter_area(const char *name);
// The texture pool began or ended making an area's textures (vk_exec_draw.c pool_loading): frames within SM_LOAD_NS of
// it count as an area load's too, in the area of the last stutter_area.
void stutter_load(void);
// A draw met its pipeline (shader 0) or one of its shaders (shader 1) missing and the replay thread spent ns on it
// before it was bound or skipped (vk_exec_draw.c, beside wait_note). vi, fi: the vertex (or mesh) and fragment shaders'
// input ids, NULL when not known (still translating, or no fragment stage).
void stutter_miss(int shader, int mesh, const uint8_t *vi, const uint8_t *fi, uint64_t ns, int skipped);
// A pipeline a draw missed became usable ms after the miss (vk_pipeline.c ready_note): recorded 1 when pipeline-keys.bin
// (or the seed list) named it, fresh 1 when one of its shaders was translated in this session (not prepared).
void stutter_ready(int mesh, const uint8_t *vi, const uint8_t *fi, double ms, int recorded, int fresh, int fast);
// Every present (perf.c ft_frame): d the frame's interval, part the time it went to by cause (names[i], n of them;
// the last one is the part nothing recorded explains), cause the one the frame is charged to.
// STUTTER_CAUSES: the number of causes, perf.c's FT_C_N (perf.c asserts it), which stutter_map.c cause_short and the
// areas' counts are sized by; n is that many.
#define STUTTER_CAUSES 10
void stutter_frame(uint64_t frame, uint64_t now, uint64_t d, const uint64_t *part, const char *const *names, int n,
int cause);
void stutter_report(const char *tag); // the map so far; every 5 minutes of play, at a focus loss in play and at exit
int stutter_report_as(const char *tag, int try_only); // try_only 1: only when its lock is free (0: it was not, nothing written)