@@ -2086,6 +2086,46 @@ static void hd_rt_probe(int rb, id<MTLTexture> ref, long frame){
20862086static uint64_t g_rt_sk_cur[RT_MAXREC][2], g_rt_sk_prev[RT_MAXREC][2]; static unsigned g_rt_sk_pn;
20872087static unsigned long long g_rt_sig_why[6]; /* order, size, added only, removed only, both, forced (max frames) */
20882088static 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+ }
20892129static 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; }
20902130static 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