Pipeline keys: the write mask and blend bit of colour targets a normal pass lacks leave the key too, and prewarm rows are normalized when read (general, from MacNativeGames)
pso_key_norm (66abbc3) drops the blend factors of targets that do not blend. It now also clears, outside a merged pass, the write mask and blend-enable bit of a colour target the pass has no format for: pso_desc reads them only for targets with a format, and they keep whatever earlier draws left. A merged pass keeps them, as its blend / mask index logical targets through lmap. MacNativeGames f3e2f66 found 35% of a Tears of the Kingdom prewarm list's render pipelines (8,479 of 24,146) differing only in such state, each compiled again.
pso_key_norm moves to nvn_pso_skey.h with the merged flag, and pso_skey_parse normalizes, so a prewarm list recorded before gives one key per pipeline; the prewarm takes each parsed key once (the line check alone let such rows through as different lines). t_pso_skey: keys round-trip normalized, two keys differing only in unused blend / mask state parse equal, a merged pass keeps the mask and blend bits. t_pso_archive gives every target a format, so its normalization checks still test the blend rule alone.
This commit is contained in:
1 parent
be5b197640
commit
2ef0a17e15
4 files changed
+37
-12
No files matched your search
@@ -4564,12 +4564,6 @@ static id<MTLRenderPipelineState> pso_build(MTLRenderPipelineDescriptor *d, NSEr
|
||||
if (ps) g_pb_spec++;
|
||||
return ps;
|
||||
}
|
||||
// What no pipeline depends on leaves the key: the blend state of targets whose blending is off (pso_desc reads blend[l] only where blend_en has bit l). Breath of the Wild recorded
|
||||
// 4,829 keys for 1,982 different pipelines, 2,845 of them told apart by such blend state alone. The depth / stencil formats stay: a Metal 4 base names none (pbase_key leaves them
|
||||
// out, so keys that differ only there cost one specialization each), but a pipeline built through the MTLDevice must name the pass's
|
||||
static void pso_key_norm(pso_key *k) {
|
||||
for (int i = 0; i < 8; i++) if (!(k->blend_en >> i & 1)) memset(&k->blend[i], 0, sizeof k->blend[i]);
|
||||
}
|
||||
// Pipeline key: current state S + pass formats (normal pass; a merged pass additionally carries lmap / specialized function, filled in by pso_get)
|
||||
static void pso_key_fill(pso_key *k, NvShader *vs, NvShader *fs, const uint32_t cfmt[8], uint32_t dfmt, uint32_t sfmt, uint32_t samples, int lmap) {
|
||||
memset(k, 0, sizeof *k);
|
||||
@@ -4579,7 +4573,7 @@ static void pso_key_fill(pso_key *k, NvShader *vs, NvShader *fs, const uint32_t
|
||||
for (int i = 0; i < 8; i++) if (k->cfmt[i] || lmap) k->blend[i] = S.blend[i];
|
||||
const nvn_refl *rf = &vs->rf;
|
||||
for (int v = 0; v < rf->nv; v++) { int a = rf->v[v].nvn; if (a < 16 && a < S.nattr) { k->attr[a] = S.attr[a]; int st = S.attr[a].stream; if (st < 16) k->stream[st] = S.stream[st]; } }
|
||||
pso_key_norm(k);
|
||||
pso_key_norm(k, lmap);
|
||||
}
|
||||
// ---- Parallel precompile: pipelines met for the first time used to be compiled synchronously one by one on the submit thread; 9 misses in a row in one frame = 284 ms (heavy scene on a real device).
|
||||
// After satisfying the first pipeline miss in a submission, scan ahead once along the remaining commands (replaying state only, no drawing) and kick a bounded number of normal-pass pipelines to the background;
|
||||
@@ -4701,6 +4695,7 @@ static void warm_worker(NSArray<NSString *> *paths) {
|
||||
@autoreleasepool {
|
||||
NSMutableDictionary<NSString *, NvShader *> *dict = [NSMutableDictionary dictionary];
|
||||
NSMutableSet<NSString *> *seen = [NSMutableSet set];
|
||||
NSMutableSet<NSData *> *seen_k = [NSMutableSet set]; // rows recorded before pso_key_norm that are one pipeline now
|
||||
for (NSString *path in paths) {
|
||||
NSString *txt = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
|
||||
if (!txt) { MLOG("[RED] cannot read prewarm list: %s", path.UTF8String); continue; }
|
||||
@@ -4718,6 +4713,8 @@ static void warm_worker(NSArray<NSString *> *paths) {
|
||||
warm_enqueue(item); g_warm_shaders++;
|
||||
} else if ([c[0] isEqualToString:@"R"] && c.count >= 2) {
|
||||
pso_skey sk; if (pso_skey_parse(c[1].UTF8String, &sk)) continue;
|
||||
NSData *kd = [NSData dataWithBytes:&sk length:sizeof sk]; if ([seen_k containsObject:kd]) continue;
|
||||
[seen_k addObject:kd];
|
||||
NSString *vkey = warm_key(sk.vs, sizeof sk.vs), *fkey = warm_key(sk.fs, sizeof sk.fs);
|
||||
NvShader *vs = vkey.length ? warm_shader(dict, vkey, sk.vs_stage) : nil;
|
||||
NvShader *fs = fkey.length ? warm_shader(dict, fkey, sk.fs_stage) : nil;
|
||||
@@ -4727,7 +4724,7 @@ static void warm_worker(NSArray<NSString *> *paths) {
|
||||
if (sk.fz_desc.kind == 1 && fs) { int fx[32]; for (int i = 0; i < 32; i++) fx[i] = sk.fz_desc.fx[i]; fsfn = bf_fn(fs, fx, sk.fz_desc.fbmask, sk.k.lmap); }
|
||||
if (!fsfn) { g_warm_skip_fz++; continue; }
|
||||
}
|
||||
pso_key k = sk.k; k.vs = (__bridge void *)vs; k.fs = (__bridge void *)fs; k.fsfn = (__bridge void *)fsfn; pso_key_norm(&k); // rows written before the key was normalized
|
||||
pso_key k = sk.k; k.vs = (__bridge void *)vs; k.fs = (__bridge void *)fs; k.fsfn = (__bridge void *)fsfn; // pso_skey_parse normalized rows written before the key was
|
||||
NSError *e = nil; id ps = pso_build(pso_desc(&k, vs, fs, fsfn, sk.merged), &e);
|
||||
if (!ps && !vs->layer_out && [e.localizedDescription containsString:@"render_target_array_index"]) { // a vertex shader that writes the layer (see pso_get_)
|
||||
vs->layer_out = 1; e = nil; ps = pso_build(pso_desc(&k, vs, fs, fsfn, sk.merged), &e); }
|
||||
|
||||
@@ -16,6 +16,19 @@ typedef struct {
|
||||
|
||||
typedef struct { char vs[24], fs[24]; uint8_t vs_stage, fs_stage, merged, fz; pso_fz_desc fz_desc; pso_key k; } pso_skey;
|
||||
|
||||
// What no pipeline depends on leaves the key, so one pipeline has one key: a target's blend factors when it does not blend (pso_desc reads blend[l] only where blend_en has
|
||||
// bit l) and, outside a merged pass (whose blend / mask index logical targets through lmap), the write mask and blend bit of a colour target the pass does not have. They
|
||||
// keep whatever earlier draws left. Breath of the Wild recorded 4,829 keys for 1,982 different pipelines, 2,845 of them told apart by such blend state alone; MacNativeGames
|
||||
// f3e2f66 found 35% of a Tears of the Kingdom prewarm list's render pipelines (8,479 of 24,146) differing only in it, each compiled again. The depth / stencil formats stay:
|
||||
// a Metal 4 base names none (pbase_key leaves them out, so keys that differ only there cost one specialization each), but a pipeline built through the MTLDevice must name
|
||||
// the pass's. pso_key_fill makes keys so, and pso_skey_parse reads rows recorded before so
|
||||
static inline void pso_key_norm(pso_key *k, int merged) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (!merged && !k->cfmt[i]) { k->blend_en &= ~(1u << i); k->chmask &= ~(0xfu << (4 * i)); }
|
||||
if (!(k->blend_en >> i & 1)) memset(&k->blend[i], 0, sizeof k->blend[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void pso_skey_from(const pso_key *k, NvShader *vs, NvShader *fs, int merged, const pso_fz_desc *fz, pso_skey *out) {
|
||||
memset(out, 0, sizeof *out);
|
||||
memcpy(&out->k, k, sizeof out->k);
|
||||
@@ -44,5 +57,6 @@ static inline int pso_skey_parse(const char *hex, pso_skey *out) {
|
||||
if (a < 0 || b < 0) return -1;
|
||||
p[i / 2] = (uint8_t)(a << 4 | b);
|
||||
}
|
||||
pso_key_norm(&out->k, out->merged);
|
||||
return 0;
|
||||
}
|
||||
@@ -206,8 +206,8 @@ int main(void) { @autoreleasepool {
|
||||
concurrent_bases(1);
|
||||
// key normalization
|
||||
pso_key k; memset(&k, 0, sizeof k); k.blend_en = 0x5; k.dfmt = MTLPixelFormatDepth32Float; k.sfmt = MTLPixelFormatStencil8;
|
||||
for (int i = 0; i < 8; i++) { k.blend[i].src_rgb = 5; k.blend[i].dst_rgb = 6; }
|
||||
pso_key_norm(&k);
|
||||
for (int i = 0; i < 8; i++) { k.cfmt[i] = MTLPixelFormatRGBA8Unorm; k.blend[i].src_rgb = 5; k.blend[i].dst_rgb = 6; } // every target present: only the blend rule acts
|
||||
pso_key_norm(&k, 0);
|
||||
EXPECT("blend of an enabled target kept", 5, k.blend[0].src_rgb);
|
||||
EXPECT("blend of an enabled target kept (2)", 6, k.blend[2].dst_rgb);
|
||||
EXPECT("blend of a disabled target dropped", 0, k.blend[1].src_rgb);
|
||||
|
||||
@@ -15,7 +15,7 @@ static void fill(pso_key *k, unsigned seed) {
|
||||
|
||||
int main(void) {
|
||||
for (unsigned seed = 1; seed <= 3; seed++) {
|
||||
pso_key k; fill(&k, seed);
|
||||
pso_key k; fill(&k, seed); pso_key_norm(&k, seed & 1); // keys are made normalized (pso_key_fill), and a parse normalizes
|
||||
NvShader *vs = [NvShader new], *fs = [NvShader new];
|
||||
vs->stage = (int)seed; fs->stage = (int)(seed + 1);
|
||||
snprintf(vs->key, sizeof vs->key, "vs-%u", seed); snprintf(fs->key, sizeof fs->key, "fs-%u", seed);
|
||||
@@ -27,6 +27,20 @@ int main(void) {
|
||||
hex[0] = hex[0] == '0' ? '1' : '0'; assert(pso_skey_parse(hex, &b) == 0); assert(memcmp(&a, &b, sizeof a) != 0);
|
||||
assert(pso_skey_parse(hex + 1, &b) == -1); hex[0] = 'g'; assert(pso_skey_parse(hex, &b) == -1);
|
||||
}
|
||||
puts("PSO_SKEY OK 3 cases round-trip; one changed character differs, missing digits / illegal characters rejected");
|
||||
// pso_key_norm: blend factors of a target that does not blend, and the mask / blend bit of a target a normal pass lacks, are not part of the key; a merged pass keeps
|
||||
// the mask and blend bits (they index logical targets through lmap)
|
||||
{
|
||||
NvShader *vs = [NvShader new]; snprintf(vs->key, sizeof vs->key, "vs-n");
|
||||
pso_key k1; memset(&k1, 0, sizeof k1); k1.cfmt[0] = 80; k1.cfmt[1] = 80; k1.blend_en = 1; k1.chmask = 0xff;
|
||||
k1.blend[0].src_rgb = 1; k1.blend[0].dst_rgb = 2;
|
||||
pso_key k2 = k1; k2.blend[1].src_rgb = 7; k2.blend[3].dst_a = 5; k2.blend_en |= 1u << 5; k2.chmask |= 0xfu << 20;
|
||||
pso_skey a, b; char ha[sizeof a * 2 + 1], hb[sizeof b * 2 + 1];
|
||||
pso_skey_from(&k1, vs, nil, 0, NULL, &a); pso_skey_from(&k2, vs, nil, 0, NULL, &b); assert(memcmp(&a, &b, sizeof a) != 0);
|
||||
pso_skey_hex(&a, ha); pso_skey_hex(&b, hb); assert(pso_skey_parse(ha, &a) == 0 && pso_skey_parse(hb, &b) == 0);
|
||||
assert(memcmp(&a, &b, sizeof a) == 0 && a.k.blend[0].src_rgb == 1 && a.k.blend[0].dst_rgb == 2 && a.k.chmask == 0xff && a.k.blend_en == 1);
|
||||
pso_skey_from(&k2, vs, nil, 1, NULL, &b); pso_skey_hex(&b, hb); assert(pso_skey_parse(hb, &b) == 0);
|
||||
assert(b.k.chmask == (0xffu | 0xfu << 20) && b.k.blend_en == (1u | 1u << 5) && b.k.blend[1].src_rgb == 0 && b.k.blend[3].dst_a == 0);
|
||||
}
|
||||
puts("PSO_SKEY OK 3 cases round-trip; one changed character differs, missing digits / illegal characters rejected; unused blend and mask state left out of the key");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user