Page 2 now samples its sources once a second, in NotePresent, while it
shows; nothing of it runs while the overlay is hidden or on page 1.
- Busy share per queue worker, the top 5 phases in ms per frame and queue
0's waits (memory, lock, GPU) come from the trace's per-thread totals.
Showing page 2 turns recording on with buffering off, so a timed scope
only adds to its thread's totals; leaving it turns recording off again.
Toggle switches it under a mutex, and ANYPS5_OVERLAY=2 switches it on at
start. An APS5_TRACE_* run keeps its own recording untouched, and page 2
then shows these lines only while that trace records. A driver thread
names itself when it starts (BindThread without APS5_TRACE_* stores the
name only) and binds on its first timed scope while page 2 shows;
unnamed threads are not bound.
- Render passes per frame: the draw path counts a begun pass in the trace
totals while page 2's totals are on (PerformanceTrace::Count).
- Flips/s and the draw cache's hit rate come from the driver through a
source it registers at start (the draw cache's lock is only tried); the
pipeline store's hit rate from PipelineStoreTotals; the compile backlog
from the shader preparation and library prebuild pools (tried locks).
- Dropped frames per second are the presents that found no free swapchain
image; repeated ones are presents of the display buffer shown just before.
The device passes the buffer's address, its present mode and the drops.
- Commit charge against the commit limit: GetPerformanceInfo on Windows,
Committed_AS and CommitLimit from /proc/meminfo on Linux; page faults per
second from PageFaultCount or getrusage.
- Host imports: their count and bytes against the budget, on devices that
import host memory.
- CPU copies: address-based builds count the bytes they copy in a
per-thread counter (a relaxed load and store, folded once a second) with
the largest build of each second against the address-copy cap.
- Unimplemented-function calls: libc counts NotImplemented_nid_no_patch
calls; refused shaders are the APS5_SKIP_REFUSED_DRAWS set's size.
- Title ID (when the title has a param.json) and present mode.
The tests cover the trace deltas, hit rates, present-mode names, the
/proc/meminfo parser and the OS reads, the per-thread copy counter's fold,
the counted unimplemented call, the trace switch with page 2 and lazy
binding without buffering, and on lavapipe the sampler end to end with a
fake driver source, its reset when page 2 is left, and the cost of a
present hidden, on page 1 and on page 2.
F10 now cycles through three states: off, the existing overlay (page 1)
and a detailed page (page 2), then off again. ANYPS5_OVERLAY=2 starts on
page 2; 1 and 0 or unset keep their meaning, and any other value still
throws, now naming 0, 1 and 2. OverlayPageAfter is the cycle, the
starting page plus the F10 presses modulo 3. Toggle stores the page it
gives under a mutex and PerformanceOverlay::Page loads it, so Visible,
which every present calls, stays one load as before. Each change of
page resets the statistics, as a toggle did.
Page 2 has its own composer, ComposeOverlayDetails, a pure function of
one OverlayDetailSample. It fills a 46-column panel without the graph.
Every value is optional, and a line whose sources are all missing is
left out; a line with some missing shows only the values it has. Each
line names its unit. Names from elsewhere (threads, trace phases, the
title, the present mode) go through OverlayText, which upper-cases
them and turns characters the font has no glyph for into spaces;
phase names are cut to fit the line. Page 1 is unchanged, line for
line.
On page 2, NotePresent keeps the frame statistics and fills the sample
with the frame rate, the last frame's time, presents per second and
the pipelines created in the last second. It reads nothing else, and
it skips page 1's memory reads and packet sums. The next commit adds
the driver and system counters. NotePresent latches the page, so
Record draws the page NotePresent sampled.
Tests: ANYPS5_OVERLAY 2 and 3, 02 and -1; the cycle from each starting
page through three presses and past 2^64 presses; page 2 of a full
sample, line for line, of an empty sample, and of a sample with
missing sources; every character either composer can emit has a
glyph; page 2 laid out at 1280x720, 1920x1080, 3840x2160 and 640x360,
every line drawn, no graph, and a 60-line page shrunk to scale 3 at
3840x2160. On lavapipe, under the Khronos validation layer with no
message: page 2 drawn at 1280x720 bit for bit, and F10 through a real
overlay: page 1, page 2 drawn from its own composer, then hidden, with
the statistics reset at each change.
The panel was fixed at OverlayMetrics::Columns, 27 columns, and always
kept rows for the frame-time graph. OverlayContent now carries its
column count and whether it has a graph, and the panel's width, height,
fit test and scale come from them. ComposeOverlay still fills 27
columns with a graph, so the existing panel is drawn as before, line
for line.
LayOutOverlay refuses a panel with no columns, a graph on a panel
narrower than its 120 frames, and frame times on a panel without a
graph.
The 5x7 font gains %, +, -, _, J, Q, Y and Z, from the same HD44780
character set as its other glyphs, and OverlayFontCharacters lists
every character it can draw.
Tests: every listed character has a glyph no wider than 5 pixels,
non-empty unless it is the space, and unlike any other; characters
outside the list are refused; a 48-column panel without a graph is
laid out and scaled at 1280x720, 1920x1080, 3840x2160 and 320x180; a
140-column panel at 3840x2160 shrinks from scale 5 to 4; a too-long
line, a 20-column graph and frame times without a graph are refused.
A depth plane and the color storage image that holds or receives its texels were copied image to buffer, then buffer to image. With VK_KHR_maintenance8, which the device already enables, vkCmdCopyImage copies the depth aspect directly to and from a single-component color format of its size (D32 with R32, D16 with R16), so those copies record one image copy and two barriers in place of the staging buffer round trip and three barriers. TakeStorage keeps its discard of the new depth surface before the copy. 24-bit depth, the stencil plane and every other format pair keep the buffer path, as does APS5_NO_DIRECT_DEPTH_COPY=1. The graphics Context now carries the maintenance8 flag. No performance claim is made.
Two runs of the same scene drift apart by more than many effects worth
measuring. APS5_AB=<seconds>:<switch>[+...] alternates phase A (switches
as the environment sets them) and phase B (the listed switches toggled)
at flip packets, under the GPU mutex after the draw pipeline drained,
and prints "[ab] phase A|B <k> begins/settled at present <n> (t <s> s)".
APS5_AB_START (a flip count, or <n>s) delays the first phase and
APS5_AB_SETTLE (default 2 s) marks when a phase's reports are clean.
Switches live in a registry (RuntimeSwitches) of relaxed atomics that
start as the environment sets them; APS5_NO_BARRIER_BATCH is the first,
since every elision it gates is decided per barrier. Any other name
fails the first flip. Without APS5_AB nothing changes.
AI-assisted: yes
The [gputime] line printed a draw's range key with the draw flag still set, so an ordinary variant id 0x2 appeared as 0x8000000000000002 next to [drawvariant] 0x2. A draw's key now comes from Recorder::DrawTimingKey and the line prints it through Recorder::TimingKeyLabel as "draw 0x2", the id [drawvariant] names.
The [drawvariant] line was only printed when a draw was recorded inline, so a queued draw's first-seen variant had a timing range with no address line. It is now printed before the draw is handed to the pipeline.
The APS5_PROFILE_GPU_DRAWS row states the label, the shared per-batch query cap and that draws using a recipe are not timed.
Opt-in with APS5_PROFILE_GPU_DRAWS=1 together with APS5_PROFILE_GPU=1: a [gputime] range around each recorded draw command keyed by the fragment program's variant id, and a [drawvariant] line naming the code address behind each variant id once.
On a PS5 the builder writes 15 dwords: a tag write of 0xc6000006, opcode 0x3a with its arguments, initiator 0x80 whatever the modifier, and a closing tag write; GetSize returns 60. AnyPS5 wrote NUM_INSTANCES and DRAW_INDEX_2 under argument meanings nothing confirms. The driver refuses 0x3a, whose field meanings are not known, and names it DRAW_INDEX_MULTI_INST as Linux's nvd.h does.
sceAgcDcbWaitRegMem and sceAgcDcbDrawIndirectMulti now write their 0x342 tag writes with header flag bit 2 and sceAgcAcbWaitRegMem with no flags, as libSceAgc on a PS5 does, instead of the predicate bit. ValidateWait accepts both forms and Pm4::IsTagMarker no longer depends on the header flags.
The recorder released its lock between recording a submission, flip or
suspend point and the driver accepting it. Another thread's delta could
then wait for the accepted work, read its blocks, and record itself
after a call whose work the driver had not accepted yet, so the replay
submitted that work before applying stale bytes over its result. The
entry points now pass the driver call to the recorder, which runs it
under the same lock right after recording the call.
agc_driver_frame_replay_concurrent submits copies from a second thread
during the capture and checks that every memory delta was read after
the submissions recorded before it had completed.
Co-Authored-By: Claude Opus 5.5 <[email protected]>