Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions games/totk/tests/fps_overlay/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ int main(void) {
sysload_busy b = {0, 0};
sysload_busy_add(&b, 1.0, 1.005); sysload_busy_add(&b, 1.005, 1.010); sysload_busy_add(&b, 1.008, 1.012); sysload_busy_add(&b, 1.020, 1.020);
CHECK(b.busy > 0.01199 && b.busy < 0.01201, "busy union of 0-5, 5-10, 8-12 ms (and an empty one) is 12 ms, got %.6f s", b.busy);
CHECK(sysload_busy_add(&b, 0, 2943907.0) == 0 && sysload_busy_add(&b, 1.013, 0) == 0 &&
sysload_busy_add(&b, 1.013, INFINITY) == 0 && sysload_busy_add(&b, NAN, 1.014) == 0 &&
b.last_end == 1.012 && b.busy > 0.01199 && b.busy < 0.01201,
"missing or nonfinite Metal timestamps must not change the measured interval");
CHECK(sysload_busy_add(&b, 1.013, 1.014) > 0 && b.last_end == 1.014,
"valid samples must remain usable after a missing timestamp");
pthread_t spin; pthread_create(&spin, NULL, spin_main, NULL); // a thread that keeps one core busy: the busiest thread
sysload_start(); usleep(1200000);
sysload_info real; int got = sysload_get(&real);
Expand Down
24 changes: 16 additions & 8 deletions games/totk/third_party/d2r_gfx/gfx/nvn/nvn_mtl.m
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,10 @@ static void keepalive_start(void) {
id<MTLCommandBuffer> cb = [q commandBuffer]; id<MTLComputeCommandEncoder> ce = [cb computeCommandEncoder];
[ce setComputePipelineState:ps]; [ce setBuffer:buf offset:0 atIndex:0]; [ce setBytes:&iters length:4 atIndex:1];
[ce dispatchThreads:MTLSizeMake(32, 1, 1) threadsPerThreadgroup:MTLSizeMake(32, 1, 1)]; [ce endEncoding];
[cb addCompletedHandler:^(id<MTLCommandBuffer> c) { double ms = (c.GPUEndTime - c.GPUStartTime) * 1e3; static int said; if (said++ < 3) MLOG("TOTK_GPU_KEEPALIVE:自旋核一次 %.3f ms", ms);
[cb addCompletedHandler:^(id<MTLCommandBuffer> c) {
double start = c.GPUStartTime, end = c.GPUEndTime;
if (!sysload_gpu_span_valid(start, end)) { dispatch_semaphore_signal(sem); return; }
double ms = (end - start) * 1e3; static int said; if (said++ < 3) MLOG("TOTK_GPU_KEEPALIVE:自旋核一次 %.3f ms", ms);
uint64_t us = (uint64_t)(ms * 1e3); atomic_fetch_add(&g_ka_n, 1); atomic_fetch_add(&g_ka_us, us); uint64_t mx = atomic_load(&g_ka_max_us); while (us > mx && !atomic_compare_exchange_weak(&g_ka_max_us, &mx, us)) {}
if (ms > 0.01) { double k = (target_us * .001) / ms; if (k > 2) k = 2; if (k < 0.5) k = 0.5; double m = n * k; n = m < 2000 ? 2000 : m > 400000 ? 400000 : (uint32_t)m; }
dispatch_semaphore_signal(sem); }];
Expand Down Expand Up @@ -2236,9 +2239,8 @@ static void gpu_tag_done(uint64_t f, double g0, double g1) {
sysload_note_gpu(g0, g1); // the load line's game GPU share
unsigned k = f % GRING;
if (atomic_load(&g_gpu[k].frame) != f) return;
double d = g1 - g0;
if (d > 0) {
atomic_fetch_add(&g_gpu[k].ns, (uint64_t)(d * 1e9));
if (sysload_gpu_span_valid(g0, g1)) {
atomic_fetch_add(&g_gpu[k].ns, (uint64_t)((g1 - g0) * 1e9));
uint64_t s0 = (uint64_t)(g0 * 1e9), s1 = (uint64_t)(g1 * 1e9), v;
for (v = atomic_load(&g_gpu[k].t0); s0 < v && !atomic_compare_exchange_weak(&g_gpu[k].t0, &v, s0);) {}
for (v = atomic_load(&g_gpu[k].t1); s1 > v && !atomic_compare_exchange_weak(&g_gpu[k].t1, &v, s1);) {}
Expand Down Expand Up @@ -2318,7 +2320,10 @@ static void mtl_sub_split(void) {
if (f.error) { atomic_store(&g_last_commit_error, 1); static int n; if (n++ < 8) MLOG("【红】分段命令缓冲错误 fence=%llu:%s", (unsigned long long)fence_next, f.error.description.UTF8String); }
}];
if (totk_tl_on()) { uint64_t tlid = totk_tl_commit_id(); totk_tl(TL_COMMIT, tlid, 0);
[o addFeedbackHandler:^(id<MTL4CommitFeedback> fb) { totk_tl_at(TL_GPU, (uint64_t)(fb.GPUStartTime * 1e9), tlid, (uint64_t)(fb.GPUEndTime * 1e9)); }]; }
[o addFeedbackHandler:^(id<MTL4CommitFeedback> fb) {
double start = fb.GPUStartTime, end = fb.GPUEndTime;
if (sysload_gpu_span_valid(start, end)) totk_tl_at(TL_GPU, (uint64_t)(start * 1e9), tlid, (uint64_t)(end * 1e9));
}]; }
id<MTL4CommandBuffer> next = [g_dev newCommandBuffer];
if (early) { w0 = clock_gettime_nsec_np(CLOCK_UPTIME_RAW); rs_sync(); fc1 = fc0 + (clock_gettime_nsec_np(CLOCK_UPTIME_RAW) - w0); } // fc1 - fc0: the wait, as before
[g_cb endCommandBuffer];
Expand Down Expand Up @@ -2423,7 +2428,10 @@ the texture dump (with DM_NVN_TEXSNAP_DEPTH=256x144 also the occluder depth). A
pt_finish(g_cb, o, gpu_f);
res_commit();
if (totk_tl_on()) { uint64_t tlid = totk_tl_commit_id(); totk_tl(TL_COMMIT, tlid, 1); totk_tl(TL_FENCE, tlid, fence);
[o addFeedbackHandler:^(id<MTL4CommitFeedback> fb) { totk_tl_at(TL_GPU, (uint64_t)(fb.GPUStartTime * 1e9), tlid, (uint64_t)(fb.GPUEndTime * 1e9)); }]; }
[o addFeedbackHandler:^(id<MTL4CommitFeedback> fb) {
double start = fb.GPUStartTime, end = fb.GPUEndTime;
if (sysload_gpu_span_valid(start, end)) totk_tl_at(TL_GPU, (uint64_t)(start * 1e9), tlid, (uint64_t)(end * 1e9));
}]; }
if (g_b2_capture >= 0) { // stereo B2: the left pass's picture for the headset (the presented texture as drawn, no MetalFX)
const int eye = g_b2_capture; g_b2_capture = -1;
if (g_b2_swap) g_b2_pics++; else g_b2_nopic++;
Expand Down Expand Up @@ -3747,8 +3755,8 @@ static void up_flush(void) {
ser_bump(0); // 让各贴图号的视图缓存走一次慢路(缓存命中时不会经过 tex_mtl,看不到这份记录) | Make the view cache of every texture ID take the slow path once (on a cache hit tex_mtl is not passed through and this record is not seen)
MLOG("%s上传命令缓冲被丢:这一批 %lu 张贴图记为内容未写,下次取用时重传(待重传共 %d 张)", fake_lost ? "DM_TEST_UP_LOST:" : "", (unsigned long)dsts.count, n);
}
double d = f.GPUEndTime - f.GPUStartTime;
if (d > 0) atomic_fetch_add(&g_upload_gpu_ns, (uint64_t)(d * 1e9));
double start = f.GPUStartTime, end = f.GPUEndTime;
if (sysload_gpu_span_valid(start, end)) atomic_fetch_add(&g_upload_gpu_ns, (uint64_t)((end - start) * 1e9));
for (id obj in staging) res_remove(obj);
[staging removeAllObjects];
for (int i = 0; i < UP_POOL; i++) if (pool_mask >> i & 1) atomic_store(&g_up_pool_busy[i], 0);
Expand Down
2 changes: 1 addition & 1 deletion games/totk/third_party/d2r_gfx/gfx/nvn/totk_sysload.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ int sysload_enabled(void) {
}

double sysload_busy_add(sysload_busy *b, double s, double e) {
if (!(e > s)) return 0;
if (!sysload_gpu_span_valid(s, e)) return 0;
double from = s > b->last_end ? s : b->last_end, add = e > from ? e - from : 0;
if (e > b->last_end) b->last_end = e;
b->busy += add;
Expand Down
5 changes: 5 additions & 0 deletions games/totk/third_party/d2r_gfx/gfx/nvn/totk_sysload.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// TOTK: system load for the bottom-right line (user, 2026-10-16 night: the load of every CPU core and of the GPU, on one
// line like the FPS counter). See totk_sysload.c.
#pragma once
#include <math.h>
#include <stddef.h>
#include <stdint.h>

Expand All @@ -18,6 +19,10 @@ int sysload_get(sysload_info *out); // 0 before the first sample
void sysload_note_gpu(double start_s, double end_s); // one of our command buffers ran on the GPU (CACurrentMediaTime seconds)
/* "P 55% 56% 55% 53% E 43% 29% 20% 12% 9% 7% T 47% GPU 99% (70%)" (T: the busiest thread) */
void sysload_text(char *out, size_t n, const sysload_info *in);
/* Missing Metal timestamps can be zero independently, even on completion. */
static inline int sysload_gpu_span_valid(double start_s, double end_s) {
return start_s > 0 && end_s > start_s && isfinite(end_s);
}
/* Busy time of command buffers that complete in order: the union of their [start, end) intervals. Returns what was added. */
typedef struct { double last_end, busy; } sysload_busy;
double sysload_busy_add(sysload_busy *b, double start_s, double end_s);