Cache native indexed texels and omit production NCLIP debug receipts

This commit is contained in:
Shokunin
2026-10-21 10:48:37 -07:00
parent 4b2fdfadc3
commit 9cdfbe0a0a
4 changed files with 249 additions and 1 deletions
+2
View File
@@ -213,6 +213,8 @@ on a fixed region -> next.
## 5. Status / Log (update every session)
- **2026-10-21 (BRIEF-4 full-range work):** Reuse exact indexed 1555 page decoding and epoch lookup from `ab784091`/`341a3598`, restricted to raw VRAM-backed source/palette to avoid mutable bank-name reuse. Coherency writes and transaction restoration invalidate affected regions. Skip debug-only NCLIP hash-table receipts in production; retain culling math and summary counters. R4 failing quiet/loaded acceptance cases will be repeated after these fixes.
- **2026-10-21 (BRIEF-4 far projection):** Opt-in preserve-projection can recover IR-saturated X/Y from qualified unshifted MACs. A new intact-word tag requires validated GTE dataflow; modified words, half writes, cache fallback, lm sign clamps and accumulator/SZ overflow keep rejection. Guest registers and the faithful default are unchanged. R4 maximum-range visual/performance acceptance remains pending.
- **2026-10-21 (BRIEF-4 production follow-up):** Remove production-only PGXP diagnostic receipt copies and reuse the FLS-owned Windows sleep timer fix from `2ca004ba`. The PGXP quiet repeat reduces p99 busy from 70.6% to 50.5%, but two >25ms completion gaps remain. Only failing acceptance runs are repeated under the owner minimum-testing rule.
+215
View File
@@ -0,0 +1,215 @@
/* Exact indexed-page decode cache. Included after TEX_FS and batch helpers.
* Array layers contain native 1555 words, not filtered/display colours. */
#ifndef GL_TEXTURE_2D_ARRAY
#define GL_TEXTURE_2D_ARRAY 0x8C1A
#endif
typedef void (APIENTRY *TcImage3D)(GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei,
GLint, GLenum, GLenum, const void *);
typedef void (APIENTRY *TcAttachLayer)(GLenum, GLenum, GLuint, GLint, GLint);
#define TC_CAP 256
typedef struct {
GLuint source, palette;
int x, y, depth, cx, cy, valid, pinned;
uint64_t stamp, used, checked_epoch;
int next;
} TexelCacheEntry;
static TexelCacheEntry s_tc[TC_CAP];
static uint64_t s_tc_tiles[32][64], s_tc_epoch, s_tc_clock;
static uint64_t s_tc_hits, s_tc_misses, s_tc_evictions;
static GLuint s_tc_array, s_tc_fbo, s_tc_prog;
static GLint s_tc_page, s_tc_clut, s_tc_depth;
static int s_tc_ready = -1, s_tc_capacity = TC_CAP;
static TcAttachLayer s_tc_attach;
static int s_tc_pins[TC_CAP], s_tc_pin_n;
static int s_tc_heads[1024], s_tc_last = -1;
static int texel_cache_lookup_fast(void) {
static int enabled = -1;
if (enabled < 0) {
const char *e = getenv("PSX_GL_TEXEL_LOOKUP_FAST");
enabled = !(e && e[0] == '0');
}
return enabled;
}
static unsigned texel_cache_hash(GLuint source, GLuint palette, int x, int y, int depth, int cx, int cy) {
uint32_t h = source * 0x9e3779b9u ^ palette * 0x85ebca6bu;
h ^= (uint32_t)x * 73856093u ^ (uint32_t)y * 19349663u;
h ^= (uint32_t)cx * 83492791u ^ (uint32_t)cy * 2654435761u ^ (uint32_t)depth;
h ^= h >> 16;
return h & 1023u;
}
static int texel_cache_matches(const TexelCacheEntry *c, GLuint source, GLuint palette,
int x, int y, int depth, int cx, int cy) {
return c->valid && c->source==source && c->palette==palette && c->x==x && c->y==y &&
c->depth==depth && c->cx==cx && c->cy==cy;
}
static void texel_cache_pin(int slot) {
if (!s_tc[slot].pinned) { s_tc[slot].pinned=1; s_tc_pins[s_tc_pin_n++]=slot; }
}
static void texel_cache_unpin(void) {
for (int i=0; i<s_tc_pin_n; ++i) s_tc[s_tc_pins[i]].pinned=0;
s_tc_pin_n=0;
}
static void texel_cache_dirty(int x0, int y0, int x1, int y1) {
if (s_tc_ready != 1) return;
if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0;
if (x1 > 1023) x1 = 1023; if (y1 > 511) y1 = 511;
if (x1 < x0 || y1 < y0) return;
uint64_t stamp = ++s_tc_epoch;
for (int y=y0/16; y<=y1/16; ++y)
for (int x=x0/16; x<=x1/16; ++x) s_tc_tiles[y][x] = stamp;
}
static uint64_t texel_cache_region(int x, int y, int w, int h) {
uint64_t stamp = 0;
for (int ty=y/16; ty<=(y+h-1)/16; ++ty)
for (int tx=x/16; tx<=(x+w-1)/16; ++tx) {
uint64_t v = s_tc_tiles[ty & 31][tx & 63];
if (v > stamp) stamp = v;
}
return stamp;
}
static void texel_cache_reset(void) {
memset(s_tc, 0, sizeof s_tc);
memset(s_tc_heads, 0, sizeof s_tc_heads);
s_tc_last = -1;
memset(s_tc_tiles, 0, sizeof s_tc_tiles);
s_tc_epoch = s_tc_clock = s_tc_hits = s_tc_misses = s_tc_evictions = 0;
s_tc_array = s_tc_fbo = s_tc_prog = 0;
s_tc_ready = -1;
s_tc_pin_n = 0;
}
static int texel_cache_init(void) {
if (s_tc_ready >= 0) return s_tc_ready;
s_tc_ready = 0;
const char *e = getenv("PSX_GL_TEXEL_CACHE");
if (e && e[0] == '0') return 0;
e = getenv("PSX_GL_TEXEL_CACHE_LAYERS");
s_tc_capacity = e ? atoi(e) : TC_CAP;
if (s_tc_capacity < 4) s_tc_capacity = 4;
if (s_tc_capacity > TC_CAP) s_tc_capacity = TC_CAP;
TcImage3D image = (TcImage3D)SDL_GL_GetProcAddress("glTexImage3D");
s_tc_attach = (TcAttachLayer)SDL_GL_GetProcAddress("glFramebufferTextureLayer");
if (!image || !s_tc_attach) return 0;
/* Reuse the literal native address/decode functions from TEX_FS. The
* decoder has no window or UV bounds: fetch_texel applies those before
* it samples the decoded layer. This avoids a second decode definition. */
const char *helpers = strstr(TEX_FS, "int vram_at(");
const char *fetch = strstr(TEX_FS, "int fetch_texel(");
const char *native = strstr(TEX_FS, " if (v_depth == 0)");
const char *end = strstr(TEX_FS, "vec3 col5(");
if (!helpers || !fetch || !native || !end) return 0;
char *fs = malloc(strlen(TEX_FS) + 512);
if (!fs) return 0;
snprintf(fs, strlen(TEX_FS) + 512,
"#version 330\nuniform usampler2D u_vram, u_palette;\n"
"uniform ivec2 v_tpage, v_clut; uniform int v_depth;\n"
"layout(location=0) out uint decoded_word;\n%.*s"
"int decode_native(int u,int v){\n%.*s"
"void main(){decoded_word=uint(decode_native(int(gl_FragCoord.x),int(gl_FragCoord.y)));}\n",
(int)(fetch-helpers), helpers, (int)(end-native), native);
s_tc_prog = build_program(PRESENT_VS, fs);
free(fs);
if (!s_tc_prog) return 0;
glGenTextures(1, &s_tc_array);
p_glActiveTexture(PSXGL_TEXTURE0 + 3);
glBindTexture(GL_TEXTURE_2D_ARRAY, s_tc_array);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
p_glBindBuffer(PSXGL_PIXEL_UNPACK_BUFFER, 0);
image(GL_TEXTURE_2D_ARRAY, 0, PSXGL_R16UI, 256, 256, s_tc_capacity, 0,
PSXGL_RED_INTEGER, GL_UNSIGNED_SHORT, NULL);
p_glActiveTexture(PSXGL_TEXTURE0);
if (glGetError() != GL_NO_ERROR) return 0;
p_glGenFramebuffers(1, &s_tc_fbo);
s_tc_page = p_glGetUniformLocation(s_tc_prog, "v_tpage");
s_tc_clut = p_glGetUniformLocation(s_tc_prog, "v_clut");
s_tc_depth = p_glGetUniformLocation(s_tc_prog, "v_depth");
p_glUseProgram(s_tc_prog);
p_glUniform1i(p_glGetUniformLocation(s_tc_prog, "u_vram"), 0);
p_glUniform1i(p_glGetUniformLocation(s_tc_prog, "u_palette"), 1);
s_tc_ready = 1;
return 1;
}
static void texel_cache_bind(void) {
p_glActiveTexture(PSXGL_TEXTURE0 + 3);
glBindTexture(GL_TEXTURE_2D_ARRAY, s_tc_array);
p_glActiveTexture(PSXGL_TEXTURE0);
}
static int texel_cache_get(GLuint source, GLuint palette, int x, int y, int depth, int cx, int cy) {
if (depth >= 2 || source != s_raw_tex || palette != s_raw_tex ||
!texel_cache_init()) return -1;
const int fast = texel_cache_lookup_fast();
const unsigned bucket = texel_cache_hash(source,palette,x,y,depth,cx,cy);
if (fast) {
int hit = s_tc_last;
if (hit < 0 || !texel_cache_matches(&s_tc[hit],source,palette,x,y,depth,cx,cy)) {
hit = s_tc_heads[bucket] - 1;
while (hit >= 0 && !texel_cache_matches(&s_tc[hit],source,palette,x,y,depth,cx,cy))
hit = s_tc[hit].next - 1;
}
if (hit >= 0) {
TexelCacheEntry *c = &s_tc[hit];
if (c->checked_epoch != s_tc_epoch) {
uint64_t stamp = source == s_raw_tex ? texel_cache_region(x,y,depth ? 128 : 64,256) : 0;
uint64_t ps = palette == s_raw_tex ? texel_cache_region(cx,cy,depth ? 256 : 16,1) : 0;
if (ps > stamp) stamp = ps;
if (c->stamp == stamp) c->checked_epoch = s_tc_epoch;
}
if (c->checked_epoch == s_tc_epoch) {
c->used=++s_tc_clock; texel_cache_pin(hit); s_tc_last=hit; s_tc_hits++; return hit;
}
}
}
uint64_t stamp = source == s_raw_tex ? texel_cache_region(x,y,depth ? 128 : 64,256) : 0;
uint64_t ps = palette == s_raw_tex ? texel_cache_region(cx,cy,depth ? 256 : 16,1) : 0;
if (ps > stamp) stamp = ps;
int slot = -1;
uint64_t oldest = UINT64_MAX;
for (int i=0; i<s_tc_capacity; ++i) {
TexelCacheEntry *c = &s_tc[i];
if (!fast && texel_cache_matches(c,source,palette,x,y,depth,cx,cy)) {
if (c->stamp == stamp) { c->used=++s_tc_clock; texel_cache_pin(i); s_tc_hits++; return i; }
}
if (!c->valid) { if (oldest != 0) { slot=i; oldest=0; } }
else if (!c->pinned && c->used < oldest) { oldest=c->used; slot=i; }
}
s_tc_misses++;
if (slot < 0) return -1; /* All layers pinned: preserve the original batch. */
if (s_tc[slot].valid) {
/* Only layers without deferred CPU draw references can be reused.
* GPU readers were already submitted, so command ordering protects
* them. Never split a mask-checked batch to make cache space. */
s_tc_evictions++;
}
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, s_tc_fbo);
s_tc_attach(PSXGL_FRAMEBUFFER, PSXGL_COLOR_ATTACHMENT0, s_tc_array, 0, slot);
if (p_glCheckFramebufferStatus(PSXGL_FRAMEBUFFER) != PSXGL_FRAMEBUFFER_COMPLETE) return -1;
glViewport(0,0,256,256);
glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDisable(GL_STENCIL_TEST);
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
p_glUseProgram(s_tc_prog);
p_glUniform2i(s_tc_page,x,y); p_glUniform2i(s_tc_clut,cx,cy); p_glUniform1i(s_tc_depth,depth);
p_glActiveTexture(PSXGL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,source);
p_glActiveTexture(PSXGL_TEXTURE0+1); glBindTexture(GL_TEXTURE_2D,palette);
p_glActiveTexture(PSXGL_TEXTURE0+3); glBindTexture(GL_TEXTURE_2D_ARRAY,0);
p_glBindVertexArray(s_empty_vao); glDrawArrays(GL_TRIANGLES,0,3);
p_glBindFramebuffer(PSXGL_FRAMEBUFFER,s_hr_fbo);
p_glActiveTexture(PSXGL_TEXTURE0);
if (s_tc[slot].valid) {
TexelCacheEntry *c = &s_tc[slot];
unsigned old = texel_cache_hash(c->source,c->palette,c->x,c->y,c->depth,c->cx,c->cy);
int *link = &s_tc_heads[old];
while (*link && *link != slot+1) link = &s_tc[*link-1].next;
if (*link) *link = c->next;
}
s_tc[slot]=(TexelCacheEntry){.source=source,.palette=palette,.x=x,.y=y,.depth=depth,
.cx=cx,.cy=cy,.valid=1,.stamp=stamp,.used=++s_tc_clock,
.checked_epoch=s_tc_epoch,.next=s_tc_heads[bucket]};
s_tc_heads[bucket]=slot+1;
s_tc_last=slot;
texel_cache_pin(slot);
return slot;
}
+26 -1
View File
@@ -799,6 +799,10 @@ static GLuint s_hr_tex = 0, s_hr_fbo = 0, s_hr_rb = 0;
static int s_hr_alloc = 0;
/* Native raw-1555 sampling mirror + readback source. */
static GLuint s_raw_tex = 0, s_raw_fbo = 0;
static void texel_cache_dirty(int x0, int y0, int x1, int y1);
static void texel_cache_reset(void);
static void texel_cache_bind(void);
static void texel_cache_unpin(void);
/* CPU->VRAM upload staging (native RGBA8). */
static GLuint s_up_tex = 0;
/* copy_rect staging (hr-sized RGBA8). */
@@ -1423,6 +1427,8 @@ static void present_target_quad(GLuint tex, float tex_w, float tex_h,
int apply_gamma, int src_scale);
static void coh_record(int kind, int x0, int y0, int x1, int y1) {
if (kind == GL_COH_PACK || kind == GL_COH_FLUSH || kind == GL_COH_UPLOAD)
texel_cache_dirty(x0,y0,x1,y1);
GlCohEvent *e = &s_coh_ring[s_coh_seq % GL_COH_RING_CAP];
e->frame = (uint32_t)s_frame_count;
e->kind = (uint8_t)kind;
@@ -2002,6 +2008,7 @@ static const char *TEX_FS =
"uniform sampler2D u_hd_texture;\n"
"uniform usampler2D u_vram;\n"
"uniform usampler2D u_palette;\n"
"uniform usampler2DArray u_decoded;\n"
"uniform int u_semipass; /* 0=all texels, 1=STP=0 only, 2=STP=1 only */\n"
"uniform int u_semimode; /* PS1 blend mode; drives dual-source factors */\n"
"uniform int u_maskset; /* GP0(E6h) set-mask: OR bit15 into output */\n"
@@ -2029,6 +2036,7 @@ static const char *TEX_FS =
" u = clamp(u, v_limits.x, v_limits.z);\n"
" v = clamp(v, v_limits.y, v_limits.w);\n"
" }\n"
" if (v_depth >= 4) return int(texelFetch(u_decoded,ivec3(u,v,v_depth-4),0).r);\n"
" if (v_depth == 0) {\n"
" int px = vram_at(v_tpage.x + (u >> 2), v_tpage.y + v);\n"
" return palette_at(v_clut.x + ((px >> ((u & 3) * 4)) & 0xF), v_clut.y);\n"
@@ -3675,7 +3683,9 @@ static void bind_textured_resources(GLuint source, GLuint palette, GLuint hd) {
p_glActiveTexture(PSXGL_TEXTURE0 + 2);
glBindTexture(GL_TEXTURE_2D, hd ? hd : s_up_tex); p_glUniform1i(s_uHdTexture, 2);
p_glActiveTexture(PSXGL_TEXTURE0);
texel_cache_bind();
}
#include "gl_texel_cache.inc"
static int s_tb_twin[4] = {0, 0, 0, 0};
static uint64_t s_batch_total = 0, s_batch_reason[8];
@@ -4211,6 +4221,7 @@ static void hiw_flush_queue(void) {
}
}
hiw_flush_tail();
if (!s_tb_n && !s_hq_n) texel_cache_unpin();
}
/* The native-wide mirrors (both modes), then the canonical projection back
@@ -4289,6 +4300,7 @@ static void flush_tex_batch_impl(void) {
}
hr_end();
if (--s_cw_flush_depth == 0) s_cw_flush_ms += cw_ms() - cw_t0;
if (!s_tb_n && !s_hq_n) texel_cache_unpin();
}
/* Flat / gouraud GEO batch — MotK title/char-select starfields issue ~30k/s
@@ -4980,6 +4992,13 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
flush_tex_batch();
}
if (s_tb_n + 3 > TEXBATCH_MAXV) { s_batch_reason[6]++; flush_tex_batch(); }
const int cache_bounds = (twx | twy) ||
(lim[0] >= 0 && lim[1] >= 0 && lim[2] <= 255 && lim[3] <= 255 &&
lim[0] <= lim[2] && lim[1] <= lim[3]);
int decoded = hd_tex || !cache_bounds ? -1 : texel_cache_get(
s_selected_bank_tex ? s_selected_bank_tex : s_raw_tex,
s_selected_bank_tex && !s_selected_bank_live_clut ? s_selected_bank_tex : s_raw_tex,
base_x,base_y,depth,clut_x,clut_y);
if (s_tb_n == 0) { /* opening a batch: capture its keyed state */
s_tb_semi = batch_semi; s_tb_mask = s_mask_set; s_tb_filter = filter; s_tb_gate = gate;
s_tb_depth = tdmode;
@@ -5000,7 +5019,8 @@ static void gpu_textured_triangle(const int *xs, const int *ys,
vp[4] = col[i*3+0]; vp[5] = col[i*3+1]; vp[6] = col[i*3+2]; vp[7] = 1.0f;
vp[8] = (float)base_x; vp[9] = (float)base_y; /* a_tpage */
vp[10] = (float)clut_x; vp[11] = (float)clut_y; /* a_clut */
vp[12] = (float)depth; vp[13] = (float)rawtex; /* a_depth, a_raw */
vp[12] = (float)(decoded < 0 ? depth : decoded + 4);
vp[13] = (float)rawtex; /* a_depth, a_raw */
vp[14] = (float)lim[0]; vp[15] = (float)lim[1]; /* a_limits */
vp[16] = (float)lim[2]; vp[17] = (float)lim[3];
vp[18] = (semi >= 0 ? (float)(semi + 1) : 0.0f) /* a_semi code */
@@ -5725,6 +5745,7 @@ static void depth24_clear_skipped_fb(void) {
glViewport(0, 0, VRAM_W, VRAM_H);
glScissor(x0, y0, rw, rh);
glClear(GL_COLOR_BUFFER_BIT);
texel_cache_dirty(x0,y0,x1,y1);
}
glDisable(GL_SCISSOR_TEST);
p_glBindFramebuffer(PSXGL_FRAMEBUFFER, 0);
@@ -6221,6 +6242,7 @@ static int init_gpu_raster(void) {
s_tex_uBcls = p_glGetUniformLocation(s_tex_prog, "u_bcls");
p_glUseProgram(s_tex_prog);
p_glUniform1i(p_glGetUniformLocation(s_tex_prog, "u_coverage_fast"), coverage_fast_enabled());
p_glUniform1i(p_glGetUniformLocation(s_tex_prog, "u_decoded"), 3);
s_geo_uBcls = p_glGetUniformLocation(s_geo_prog, "u_bcls");
s_geo_uBadd = p_glGetUniformLocation(s_geo_prog, "u_badd");
s_uBlitSrc = p_glGetUniformLocation(s_blit_prog, "u_src");
@@ -6542,6 +6564,7 @@ int gl_renderer_select_texture_bank_live_clut(uint16_t id) {
int gl_renderer_init_context(SDL_Window *win) {
s_win = win;
texel_cache_reset();
s_window_owner = window_thread_id();
#if defined(PSX_SDL3)
if (s_drawable_watch) SDL_RemoveEventWatch(drawable_event_watch, NULL);
@@ -8631,6 +8654,7 @@ static void pass_journal_rollback(void) {
pass_blit(j->hr_fbo, s_hr_fbo, 0, 0, x * S, y * S, w * S, h * S,
GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
pass_blit(j->raw_fbo, s_raw_fbo, 0, 0, x, y, w, h, GL_COLOR_BUFFER_BIT);
texel_cache_dirty(x,y,x+w-1,y+h-1);
}
render_pass_journal_rollback(&s_pj_cpu, s_vram, VRAM_W);
}
@@ -8807,6 +8831,7 @@ static void transaction_restore_ex(int keep_color) {
s_pass_w * S, s_pass_h * S, hr_bits);
pass_blit(s_pb_raw_fbo, s_raw_fbo, 0, 0, s_pass_x, s_pass_y, s_pass_w,
s_pass_h, GL_COLOR_BUFFER_BIT);
texel_cache_dirty(s_pass_x,s_pass_y,s_pass_x+s_pass_w-1,s_pass_y+s_pass_h-1);
if (s_pb_wide_src && s_pb_wide_src == pass_wide_fbo_for(s_pass_x)) {
pass_blit(s_pb_wide_fbo, s_pb_wide_src, 0, 0, 0, s_pass_y * S,
g_wide_w * S, s_pass_h * S, hr_bits);
+6
View File
@@ -1368,6 +1368,7 @@ extern "C" int32_t gte_nclip_exact_sign(int32_t native_mac0, uint32_t pc) {
const bool usable = s_nclip_exact_enabled && s_nclip_last_precise_valid &&
native_mac0 == s_nclip_last_native;
const int32_t sign = usable ? s_nclip_last_precise_sign : native;
#ifndef PSX_NO_DEBUG_TOOLS
if (!s_gte_replay_sandbox && s_speculative_depth == 0) {
if (GteNclipSiteStat *e = nclip_find(s_nclip_site, pc,
&GteNclipSiteStat::pc,
@@ -1378,6 +1379,9 @@ extern "C" int32_t gte_nclip_exact_sign(int32_t native_mac0, uint32_t pc) {
e->last_frame = (uint32_t)s_frame_count;
}
}
#else
(void)pc;
#endif
return sign;
}
extern "C" int gte_nclip_native_wide_sign(int32_t native_mac0, int* sign) {
@@ -1487,6 +1491,7 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
const bool disagree = (cross > 0 && out <= 0) || (cross < 0 && out >= 0);
if (disagree)
s_nclip_disagreements++;
#ifndef PSX_NO_DEBUG_TOOLS
/* Last guest store before this NCLIP: codegen stamps it on every
* store, so it lands inside the emitter that issued the NCLIP (the
* function stamp is not maintained for static code). */
@@ -1502,6 +1507,7 @@ void gte_nclip(GTEState* gte, uint32_t instr) {
e->saturated++;
e->last_frame = (uint32_t)s_frame_count;
}
#endif
} else if (gpu_ws_precise_nclip_enabled() &&
!s_gte_replay_sandbox && s_speculative_depth == 0) {
s_nclip_fallbacks++;