Skip to content

Commit 60acea9

Browse files
committed
HD reflections: keep the instant structure's layout, refit instead of rebuilding
Metal's acceleration-structure build on M1 Pro has a large fixed cost (measured standalone at the highest clock: 500 triangles 0.6 ms, the game's instant triangles 1.0 ms, refit 0.1 ms). The instant structure was rebuilt whenever the draw list changed at all and every 20 frames; in a career session 54% of frames paid for a full build. Now the last build's layout is kept: each draw goes back to its own slot, a slot nobody takes is collapsed in place (area 0), a new draw only goes into a hole built at the same place, otherwise a full build as before. The forced rebuild every 20 frames stays only when a draw has no known position (the position check already catches motion). Same triangles for the rays; the ray kernel time is unchanged (2.28 ms vs 2.26 ms). Quick Race: builds 290 -> 118 per run. HD_RT_SLOT=0 old behavior, ABTEST=RTSLOT. HD_RT_DUMPDYN=<frame> writes the instant vertices for standalone build timing.
1 parent a992a55 commit 60acea9

1 file changed

Lines changed: 188 additions & 14 deletions

File tree

‎runtime/metal.m‎

Lines changed: 188 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2086,6 +2086,46 @@ static void hd_rt_probe(int rb, id<MTLTexture> ref, long frame){
20862086
static uint64_t g_rt_sk_cur[RT_MAXREC][2], g_rt_sk_prev[RT_MAXREC][2]; static unsigned g_rt_sk_pn;
20872087
static unsigned long long g_rt_sig_why[6]; /* order, size, added only, removed only, both, forced (max frames) */
20882088
static unsigned long long g_rt_sig_add, g_rt_sig_rem;
2089+
/* --- KEPT LAYOUT (instant structure) --------------------------------------------
2090+
* Metal's acceleration-structure build on this GPU has a large fixed cost (measured
2091+
* standalone, M1 Pro at the highest clock: 500 triangles 0.6 ms, the game's ~5-11
2092+
* thousand instant triangles 1.0 ms; a refit 0.1-0.14 ms). The instant structure was
2093+
* rebuilt whenever the draw list changed at all (an object leaving, coming back, the
2094+
* order of copies) and every RT_REFIT_MAX frames: in a career session 54% of frames
2095+
* built it (20863 of 38855). Now the layout of the last full build is KEPT:
2096+
* - each draw goes back to its own slot (same VS, size, texture, geometry id; among
2097+
* copies the nearest by position), the order in the draw list doesn't matter;
2098+
* - a slot nobody took becomes a hole: collapsed onto its first vertex (area 0, rays
2099+
* don't hit it, the tree's boxes stay in place);
2100+
* - a new draw only goes into a hole that was built at the same place and is big
2101+
* enough (the same object back, another LOD), otherwise a full build as before;
2102+
* - the position check against the last build stays (RT_REFIT_MOVE). So the forced
2103+
* rebuild every RT_REFIT_MAX frames is only kept when a draw has no known
2104+
* position; otherwise every RT_REFIT_MAXP frames.
2105+
* The rays see the same triangles: only how often the tree is rebuilt changes.
2106+
* HD_RT_SLOT=0 old behavior, ABTEST=RTSLOT in the same run. */
2107+
#define RT_REFIT_MAXP 240u
2108+
#define RT_LS_MAXREC 2048u
2109+
#define RT_LS_MAXCOL 480u /* collapse ranges per frame (setBytes <= 4 KB) */
2110+
typedef struct { uint64_t key; uint32_t first, n, cap; float bp[3]; uint8_t bpok, hole, collapsed, taken; } RTSlot;
2111+
static RTSlot g_ls[RT_LS_MAXREC]; static unsigned g_ls_n; static uint32_t g_ls_tot; static int g_ls_ok;
2112+
static uint64_t g_rt_rk[RT_MAXREC]; static uint16_t g_rt_rvs[RT_MAXREC]; static uint32_t g_rt_rn[RT_MAXREC], g_rt_rf[RT_MAXREC];
2113+
static _Atomic unsigned long long g_rt_ls_noplace, g_rt_ls_reuse;
2114+
static int hd_rt_refit_on(void);
2115+
static int rt_ls_on(void){
2116+
static int v = -1;
2117+
if (v < 0){ const char *e = ENVC("HD_RT_SLOT"); v = (e && *e == '0') ? 0 : 1; }
2118+
return v && hd_rt_refit_on() && !abt_off("RTSLOT");
2119+
}
2120+
static uint64_t rt_ls_key(const MDraw *dr, unsigned n){
2121+
uint64_t k = (uint64_t)dr->vsid * 1099511628211ull ^ ((uint64_t)(uint32_t)dr->ps << 32 | dr->tex);
2122+
k = k * 1099511628211ull ^ dr->gh;
2123+
return k * 1099511628211ull ^ n;
2124+
}
2125+
static int rt_rf_cmp(void *t, const void *a, const void *b){
2126+
(void)t; uint32_t x = g_rt_rf[*(const uint32_t *)a], y = g_rt_rf[*(const uint32_t *)b];
2127+
return x < y ? -1 : x > y;
2128+
}
20892129
static int u64cmp(const void *a, const void *b){ uint64_t x = *(const uint64_t *)a, y = *(const uint64_t *)b; return x < y ? -1 : x > y; }
20902130
static void rt_sig_classify(unsigned n){
20912131
static uint64_t a[RT_MAXREC], b[RT_MAXREC];
@@ -3088,6 +3128,83 @@ static unsigned hd_rt_frame(id<MTLCommandBuffer> cb, int rb, unsigned upto, cons
30883128
}
30893129
{ unsigned a = 0; for (int g = 0; g < HD_MAXVSI; g++){ off[g] = a; a += cnt[g]; } }
30903130
memcpy(fill, off, sizeof fill);
3131+
/* --- placement: dense (a full build) or the kept layout (refit, see KEPT LAYOUT) --- */
3132+
static uint32_t pfirst[MAXD]; static uint16_t pslot[MAXD];
3133+
int slot_mode = 0; unsigned ncol = 0;
3134+
static uint32_t colr[RT_LS_MAXCOL][2];
3135+
if (rt_ls_on() && g_ls_ok && nrec <= RT_LS_MAXREC){
3136+
static float mv2 = -1.f;
3137+
if (mv2 < 0.f){ const char *ev = ENVC("HD_RT_REFIT_MOVE"); float m = ev ? (float)atof(ev) : RT_REFIT_MOVE; mv2 = m * m; }
3138+
int ok = 1, nopos = 0;
3139+
static uint16_t pend[RT_LS_MAXREC]; unsigned npend = 0;
3140+
for (unsigned s = 0; s < g_ls_n; s++) g_ls[s].taken = 0;
3141+
for (unsigned i = 0; i < upto && i < MAXD && ok; i++){
3142+
if (!act[i]) continue;
3143+
MDraw *dr = &g_dbuf[rb][i];
3144+
unsigned n = dr->count - dr->count % 3u;
3145+
uint64_t key = rt_ls_key(dr, n);
3146+
if (!g_rt_dpok[i]) nopos = 1;
3147+
int best = -1; float bd = 3e38f;
3148+
for (unsigned s = 0; s < g_ls_n; s++){
3149+
RTSlot *sl = &g_ls[s];
3150+
if (sl->taken || sl->hole || sl->key != key) continue;
3151+
float d = 0.f;
3152+
if (g_rt_dpok[i] && sl->bpok){
3153+
float dx = g_rt_dp[i][0] - sl->bp[0], dy = g_rt_dp[i][1] - sl->bp[1], dz = g_rt_dp[i][2] - sl->bp[2];
3154+
d = dx*dx + dy*dy + dz*dz; }
3155+
if (d < bd){ bd = d; best = (int)s; if (d == 0.f) break; }
3156+
}
3157+
if (best < 0){ pend[npend++] = (uint16_t)i; continue; }
3158+
if (bd > mv2){ ok = 0; atomic_fetch_add(&g_rt_nmoved, 1ull); break; } /* see POSITION CHECK */
3159+
g_ls[best].taken = 1; pfirst[i] = g_ls[best].first; pslot[i] = (uint16_t)best;
3160+
}
3161+
/* slots nobody took this frame become holes (collapsed below) */
3162+
for (unsigned s = 0; s < g_ls_n && ok; s++) if (!g_ls[s].taken && !g_ls[s].hole){ g_ls[s].hole = 1; }
3163+
/* a new draw goes into a hole only where the hole's part was built and fits
3164+
* (the same object back, another LOD): the tree's boxes are already there */
3165+
for (unsigned q = 0; q < npend && ok; q++){
3166+
unsigned i = pend[q];
3167+
MDraw *dr = &g_dbuf[rb][i];
3168+
unsigned n = dr->count - dr->count % 3u;
3169+
int best = -1;
3170+
if (g_rt_dpok[i]) for (unsigned s = 0; s < g_ls_n; s++){
3171+
RTSlot *sl = &g_ls[s];
3172+
if (!sl->hole || sl->taken || !sl->bpok || sl->cap < n) continue;
3173+
float dx = g_rt_dp[i][0] - sl->bp[0], dy = g_rt_dp[i][1] - sl->bp[1], dz = g_rt_dp[i][2] - sl->bp[2];
3174+
if (dx*dx + dy*dy + dz*dz <= mv2){ best = (int)s; break; }
3175+
}
3176+
if (best < 0){ ok = 0; atomic_fetch_add(&g_rt_ls_noplace, 1ull); break; }
3177+
RTSlot *sl = &g_ls[best];
3178+
sl->taken = 1; sl->hole = 0; sl->key = rt_ls_key(dr, n); sl->n = n;
3179+
if (!sl->collapsed && ncol < RT_LS_MAXCOL){ colr[ncol][0] = sl->first; colr[ncol][1] = sl->cap; ncol++; }
3180+
else if (!sl->collapsed) ok = 0;
3181+
sl->collapsed = 0;
3182+
pfirst[i] = sl->first; pslot[i] = (uint16_t)best;
3183+
atomic_fetch_add(&g_rt_ls_reuse, 1ull);
3184+
}
3185+
if (ok && g_rt_dsince >= (nopos ? RT_REFIT_MAX : RT_REFIT_MAXP)){ ok = 0; g_rt_sig_why[5]++; }
3186+
if (ok){
3187+
/* holes not collapsed yet: collapse onto their first vertex (area 0, in place) */
3188+
for (unsigned s = 0; s < g_ls_n && ok; s++){
3189+
RTSlot *sl = &g_ls[s];
3190+
if (!sl->hole || sl->collapsed) continue;
3191+
if (ncol >= RT_LS_MAXCOL){ ok = 0; break; }
3192+
colr[ncol][0] = sl->first; colr[ncol][1] = sl->cap; ncol++;
3193+
}
3194+
}
3195+
if (ok){
3196+
for (unsigned s = 0; s < g_ls_n; s++) if (g_ls[s].hole) g_ls[s].collapsed = 1;
3197+
slot_mode = 1; total = g_ls_tot;
3198+
} else {
3199+
g_ls_ok = 0; ncol = 0; /* a full build below sets a new layout */
3200+
}
3201+
}
3202+
if (!slot_mode)
3203+
for (unsigned i = 0; i < upto && i < MAXD; i++){
3204+
if (!act[i]) continue;
3205+
MDraw *dr = &g_dbuf[rb][i];
3206+
pfirst[i] = fill[dr->vsid]; fill[dr->vsid] += dr->count - dr->count % 3u;
3207+
}
30913208
uint32_t *item = (uint32_t *)[g_rt_item[ring] contents];
30923209
HDRecC *rec = (HDRecC *)[g_rt_rec[ring] contents];
30933210
HDMatC *mat = (HDMatC *)[g_rt_mat[ring] contents];
@@ -3101,13 +3218,13 @@ static unsigned hd_rt_frame(id<MTLCommandBuffer> cb, int rb, unsigned upto, cons
31013218
for (unsigned i = 0; i < upto && i < MAXD; i++){
31023219
if (!act[i]) continue;
31033220
MDraw *dr = &g_dbuf[rb][i];
3104-
unsigned n = dr->count - dr->count % 3u, f = fill[dr->vsid];
3221+
unsigned n = dr->count - dr->count % 3u, f = pfirst[i];
31053222
{ uint64_t w[4] = { (uint64_t)dr->vsid << 32 | n, (uint64_t)(uint32_t)dr->ps << 32 | dr->tex, dr->gh, f };
31063223
for (int q = 0; q < 4; q++){ dsig ^= w[q]; dsig *= 1099511628211ull; }
31073224
if (ri < RT_MAXREC){ /* HD_RT_SIGLOG: the record's identity with / without its size */
31083225
uint64_t k = (uint64_t)dr->vsid * 1099511628211ull ^ ((uint64_t)(uint32_t)dr->ps << 32 | dr->tex);
31093226
k = k * 1099511628211ull ^ dr->gh; g_rt_sk_cur[ri][0] = k * 1099511628211ull ^ n; g_rt_sk_cur[ri][1] = k; } }
3110-
fill[dr->vsid] += n;
3227+
if (ri < RT_MAXREC){ g_rt_rk[ri] = rt_ls_key(dr, n); g_rt_rvs[ri] = (uint16_t)dr->vsid; g_rt_rn[ri] = n; g_rt_rf[ri] = f; }
31113228
int base = dr->ps % PSREFL, cube = base >= PSCUBE, b = base % PSCUBE;
31123229
HDMatC m; rt_mat_of(dr, &m);
31133230
RTEnt *e = ent[i];
@@ -3209,8 +3326,33 @@ static unsigned hd_rt_frame(id<MTLCommandBuffer> cb, int rb, unsigned upto, cons
32093326
for (int g = 1; g < HD_MAXVSI && o < 480; g++) if (cnt[g]) o += snprintf(ln + o, sizeof ln - (size_t)o, " vs_%d:%u", g, cnt[g]);
32103327
fprintf(stderr, "[RTLOG] instant capture (VS:verts):%s\n", o ? ln : " -"); }
32113328
} }
3329+
/* --- GPU: collapse the kept layout's new holes (before the capture rewrites reused ones) --- */
3330+
if (ncol){
3331+
id<MTLComputeCommandEncoder> ce = pp_compute(cb, "RT: instant holes collapse");
3332+
[ce setComputePipelineState:g_rt_colk];
3333+
[ce setBuffer:g_rt_pos offset:0 atIndex:0];
3334+
[ce setBytes:colr length:(NSUInteger)ncol * 8 atIndex:1];
3335+
uint32_t nr = ncol; [ce setBytes:&nr length:4 atIndex:2];
3336+
NSUInteger tw = g_rt_colk.threadExecutionWidth;
3337+
[ce dispatchThreadgroups:MTLSizeMake((ncol + tw - 1) / tw, 1, 1) threadsPerThreadgroup:MTLSizeMake(tw, 1, 1)];
3338+
[ce endEncoding];
3339+
}
3340+
/* capture runs: records sorted by first vertex, adjacent ones of the same VS merged
3341+
* (dense layout: one run per VS, as before) */
3342+
static uint32_t run[RT_MAXREC][3]; unsigned nrun = 0;
3343+
{ static uint32_t ord[RT_MAXREC];
3344+
unsigned nr = ri < RT_MAXREC ? ri : RT_MAXREC;
3345+
for (unsigned q = 0; q < nr; q++) ord[q] = q;
3346+
if (slot_mode) qsort_r(ord, nr, 4, NULL, rt_rf_cmp);
3347+
for (unsigned q = 0; q < nr; q++){
3348+
unsigned r = ord[q];
3349+
if (!g_rt_rn[r]) continue;
3350+
if (nrun && run[nrun-1][2] == g_rt_rvs[r] && run[nrun-1][0] + run[nrun-1][1] == g_rt_rf[r]) run[nrun-1][1] += g_rt_rn[r];
3351+
else { run[nrun][0] = g_rt_rf[r]; run[nrun][1] = g_rt_rn[r]; run[nrun][2] = g_rt_rvs[r]; nrun++; }
3352+
}
3353+
}
32123354
/* --- GPU: capture --- */
3213-
if (total){
3355+
if (nrun){
32143356
id<MTLComputeCommandEncoder> ce = pp_compute(cb, "RT: vertex capture (compute)");
32153357
[ce setBuffer:g_mtlrvb offset:0 atIndex:1];
32163358
[ce setBuffer:g_mtlkb offset:0 atIndex:2];
@@ -3219,31 +3361,31 @@ static unsigned hd_rt_frame(id<MTLCommandBuffer> cb, int rb, unsigned upto, cons
32193361
[ce setBuffer:g_rt_pos offset:0 atIndex:5];
32203362
[ce setBuffer:g_rt_attr offset:0 atIndex:6];
32213363
[ce setBuffer:(g_pool_fr[rb] ? g_pool_fr[rb] : g_mtlrvb) offset:0 atIndex:8]; /* indexed records */
3222-
for (int g = 1; g < HD_MAXVSI; g++){
3223-
if (!cnt[g]) continue;
3364+
for (unsigned u = 0; u < nrun; u++){
3365+
int g = (int)run[u][2];
32243366
struct { float invV[16]; float prm[4]; uint32_t first, count, p0, p1; } C;
32253367
memcpy(C.invV, iv, sizeof C.invV);
32263368
C.prm[0] = ipx; C.prm[1] = ipy; C.prm[2] = C.prm[3] = 0.f;
3227-
C.first = off[g]; C.count = cnt[g]; C.p0 = C.p1 = 0;
3369+
C.first = run[u][0]; C.count = run[u][1]; C.p0 = C.p1 = 0;
32283370
[ce setComputePipelineState:g_hd_vsk[g]];
32293371
[ce setBytes:&C length:sizeof C atIndex:7];
32303372
NSUInteger tw = g_hd_vsk[g].maxTotalThreadsPerThreadgroup;
3231-
[ce dispatchThreadgroups:MTLSizeMake((cnt[g] + tw - 1) / tw, 1, 1)
3373+
[ce dispatchThreadgroups:MTLSizeMake((run[u][1] + tw - 1) / tw, 1, 1)
32323374
threadsPerThreadgroup:MTLSizeMake(tw, 1, 1)];
32333375
}
32343376
if (g_rt_fixk && g_rt_fixcnt){
32353377
[ce setComputePipelineState:g_rt_fixk];
32363378
[ce setBuffer:g_rt_pos offset:0 atIndex:0];
32373379
[ce setBuffer:g_rt_fixcnt offset:0 atIndex:1];
32383380
NSUInteger tw = g_rt_fixk.maxTotalThreadsPerThreadgroup;
3239-
for (int g = 1; g < HD_MAXVSI; g++){ /* per VS: so the counters tell which shader it is */
3240-
if (!cnt[g]) continue;
3381+
for (unsigned u = 0; u < nrun; u++){ /* per run of one VS: so the counters tell which shader it is */
3382+
int g = (int)run[u][2];
32413383
static float poison = -1.f; /* HD_RT_POISON=<value>: causality test (no check done) */
32423384
if (poison < 0.f){ const char *ev = ENVC("HD_RT_POISON"); poison = ev ? (float)atof(ev) : 0.f; }
3243-
struct { uint32_t ntri; float lim; uint32_t first, slot; } F = { cnt[g] / 3u, poison > 0.f ? -poison : 100000.f, off[g], (uint32_t)g };
3385+
struct { uint32_t ntri; float lim; uint32_t first, slot; } F = { run[u][1] / 3u, poison > 0.f ? -poison : 100000.f, run[u][0], (uint32_t)g };
32443386
if (poison > 0.f && g != 3) continue; /* only the first triangle of one VS (world) */
32453387
[ce setBytes:&F length:sizeof F atIndex:2];
3246-
[ce dispatchThreadgroups:MTLSizeMake((cnt[g] / 3u + tw - 1) / tw, 1, 1)
3388+
[ce dispatchThreadgroups:MTLSizeMake((run[u][1] / 3u + tw - 1) / tw, 1, 1)
32473389
threadsPerThreadgroup:MTLSizeMake(tw, 1, 1)];
32483390
}
32493391
}
@@ -3282,12 +3424,14 @@ static unsigned hd_rt_frame(id<MTLCommandBuffer> cb, int rb, unsigned upto, cons
32823424
int have_dyn = total >= 3, dyn_refit = 0;
32833425
if (have_dyn){
32843426
dsig ^= (uint64_t)total; dsig *= 1099511628211ull;
3285-
dyn_refit = hd_rt_refit_on() && dsig == g_rt_dsig && g_rt_dsince < RT_REFIT_MAX;
3286-
if (ENVC("HD_RT_SIGLOG")){
3427+
dyn_refit = slot_mode ? 1 : (!rt_ls_on() && hd_rt_refit_on() && dsig == g_rt_dsig && g_rt_dsince < RT_REFIT_MAX);
3428+
if (slot_mode) dsig = 0; /* the dense signature no longer describes the layout */
3429+
if (ENVC("HD_RT_SIGLOG") && !slot_mode){
32873430
if (dsig != g_rt_dsig) rt_sig_classify(ri);
32883431
else if (g_rt_dsince >= RT_REFIT_MAX) g_rt_sig_why[5]++;
32893432
memcpy(g_rt_sk_prev, g_rt_sk_cur, (size_t)ri * sizeof g_rt_sk_cur[0]); g_rt_sk_pn = ri; }
3290-
if (dyn_refit && ri == g_rt_bn){ /* see POSITION CHECK */
3433+
if (slot_mode) ; /* checked in the placement */
3434+
else if (dyn_refit && ri == g_rt_bn){ /* see POSITION CHECK */
32913435
static float mv2 = -1.f;
32923436
if (mv2 < 0.f){ const char *ev = ENVC("HD_RT_REFIT_MOVE"); float m = ev ? (float)atof(ev) : RT_REFIT_MOVE; mv2 = m * m; }
32933437
for (unsigned q = 0; q < ri; q++){
@@ -3326,6 +3470,13 @@ static unsigned hd_rt_frame(id<MTLCommandBuffer> cb, int rb, unsigned upto, cons
33263470
[ae buildAccelerationStructure:g_rt_as descriptor:ad scratchBuffer:g_rt_scratch scratchBufferOffset:0];
33273471
g_rt_dsince = 0; atomic_fetch_add(&g_rt_nbuild, 1ull);
33283472
memcpy(g_rt_bpos, cpos, (size_t)ri * sizeof cpos[0]); memcpy(g_rt_bpok, cpok, ri); g_rt_bn = ri;
3473+
/* the new tree's layout: kept from now on (see KEPT LAYOUT) */
3474+
g_ls_ok = ri <= RT_LS_MAXREC; g_ls_n = g_ls_ok ? ri : 0; g_ls_tot = total;
3475+
for (unsigned r = 0; r < g_ls_n; r++){
3476+
RTSlot *sl = &g_ls[r]; memset(sl, 0, sizeof *sl);
3477+
sl->key = g_rt_rk[r]; sl->first = g_rt_rf[r]; sl->n = sl->cap = g_rt_rn[r];
3478+
sl->bpok = cpok[r]; if (cpok[r]) memcpy(sl->bp, cpos[r], 12);
3479+
}
33293480
}
33303481
g_rt_dsig = dsig;
33313482
}
@@ -3356,6 +3507,27 @@ static unsigned hd_rt_frame(id<MTLCommandBuffer> cb, int rb, unsigned upto, cons
33563507
}
33573508
[ae endEncoding];
33583509
}
3510+
/* HD_RT_DUMPDYN=<frame> HD_RT_DUMPDYN_FILE=<path>: write that frame's instant-structure
3511+
* vertices (float3, non-indexed) so the build can be timed outside the game (diagnostic) */
3512+
if (have_dyn){
3513+
static long dumpf = -2;
3514+
if (dumpf == -2){ const char *e = ENVC("HD_RT_DUMPDYN"); dumpf = e ? atol(e) : -1; }
3515+
if (dumpf >= 0 && frame >= (unsigned long)dumpf){
3516+
dumpf = -1;
3517+
const char *fn = ENVC("HD_RT_DUMPDYN_FILE"); if (!fn) fn = "rt_dyn.bin";
3518+
char *path = strdup(fn);
3519+
NSUInteger nb = (NSUInteger)total * 12;
3520+
id<MTLBuffer> sb = [g_dev newBufferWithLength:nb options:MTLResourceStorageModeShared];
3521+
id<MTLBlitCommandEncoder> be = [cb blitCommandEncoder];
3522+
[be copyFromBuffer:g_rt_pos sourceOffset:0 toBuffer:sb destinationOffset:0 size:nb];
3523+
[be endEncoding];
3524+
[cb addCompletedHandler:^(id<MTLCommandBuffer> c){ (void)c;
3525+
FILE *f = fopen(path, "wb");
3526+
if (f){ fwrite(sb.contents, 1, nb, f); fclose(f); }
3527+
fprintf(stderr, "[HD] RT instant vertices dumped: %lu tris -> %s\n", (unsigned long)(nb / 36), path);
3528+
free(path); }];
3529+
}
3530+
}
33593531
if (!have_dyn) g_rt_dsince = 0xFFFFFFFFu;
33603532
/* the pending promotions in the built new region are now in the structure: remove from capture */
33613533
if (ract == 1) for (unsigned q = 0; q < RT_TABN; q++){
@@ -8507,6 +8679,8 @@ void metal_budget_report(void){
85078679
g_rt_sig_add, g_rt_sig_rem, RT_REFIT_MAX, g_rt_sig_why[5]);
85088680
fprintf(stderr, "[HD] RT instant structure: %llu full builds (%llu due to position shift), %llu in-place updates (refit %s)\n",
85098681
atomic_load(&g_rt_nbuild), atomic_load(&g_rt_nmoved), atomic_load(&g_rt_nrefit), hd_rt_refit_on() ? "ON" : "OFF");
8682+
fprintf(stderr, "[HD] RT kept layout %s: new draws into holes %llu, no place (full build) %llu, forced %llu\n",
8683+
rt_ls_on() ? "ON" : "OFF", atomic_load(&g_rt_ls_reuse), atomic_load(&g_rt_ls_noplace), g_rt_sig_why[5]);
85108684
{ uint32_t gv = 0; for (unsigned k = 0; k < g_rt_ng; k++) gv += g_rt_grp[k].built;
85118685
fprintf(stderr, "[HD] RT cache: merges %llu (oldest dropped %llu), part builds %llu, refits %llu, "
85128686
"rehashes %llu, store %u verts (%u dead; %u groups %u verts, new %u)\n",

0 commit comments

Comments
 (0)