mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Count the queue's submissions, the game's waits that returned with work held and the gate's waits with atomic adds, as several of the game's threads reach them at once.
nvnQueueSubmitCommands' q->submits++, vk_backend.c synced_held_count's two counts and vk_exec.c vk_exec_gate_wait's window (GW) run on the calling guest thread without a lock, and the game submits and waits from several threads (five in the 0.10.0 fresh session). A plain ++ there loses counts, and two threads could both reach a window's end and log it, or reset it under each other. Now each count is an atomic add; the line is logged by the thread whose add reached it, and the gate's window is taken with exchanges by the thread whose add made it 600, so adds that come in meanwhile count in that window or the next. Nothing reads q->submits today; the counts only feed log lines. Not reproduced before the change. Checks that passed: pass_cc_check of vk_exec.c, vk_backend.c and nvn_api_core.c with no new -Wall -Wextra warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -747,7 +747,7 @@ NVN_API(nvnQueueSubmitCommands) {
|
||||
NVN_BLOCKING();
|
||||
nvn_call_note(0, (uintptr_t)q, 1);
|
||||
uint64_t no = __atomic_add_fetch(&lod_entered, 1, __ATOMIC_SEQ_CST);
|
||||
q->submits++;
|
||||
__atomic_add_fetch(&q->submits, 1, __ATOMIC_RELAXED); // two of the game's threads may submit on one queue at once
|
||||
uint64_t f;
|
||||
if (gfx_replay_async()) {
|
||||
// Prepare and queue under one lock: submit numbers follow queue order (gfx_replayed_submits).
|
||||
|
||||
@@ -949,20 +949,20 @@ static void vk_be_guest_read(uint64_t addr, uint64_t size) {
|
||||
// The count of such waits that came back while event-ordered work was still held (vk_exec.c): a submission stopped at an
|
||||
// event wait returns its value with its remainder held for a later replay, so the game may take a wait for that value
|
||||
// as the whole submission done and reuse what the held part still reads. How often that happens decides whether the
|
||||
// value should be signalled only after the remainder (audit finding F-2-05). The count is unlocked: a diagnostic.
|
||||
// value should be signalled only after the remainder (audit finding F-2-05). The counts are atomic adds without a lock:
|
||||
// the game's threads wait at the same time, and each line is logged by the thread whose add reached it.
|
||||
// A wait for value 0 is left out: the window acquire's sync stands for no submission (nvn_api_obj.c
|
||||
// nvnWindowAcquireTexture gives it fence 0), and such waits were half of the lines in two sessions of 2026-11-13.
|
||||
static uint64_t synced_n, synced_held;
|
||||
static void synced_held_count(uint64_t fence) {
|
||||
if (!fence) return;
|
||||
synced_n++;
|
||||
uint64_t n = __atomic_add_fetch(&synced_n, 1, __ATOMIC_RELAXED);
|
||||
int held = vk_exec_held();
|
||||
if (!held) return;
|
||||
synced_held++;
|
||||
if (synced_held <= 10 || synced_held % 600 == 0)
|
||||
uint64_t k = __atomic_add_fetch(&synced_held, 1, __ATOMIC_RELAXED);
|
||||
if (k <= 10 || k % 600 == 0)
|
||||
rt_log("vk: a game's wait for %llu returned with %d event-ordered submission(s) still held (%llu of %llu such "
|
||||
"waits so far)", (unsigned long long)fence, held, (unsigned long long)synced_held,
|
||||
(unsigned long long)synced_n);
|
||||
"waits so far)", (unsigned long long)fence, held, (unsigned long long)k, (unsigned long long)n);
|
||||
}
|
||||
|
||||
static void vk_be_cpu_synced(uint64_t fence) {
|
||||
|
||||
@@ -1473,7 +1473,9 @@ static int cont_ready(const Cont *c) {
|
||||
// taken after the game has finished writing, and the frame is replayed in the call the game's fence stands for.
|
||||
// Only state may precede the wait in the command buffer, and the event must be one the CPU has signalled before;
|
||||
// the wait ends after NX2NAVI_GATE_WAIT_MS at the latest, and the submission is then held as before.
|
||||
static struct { uint64_t n, ns, late; } GW; // since the last perf line: waits, their time, waits that ran out
|
||||
// Since the last perf line: waits, their time, waits that ran out. vk_exec_gate_wait runs on the submitting thread
|
||||
// before the submission lock, so several threads add at once: atomics, and the thread whose add ends the window logs it.
|
||||
static struct { uint64_t n, ns, late; } GW;
|
||||
|
||||
static const void *gate_first_wait(const op_hdr *h) {
|
||||
for (int k = 0; k < 256; k++) {
|
||||
@@ -1508,19 +1510,21 @@ void vk_exec_gate_wait(const uint64_t *handles, int n) {
|
||||
uint64_t t0 = kern_now_ns(), now = t0;
|
||||
for (unsigned i = 0; !vk_exec_event_holds_now(w); i++) {
|
||||
if ((i & 15) == 15 && (now = kern_now_ns()) - t0 >= (uint64_t)cap_ns) {
|
||||
GW.late++;
|
||||
__atomic_add_fetch(&GW.late, 1, __ATOMIC_RELAXED);
|
||||
break;
|
||||
}
|
||||
if (i < 20000) plat_pause();
|
||||
else plat_yield();
|
||||
}
|
||||
GW.n++, GW.ns += kern_now_ns() - t0;
|
||||
if (GW.n >= 600) {
|
||||
__atomic_add_fetch(&GW.ns, kern_now_ns() - t0, __ATOMIC_RELAXED);
|
||||
if (__atomic_add_fetch(&GW.n, 1, __ATOMIC_RELAXED) == 600) { // one thread takes the window; adds after it count in
|
||||
uint64_t wn = __atomic_exchange_n(&GW.n, 0, __ATOMIC_RELAXED);
|
||||
uint64_t wns = __atomic_exchange_n(&GW.ns, 0, __ATOMIC_RELAXED);
|
||||
uint64_t late = __atomic_exchange_n(&GW.late, 0, __ATOMIC_RELAXED);
|
||||
if (rt_trace & RT_TRACE_PERF)
|
||||
rt_log("perf: gate: %llu submissions waited for the CPU's signal before their copies, %.2f ms on average; "
|
||||
"%llu waited %lld ms without it and were held", (unsigned long long)GW.n, (double)GW.ns / (double)GW.n / 1e6,
|
||||
(unsigned long long)GW.late, (long long)(cap_ns / 1000000));
|
||||
GW.n = GW.ns = GW.late = 0;
|
||||
"%llu waited %lld ms without it and were held", (unsigned long long)wn, (double)wns / (double)wn / 1e6,
|
||||
(unsigned long long)late, (long long)(cap_ns / 1000000));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user