Skip to main content

cram_codec/
gpuwav3.rs

1//! `tc-gpu` v3 (stream magic `TCG3`): **one global** integer CDF 9/7
2//! transform over the whole field, with the coefficients entropy-coded in
3//! independently decodable **code-blocks** (JPEG2000-style).
4//!
5//! # Dependency rule
6//!
7//! - Subbands are decoded coarse → fine (LL, then levels `nlev−1 … 0`). A
8//!   code-block may read **any coefficient of a coarser level** (its
9//!   parent band: the parent and the parent's 6 face neighbours) — those are
10//!   decoded first — and, inside its own region, every earlier line in
11//!   raster order plus its own earlier samples on the line. Nothing outside
12//!   the block at its own level is visible, so all blocks of one level decode
13//!   in parallel (one rANS lane each: the parallelism is the block count).
14//! - Contexts use the *residuals* `q − pred` of the visible cells
15//!   (optional, `resctx`), predictions use the values.
16//!
17//! # Random access
18//!
19//! A sub-box needs, at every level, the coefficients in the support of the
20//! integer synthesis lifting (±3 samples per half-band per level, a
21//! conservative bound for the 4-step 9/7 lifting), plus — for decoding — the
22//! parent cells their blocks condition on. Only those code-blocks (and the
23//! outlier tiles touching the box) are read; the inverse lifting runs only
24//! on the lines inside the needed region at each level.
25
26use crate::bitio::{ByteReader, ByteWriter, DecodeError};
27use crate::codec::ErrorBound;
28use crate::gpuwav::{
29    code_res, dequant, feats, for_lines, gamma, init_of, lift_consts, prior_table, rate_control,
30    read_priors, row_ctx, to_f32_bits, write_priors, BandInfo, CountSink, DecSink, Enc, GpuStats,
31    RansDec, RecSink, Shape, Sink, MINIT, NDL, NMODEL, NOMODEL, NTAP,
32};
33use crate::lmodel3::{code_learned_ad, feat_vec3, AdCfg, Adapt, Mix3, Net32};
34
35/// Stream magic.
36pub const MAGIC3: &[u8; 4] = b"TCG3";
37
38/// v3 options.
39#[derive(Clone, Debug)]
40pub struct Gpu3Config {
41    /// Error target.
42    pub bound: ErrorBound,
43    /// Code-block edge for 3D data (in coefficients).
44    pub block: usize,
45    /// Code-block edge for 2D data.
46    pub block2d: usize,
47    /// Outlier-tile edge (3D; 2D tiles are `4×` this).
48    pub tile: usize,
49    /// Residual-based contexts.
50    pub resctx: bool,
51    /// Deadzone rounding offset.
52    pub rho: f64,
53    /// rANS lanes per code-block: 1 = raster order (best ratio); more
54    /// splits a block's lines into parity passes dealt to the lanes (more
55    /// GPU parallelism, TCG1's rule inside the block).
56    pub lanes: usize,
57    /// Coefficient probability model.
58    pub model: Model3,
59    /// Compact lanes: a multi-lane block of `v` coefficients uses
60    /// `clamp(8·v·lanes / V, 1, lanes)` lanes, `V` the full block volume
61    /// (small coarse-level blocks stop paying `lanes` lanes of rANS framing,
62    /// while no lane runs longer than 1/8 of a full block's lane), and the
63    /// last lane's length is implied by the block size.
64    pub compact: bool,
65    /// Axis-grouped line taps ([`crate::gpuwav::BandInfo::ax`]).
66    pub axis: bool,
67    /// Per-lane adaptive probability maps over the learned model
68    /// ([`crate::lmodel3::Adapt`]); ignored by static models.
69    pub adapt: Option<AdCfg>,
70}
71
72/// Probability model of a v3 stream.
73#[derive(Clone, Debug, Default)]
74pub enum Model3 {
75    /// Static per-context priors (hand contexts).
76    #[default]
77    Static,
78    /// A pretrained integer MLP, referenced by fingerprint (register it with
79    /// [`crate::learned::register`] before decoding).
80    Pretrained(std::sync::Arc<crate::learned::Net>),
81    /// An MLP trained at encode time; its weights are shipped in the stream.
82    Overfit {
83        /// Hidden widths.
84        hidden: Vec<usize>,
85        /// Samples per epoch.
86        per_epoch: usize,
87        /// Epochs.
88        epochs: usize,
89    },
90}
91
92impl Gpu3Config {
93    /// Defaults: 32³ / 128² blocks, 64³ outlier tiles, residual contexts.
94    pub fn new(bound: ErrorBound) -> Self {
95        Self {
96            bound,
97            block: 32,
98            block2d: 128,
99            tile: 64,
100            resctx: true,
101            rho: 0.45,
102            lanes: 1,
103            model: Model3::Static,
104            compact: true,
105            axis: true,
106            adapt: Some(ad_default()),
107        }
108    }
109}
110
111/// Predictor key of a code-block band (pass 0; passes add `p`).
112pub fn key3(level: usize, mask: u8) -> usize {
113    let l = if mask == 0 { 15 } else { level.min(14) };
114    (l * 8 + mask as usize) * 8
115}
116
117/// Compact streams store predictor taps in units of `2^TAPQ` (Q12 → Q8).
118const TAPQ: u32 = 4;
119
120/// Cells per lane of compact-lane streams (0 = not compact).
121fn lane_cells(cfg: &Gpu3Config, cb: [usize; 3]) -> usize {
122    if cfg.compact && cfg.lanes > 1 {
123        // a coarse block's per-lane chain stays ≤ 1/8 of a full block's, so
124        // the coarse levels (decoded one after another) add little latency
125        (cb.iter().product::<usize>() / (8 * cfg.lanes))
126            .max(1)
127            .next_power_of_two()
128    } else {
129        0
130    }
131}
132
133/// Code-blocks of a shape in coding order.
134pub fn blocks_of(
135    sh: &Shape,
136    cb: [usize; 3],
137    lanes: usize,
138    lane_cells: usize,
139    axis: bool,
140) -> (Vec<BandInfo>, Vec<usize>) {
141    let mut v = Vec::new();
142    let mut band = Vec::new();
143    for (bi, b) in sh.bands.iter().enumerate() {
144        let pb = &sh.p.bands[bi];
145        for z in (b.start[0]..b.end[0]).step_by(cb[0]) {
146            for y in (b.start[1]..b.end[1]).step_by(cb[1]) {
147                for x in (b.start[2]..b.end[2]).step_by(cb[2]) {
148                    let mut k = b.clone();
149                    k.start = [z, y, x];
150                    k.end = [
151                        (z + cb[0]).min(b.end[0]),
152                        (y + cb[1]).min(b.end[1]),
153                        (x + cb[2]).min(b.end[2]),
154                    ];
155                    k.org = b.start;
156                    k.ax = axis;
157                    let base = key3(pb.level, pb.mask);
158                    if lanes <= 1 {
159                        k.raster = true;
160                        k.npass = 1;
161                        k.pkey = [base; 8];
162                    } else {
163                        k.raster = false;
164                        k.npass = crate::gpuwav::npass_of(k.start, k.end);
165                        k.pkey = std::array::from_fn(|p| base + p);
166                        k.nl = k
167                            .end
168                            .iter()
169                            .zip(&k.start)
170                            .map(|(e, s)| e - s)
171                            .product::<usize>()
172                            .checked_div(lane_cells)
173                            .map_or(lanes, |v| v.clamp(1, lanes));
174                    }
175                    v.push(k);
176                    band.push(bi);
177                }
178            }
179        }
180    }
181    (v, band)
182}
183
184/// Code one block (one lane, static models). `rs` receives residuals.
185#[allow(clippy::too_many_arguments)]
186fn code_block<S: Sink>(
187    sh: &Shape,
188    b: &BandInfo,
189    q: &mut [i32],
190    mut rs: Option<&mut [i32]>,
191    preds: &[[i32; NTAP]],
192    m: &mut [u32],
193    lanes: usize,
194    net: Option<&Net32>,
195    adc: AdCfg,
196    s: &mut S,
197    mut ir: Option<&mut IrCtx>,
198) {
199    let [_, ny, nx] = sh.dims;
200    // TC_CPU_IR: evaluate the net with the tang-expr integer interpreter
201    let mut prog = net.filter(|_| cpu_ir()).map(|n| n.program());
202    let mut ad = vec![Adapt::new(adc); lanes.max(1)];
203    for_lines(b, lanes.max(1), |p, lane, z, y| {
204        let w = &preds[b.pkey[p]];
205        let rc = row_ctx(sh, b, p, z, y);
206        for x in b.start[2]..b.end[2] {
207            let (f, pred) = feats(sh, b, &rc, q, rs.as_deref(), z, y, x, w);
208            let i = (z * ny + y) * nx + x;
209            let r = match (net, ir.as_deref_mut()) {
210                // decoding through tang-expr graphs (shipped or TC_CPU_IRRES)
211                (None, Some(c)) => c.res.res_static(
212                    &mut c.lanes[lane],
213                    c.mt,
214                    c.sw,
215                    f.sig as u32,
216                    f.e as u32,
217                    b.class as u32,
218                    f.sctx as u32,
219                ),
220                (Some(n), Some(c)) => {
221                    let x = feat_vec3(&f, b.class);
222                    let o = match c.mlp.as_mut() {
223                        Some(p) => n.infer_ir(p, &x),
224                        None => n.infer(&x),
225                    };
226                    c.lres
227                        .res_learned(&mut c.lanes[lane], c.mt, c.sw, &o, &mut c.adt[lane])
228                }
229                (None, None) => {
230                    code_res::<S, false>(s, lane, m, b.class, &f, q[i].wrapping_sub(pred))
231                }
232                (Some(n), None) => {
233                    let x = feat_vec3(&f, b.class);
234                    let o = match prog.as_mut() {
235                        Some(p) => n.infer_ir(p, &x),
236                        None => n.infer(&x),
237                    };
238                    let mx = Mix3::new(&o);
239                    code_learned_ad(s, lane, &mx, q[i].wrapping_sub(pred), &mut ad[lane])
240                }
241            };
242            q[i] = r.wrapping_add(pred);
243            if let Some(rs) = rs.as_deref_mut() {
244                rs[i] = r;
245            }
246        }
247    });
248}
249
250/// Default adaptive layer (`TC_AD=wa,lim,mask` overrides, for sweeps).
251pub fn ad_default() -> AdCfg {
252    static V: std::sync::OnceLock<AdCfg> = std::sync::OnceLock::new();
253    *V.get_or_init(|| {
254        let v: Vec<u32> = std::env::var("TC_AD")
255            .unwrap_or("10,14,15".into())
256            .split(',')
257            .map(|x| x.parse().unwrap())
258            .collect();
259        AdCfg {
260            wa: v[0].min(16),
261            lim: v[1].min(255),
262            mask: v[2] & 15,
263        }
264    })
265}
266
267const AD_OFF: AdCfg = AdCfg {
268    wa: 0,
269    lim: 0,
270    mask: 0,
271};
272
273fn ad_pack(a: AdCfg) -> u64 {
274    (a.wa | a.lim << 5 | a.mask << 13) as u64
275}
276
277fn ad_unpack(v: u64) -> AdCfg {
278    AdCfg {
279        wa: (v & 31).min(16) as u32,
280        lim: (v >> 5 & 255) as u32,
281        mask: (v >> 13 & 15) as u32,
282    }
283}
284
285/// CPU decoding state for tang-expr residual graphs.
286pub(crate) struct IrCtx<'a> {
287    res: crate::irdec::CpuRes,
288    lres: crate::irdec::CpuRes,
289    mlp: Option<tang_expr::int::Program>,
290    lanes: Vec<crate::irdec::Lane>,
291    /// Per-lane adaptive state (fresh per block).
292    adt: Vec<[u32; crate::lmodel3::AC * crate::lmodel3::AB]>,
293    mt: &'a [u32],
294    sw: &'a [u32],
295}
296
297/// Per-decode buffers shared by the blocks.
298struct IrBase {
299    dec: std::sync::Arc<crate::shipdec::Decoder>,
300    mt: Vec<u32>,
301    off: [u32; 2],
302    sw: Vec<u32>,
303}
304
305impl IrBase {
306    fn new(ps: &Parsed3, bytes: &[u8]) -> Option<Self> {
307        let dec = ps
308            .dec
309            .clone()
310            .or_else(|| cpu_irres().then(builtin_decoder))?;
311        let (mt, off) = crate::irdec::cpu_mt(&ps.init);
312        Some(Self {
313            dec,
314            mt,
315            off,
316            sw: crate::irdec::stream_words(bytes),
317        })
318    }
319}
320
321fn builtin_decoder() -> std::sync::Arc<crate::shipdec::Decoder> {
322    static D: std::sync::OnceLock<std::sync::Arc<crate::shipdec::Decoder>> =
323        std::sync::OnceLock::new();
324    D.get_or_init(|| {
325        std::sync::Arc::new(
326            crate::shipdec::compile(&crate::shipdec::builtin_program(None, false)).unwrap(),
327        )
328    })
329    .clone()
330}
331
332fn cpu_irres() -> bool {
333    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
334    *V.get_or_init(|| std::env::var("TC_CPU_IRRES").is_ok())
335}
336
337fn cpu_ir() -> bool {
338    static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
339    *V.get_or_init(|| std::env::var("TC_CPU_IR").is_ok())
340}
341
342fn block_dims(d3: [usize; 3], cfg_block: usize, block2d: usize) -> [usize; 3] {
343    match d3.iter().filter(|&&d| d > 1).count() {
344        3 => [cfg_block; 3],
345        2 => [1, block2d, block2d],
346        _ => [1, 1, block2d * block2d],
347    }
348}
349
350fn tile_dims(d3: [usize; 3], t: usize) -> [usize; 3] {
351    match d3.iter().filter(|&&d| d > 1).count() {
352        3 => [t; 3],
353        2 => [1, 4 * t, 4 * t],
354        _ => [1, 1, 64 * t * t],
355    }
356}
357
358fn grid(d3: [usize; 3], bd: [usize; 3]) -> Vec<([usize; 3], [usize; 3])> {
359    let mut v = Vec::new();
360    for z in (0..d3[0]).step_by(bd[0]) {
361        for y in (0..d3[1]).step_by(bd[1]) {
362            for x in (0..d3[2]).step_by(bd[2]) {
363                let o = [z, y, x];
364                v.push((o, [0, 1, 2].map(|a| bd[a].min(d3[a] - o[a]))));
365            }
366        }
367    }
368    v
369}
370
371/// Static priors with a cost-based header: a context gets a transmitted
372/// probability only when it saves more than ~12 bits over `p = ½`.
373fn choose_priors3(n: &[[u32; 2]]) -> Vec<Option<u8>> {
374    let t = prior_table();
375    n.iter()
376        .map(|&[n0, n1]| {
377            if n0 + n1 == 0 {
378                return None;
379            }
380            let cost = |p: u32| {
381                let p = p as f64 / 65536.0;
382                -(n1 as f64) * p.log2() - (n0 as f64) * (1.0 - p).log2()
383            };
384            let k =
385                (0..64u8).min_by(|&a, &b| cost(t[a as usize]).total_cmp(&cost(t[b as usize])))?;
386            if cost(32768) - cost(t[k as usize]) > 12.0 {
387                Some(k)
388            } else {
389                None
390            }
391        })
392        .collect()
393}
394
395/// LS predictor fit per key over the blocks.
396fn fit3(sh: &Shape, blocks: &[BandInfo], q: &[i32], tq: u32) -> Vec<Option<[i32; NTAP]>> {
397    let nkey = crate::gpuwav::NKEY;
398    let mut g = vec![[[0.0f64; NTAP]; NTAP]; nkey];
399    let mut h = vec![[0.0f64; NTAP]; nkey];
400    let mut n = vec![0usize; nkey];
401    let mut obs: Vec<Vec<([i32; NTAP], i32)>> = vec![Vec::new(); nkey];
402    let zero = [0i32; NTAP];
403    let [_, ny, nx] = sh.dims;
404    for b in blocks {
405        for_lines(b, 1, |p, _, z, y| {
406            let k = b.pkey[p];
407            let rc = row_ctx(sh, b, p, z, y);
408            for x in b.start[2]..b.end[2] {
409                let (f, _) = feats(sh, b, &rc, q, None, z, y, x, &zero);
410                let v = q[(z * ny + y) * nx + x];
411                if v == 0 && f.taps.iter().all(|&t| t == 0) {
412                    continue;
413                }
414                n[k] += 1;
415                for i in 0..NTAP {
416                    h[k][i] += f.taps[i] as f64 * v as f64;
417                    for j in 0..NTAP {
418                        g[k][i][j] += f.taps[i] as f64 * f.taps[j] as f64;
419                    }
420                }
421                if obs[k].len() < 200_000 {
422                    obs[k].push((f.taps, v));
423                }
424            }
425        });
426    }
427    (0..nkey)
428        .map(|k| {
429            if n[k] < 64 {
430                return None;
431            }
432            let mut a = g[k];
433            let mut r = h[k];
434            for i in 0..NTAP {
435                a[i][i] += 1e-3 * (g[k][i][i] + 1.0);
436            }
437            for c in 0..NTAP {
438                let piv = a[c][c];
439                if piv.abs() < 1e-12 {
440                    continue;
441                }
442                for rr in 0..NTAP {
443                    if rr != c {
444                        let f = a[rr][c] / piv;
445                        for kk in 0..NTAP {
446                            a[rr][kk] -= f * a[c][kk];
447                        }
448                        r[rr] -= f * r[c];
449                    }
450                }
451            }
452            let mut w = [0i32; NTAP];
453            for i in 0..NTAP {
454                let wi = if a[i][i].abs() > 1e-12 {
455                    r[i] / a[i][i]
456                } else {
457                    0.0
458                };
459                w[i] = ((wi * 4096.0 / (1 << tq) as f64)
460                    .round()
461                    .clamp(-(32767 >> tq) as f64, (32767 >> tq) as f64)
462                    as i32)
463                    << tq;
464            }
465            let (mut c0, mut c1) = (0.0f64, 0.0f64);
466            for &(t, v) in &obs[k] {
467                let mut acc = 0i32;
468                for i in 0..NTAP {
469                    acc = acc.wrapping_add(w[i].wrapping_mul(t[i]));
470                }
471                let pr = acc.wrapping_add(2048) >> 12;
472                c0 += (1.0 + v.unsigned_abs() as f64).log2();
473                c1 += (1.0 + v.wrapping_sub(pr).unsigned_abs() as f64).log2();
474            }
475            if c1 < 0.98 * c0 && c0 - c1 > 64.0 {
476                Some(w)
477            } else {
478                None
479            }
480        })
481        .collect()
482}
483
484fn used_keys3(blocks: &[BandInfo]) -> Vec<usize> {
485    let mut k: Vec<usize> = blocks
486        .iter()
487        .flat_map(|b| b.pkey[..b.npass].to_vec())
488        .collect();
489    k.sort_unstable();
490    k.dedup();
491    k
492}
493
494fn words_le(v: &[u16], w: &mut ByteWriter) {
495    for &x in v {
496        w.bytes(&x.to_le_bytes());
497    }
498}
499
500/// `(features, residual)` of every coefficient in coding order (strided down
501/// to at most `max` samples).
502fn collect3(
503    sh: &Shape,
504    blocks: &[BandInfo],
505    q: &mut [i32],
506    resctx: bool,
507    preds: &[[i32; NTAP]],
508    lanes: usize,
509    max: usize,
510) -> Vec<crate::learned::Sample> {
511    let n: usize = q.len();
512    let stride = n.div_ceil(max.max(1)).max(1);
513    let mut rs = vec![0i32; n];
514    let mut out = Vec::new();
515    let [_, ny, nx] = sh.dims;
516    let mut k = 0usize;
517    for b in blocks {
518        for_lines(b, lanes.max(1), |p, _, z, y| {
519            let rc = row_ctx(sh, b, p, z, y);
520            for x in b.start[2]..b.end[2] {
521                let (f, pred) = feats(
522                    sh,
523                    b,
524                    &rc,
525                    q,
526                    resctx.then_some(&rs[..]),
527                    z,
528                    y,
529                    x,
530                    &preds[b.pkey[p]],
531                );
532                let i = (z * ny + y) * nx + x;
533                let r = q[i].wrapping_sub(pred);
534                rs[i] = r;
535                if k.is_multiple_of(stride) {
536                    out.push(crate::learned::Sample {
537                        x: feat_vec3(&f, b.class),
538                        r,
539                    });
540                }
541                k += 1;
542            }
543        });
544    }
545    out
546}
547
548/// Training samples for a field at a PSNR/RMSE target (the coefficients the
549/// v3 coder would see at the rate-controlled step).
550pub fn samples3(
551    x: &[f32],
552    dims: &[usize],
553    cfg: &Gpu3Config,
554    threads: usize,
555    max: usize,
556) -> Vec<crate::learned::Sample> {
557    let big = dims.iter().copied().max().unwrap_or(1).max(2) * 2;
558    let got = std::sync::Mutex::new(Vec::new());
559    rate_control(x, dims, big, cfg.bound, cfg.rho, threads, |enc, dg, _| {
560        let shapes = enc.shapes_at(dg);
561        let sh = &shapes[0];
562        let (qs, _) = enc.quantize(dg, cfg.rho);
563        let cb = block_dims(sh.dims, cfg.block, cfg.block2d);
564        let lc = lane_cells(cfg, cb);
565        let (blocks, _) = blocks_of(sh, cb, cfg.lanes, lc, cfg.axis);
566        let fitted = fit3(sh, &blocks, &qs[0], if lc > 0 { TAPQ } else { 0 });
567        let preds: Vec<[i32; NTAP]> = fitted.iter().map(|p| p.unwrap_or([0; NTAP])).collect();
568        let mut q = qs[0].clone();
569        *got.lock().unwrap() = collect3(sh, &blocks, &mut q, cfg.resctx, &preds, cfg.lanes, max);
570        (Vec::new(), GpuStats::default())
571    });
572    got.into_inner().unwrap()
573}
574
575/// Encode as a v3 stream.
576pub fn encode3_f32(
577    x: &[f32],
578    dims: &[usize],
579    cfg: &Gpu3Config,
580    threads: usize,
581) -> (Vec<u8>, GpuStats) {
582    // a brick larger than every axis: Enc then holds the global transform
583    let big = dims.iter().copied().max().unwrap_or(1).max(2) * 2;
584    rate_control(x, dims, big, cfg.bound, cfg.rho, threads, |enc, dg, mx| {
585        encode_at3(enc, dims, cfg, dg, mx)
586    })
587}
588
589fn encode_at3(
590    enc: &Enc,
591    dims: &[usize],
592    cfg: &Gpu3Config,
593    dg: f64,
594    maxabs: Option<f64>,
595) -> (Vec<u8>, GpuStats) {
596    let shapes = enc.shapes_at(dg);
597    assert_eq!(shapes.len(), 1);
598    let sh = &shapes[0];
599    let d3 = sh.dims;
600    let (qs, dl) = enc.quantize(dg, cfg.rho);
601    let cb = block_dims(d3, cfg.block, cfg.block2d);
602    let lc = lane_cells(cfg, cb);
603    let (blocks, _) = blocks_of(sh, cb, cfg.lanes, lc, cfg.axis);
604    let fitted = if std::env::var("TC_NOPRED").is_ok() {
605        vec![None; crate::gpuwav::NKEY]
606    } else {
607        fit3(sh, &blocks, &qs[0], if lc > 0 { TAPQ } else { 0 })
608    };
609    let preds: Vec<[i32; NTAP]> = fitted.iter().map(|p| p.unwrap_or([0; NTAP])).collect();
610    let mut cs = enc.reconstruct(&shapes, &qs, &dl);
611    let scale = enc.scale();
612    let g = 2f64.powi(enc.e);
613    let corr = maxabs
614        .map(|b| ((2.0 * b / g).floor() as i64).clamp(0, 1 << 30) as i32)
615        .unwrap_or(0);
616    let td = tile_dims(d3, cfg.tile);
617    let tiles = grid(d3, td);
618    let n = enc.x.len();
619    // corrections + escapes, per outlier tile (global indices)
620    let mut outl: Vec<Vec<(usize, i32)>> = vec![Vec::new(); tiles.len()];
621    let mut escs: Vec<Vec<(usize, u32)>> = vec![Vec::new(); tiles.len()];
622    if let Some(b) = maxabs {
623        let c = &mut cs[0];
624        for (ti, (o, td)) in tiles.iter().enumerate() {
625            for z in 0..td[0] {
626                for y in 0..td[1] {
627                    for xx in 0..td[2] {
628                        let li = (z * td[1] + y) * td[2] + xx;
629                        let gi = ((o[0] + z) * d3[1] + o[1] + y) * d3[2] + o[2] + xx;
630                        let xv = enc.x[gi];
631                        let yv =
632                            f32::from_bits(to_f32_bits(c[gi].wrapping_add(enc.mean_fix), scale))
633                                as f64;
634                        let r = xv as f64 - yv;
635                        if r.abs() > b && corr > 0 {
636                            let k = (r / g / corr as f64)
637                                .round()
638                                .clamp(-(1 << 20) as f64, (1 << 20) as f64)
639                                as i32;
640                            if k != 0 {
641                                c[gi] = c[gi].wrapping_add(k.wrapping_mul(corr));
642                                outl[ti].push((li, k));
643                            }
644                        }
645                        let yv =
646                            f32::from_bits(to_f32_bits(c[gi].wrapping_add(enc.mean_fix), scale))
647                                as f64;
648                        if (xv as f64 - yv).abs() > b {
649                            escs[ti].push((li, xv.to_bits()));
650                        }
651                    }
652                }
653            }
654        }
655    }
656    // priors: counts over every block decision (sequential: contexts need
657    // the residuals of coarser blocks)
658    let mut q = qs[0].clone();
659    let mut rs = vec![0i32; n];
660    let mut cnt = CountSink {
661        n: vec![[0; 2]; NMODEL],
662    };
663    let mut m0 = vec![MINIT; NMODEL];
664    for b in &blocks {
665        code_block(
666            sh,
667            b,
668            &mut q,
669            cfg.resctx.then_some(&mut rs[..]),
670            &preds,
671            &mut m0,
672            cfg.lanes,
673            None,
674            AD_OFF,
675            &mut cnt,
676            None,
677        );
678    }
679    // learned model (overfit: trained here on this field's coefficients)
680    let mut q2 = qs[0].clone();
681    let (net, net_bytes): (Option<Net32>, Vec<u8>) = match &cfg.model {
682        Model3::Static => (None, vec![0]),
683        Model3::Pretrained(n) => {
684            let mut v = vec![1];
685            v.extend(n.fingerprint().to_le_bytes());
686            (Some(Net32::new(n)), v)
687        }
688        Model3::Overfit {
689            hidden,
690            per_epoch,
691            epochs,
692        } => {
693            let smp = collect3(
694                sh, &blocks, &mut q2, cfg.resctx, &preds, cfg.lanes, 2_000_000,
695            );
696            let tc = crate::learned::TrainConfig {
697                hidden: hidden.clone(),
698                epochs: *epochs,
699                per_epoch: *per_epoch,
700                batch: 256,
701                lr: 4e-3,
702                apm: false,
703                seed: 1,
704            };
705            let n = crate::learned::train(&smp, &tc);
706            let mut v = vec![2];
707            let nb = n.to_bytes();
708            v.extend((nb.len() as u32).to_le_bytes());
709            v.extend(nb);
710            (Some(Net32::new(&n)), v)
711        }
712    };
713    let adc = if net.is_some() {
714        cfg.adapt.unwrap_or(AD_OFF)
715    } else {
716        AD_OFF
717    };
718    let priors = if net.is_some() {
719        vec![None; NMODEL]
720    } else {
721        choose_priors3(&cnt.n)
722    };
723    let mut init = init_of(&priors);
724    let mut bw: Vec<Vec<u8>> = Vec::with_capacity(blocks.len());
725    let dbg = std::env::var("TC_STATS").is_ok();
726    let (mut ideal, mut nlanes, mut wbytes) = (0f64, 0usize, 0usize);
727    for b in &blocks {
728        let nl = cfg.lanes.max(1);
729        let mut sink = RecSink {
730            syms: vec![Vec::new(); nl],
731        };
732        code_block(
733            sh,
734            b,
735            &mut q,
736            cfg.resctx.then_some(&mut rs[..]),
737            &preds,
738            &mut init,
739            nl,
740            net.as_ref(),
741            adc,
742            &mut sink,
743            None,
744        );
745        let lw: Vec<Vec<u16>> = sink
746            .syms
747            .iter()
748            .map(|s| crate::gpuwav::rans_encode(s))
749            .collect();
750        if dbg {
751            ideal += sink
752                .syms
753                .iter()
754                .flatten()
755                .map(|&(_, f)| 16.0 - (f as f64).log2())
756                .sum::<f64>();
757            nlanes += lw.iter().filter(|l| !l.is_empty()).count();
758            wbytes += 2 * lw.iter().map(|l| l.len()).sum::<usize>();
759        }
760        let mut w = ByteWriter::new();
761        if nl > 1 {
762            let used = if lc > 0 { b.nl } else { nl };
763            debug_assert!(lw[used..].iter().all(|l| l.is_empty()));
764            let nv = if lc > 0 { used - 1 } else { used };
765            let mut prev = 0i64;
766            for l in &lw[..nv] {
767                if lc > 0 {
768                    // compact: zig-zag delta from the previous lane (lanes
769                    // get rows round-robin, so their lengths are close)
770                    w.svarint(l.len() as i64 - prev);
771                    prev = l.len() as i64;
772                } else {
773                    w.varint(l.len() as u64);
774                }
775            }
776            if w.buf.len() % 2 == 1 {
777                w.u8(0);
778            }
779        }
780        for l in &lw {
781            words_le(l, &mut w);
782        }
783        bw.push(w.buf);
784    }
785    // outlier tiles
786    let mut tp: Vec<Vec<u8>> = Vec::with_capacity(tiles.len());
787    for ti in 0..tiles.len() {
788        let mut sink = RecSink {
789            syms: vec![Vec::new()],
790        };
791        if maxabs.is_some() {
792            let mut om = vec![MINIT; NOMODEL];
793            gamma::<_, true>(&mut sink, 0, &mut om, 0, outl[ti].len() as u32);
794            let mut last: i64 = -1;
795            for &(i, k) in &outl[ti] {
796                gamma::<_, true>(&mut sink, 0, &mut om, 32, (i as i64 - last - 1) as u32);
797                last = i as i64;
798                sink.raw(0, (k < 0) as u32, 1);
799                gamma::<_, true>(&mut sink, 0, &mut om, 64, k.unsigned_abs() - 1);
800            }
801        }
802        let words = crate::gpuwav::rans_encode(&sink.syms[0]);
803        let mut w = ByteWriter::new();
804        w.varint(words.len() as u64);
805        w.varint(escs[ti].len() as u64);
806        let mut last = 0usize;
807        for (j, &(i, bits)) in escs[ti].iter().enumerate() {
808            w.varint(if j == 0 { i } else { i - last } as u64);
809            last = i;
810            w.bytes(&bits.to_le_bytes());
811        }
812        if w.buf.len() % 2 == 1 {
813            w.u8(0);
814        }
815        words_le(&words, &mut w);
816        tp.push(w.buf);
817    }
818    // header
819    let mut w = ByteWriter::new();
820    w.bytes(MAGIC3);
821    w.varint(dims.len() as u64);
822    for &d in dims {
823        w.varint(d as u64);
824    }
825    for a in 0..3 {
826        w.varint(cb[a] as u64);
827    }
828    for a in 0..3 {
829        w.varint(td[a] as u64);
830    }
831    w.u8(cfg.resctx as u8);
832    let has_ad = adc.wa > 0;
833    if lc > 0 || cfg.axis || has_ad {
834        w.varint(
835            (cfg.lanes.max(1) | 1 << 9 | (cfg.axis as usize) << 10 | (has_ad as usize) << 11)
836                as u64,
837        );
838        w.u8(if lc > 0 {
839            lc.trailing_zeros() as u8 + 1
840        } else {
841            0
842        });
843        if has_ad {
844            w.varint(ad_pack(adc));
845        }
846    } else {
847        w.varint(cfg.lanes.max(1) as u64);
848    }
849    w.bytes(&net_bytes);
850    w.svarint(enc.e as i64);
851    w.svarint(enc.mean_fix as i64);
852    w.f64(dg);
853    w.bytes(&dl);
854    w.varint(corr as u64);
855    let uk = used_keys3(&blocks);
856    let pred_at = w.buf.len();
857    if lc > 0 {
858        // compact layout: presence bitset, then the taps
859        for c in uk.chunks(8) {
860            w.u8(c
861                .iter()
862                .enumerate()
863                .map(|(i, &k)| (fitted[k].is_some() as u8) << i)
864                .sum());
865        }
866        for &k in &uk {
867            for &t in fitted[k].iter().flatten() {
868                w.svarint((t >> TAPQ) as i64);
869            }
870        }
871    } else {
872        for &k in &uk {
873            match fitted[k] {
874                Some(p) => {
875                    w.u8(1);
876                    for &t in &p {
877                        w.svarint(t as i64);
878                    }
879                }
880                None => w.u8(0),
881            }
882        }
883    }
884    let pred_bytes = w.buf.len() - pred_at;
885    write_priors(&mut w, &priors);
886    let hdr_fixed = w.buf.len();
887    for b in &bw {
888        w.varint(b.len() as u64);
889    }
890    for t in &tp {
891        w.varint(t.len() as u64);
892    }
893    if w.buf.len() % 2 == 1 {
894        w.u8(0);
895    }
896    let header_bytes = w.buf.len();
897    if dbg {
898        let bb: usize = bw.iter().map(|b| b.len()).sum();
899        eprintln!(
900            "STATS hdr_fixed {hdr_fixed} index {} blocks {} block_bytes {bb} ideal_bytes {:.0} pred_bytes {pred_bytes} nkeys {} nfit {} nonempty_lanes {nlanes} word_bytes {wbytes} tiles_bytes {}",
901            header_bytes - hdr_fixed,
902            blocks.len(),
903            ideal / 8.0,
904            uk.len(),
905            uk.iter().filter(|&&k| fitted[k].is_some()).count(),
906            tp.iter().map(|t| t.len()).sum::<usize>()
907        );
908    }
909    for b in &bw {
910        w.bytes(b);
911    }
912    for t in &tp {
913        w.bytes(t);
914    }
915    // stats from the exact output
916    let c = &cs[0];
917    let mut se = 0.0;
918    let mut mx = 0.0f64;
919    let mut esc_at: Vec<(usize, u32)> = Vec::new();
920    for (ti, (o, td)) in tiles.iter().enumerate() {
921        for &(li, bits) in &escs[ti] {
922            let (z, y, xx) = (li / (td[1] * td[2]), (li / td[2]) % td[1], li % td[2]);
923            esc_at.push((((o[0] + z) * d3[1] + o[1] + y) * d3[2] + o[2] + xx, bits));
924        }
925    }
926    esc_at.sort_unstable();
927    let mut ei = esc_at.iter().peekable();
928    for gi in 0..n {
929        let mut yv = f32::from_bits(to_f32_bits(c[gi].wrapping_add(enc.mean_fix), scale));
930        if ei.peek().is_some_and(|e| e.0 == gi) {
931            yv = f32::from_bits(ei.next().unwrap().1);
932        }
933        let e = enc.x[gi] as f64 - yv as f64;
934        se += e * e;
935        mx = mx.max(e.abs());
936    }
937    let st = GpuStats {
938        bytes: w.buf.len(),
939        bits_per_value: 8.0 * w.buf.len() as f64 / n as f64,
940        header_bytes,
941        index_bytes: header_bytes - hdr_fixed,
942        outlier_bytes: tp.iter().map(|t| t.len()).sum(),
943        n_outliers: outl.iter().map(|v| v.len()).sum(),
944        n_escapes: esc_at.len(),
945        rmse: (se / n as f64).sqrt(),
946        max_abs: mx,
947        delta: dg * g,
948    };
949    (w.buf, st)
950}
951
952// ------------------------------------------------------------------ parse
953
954/// An outlier tile.
955#[derive(Clone, Debug)]
956pub struct TileRef {
957    /// Origin.
958    pub origin: [usize; 3],
959    /// Extent.
960    pub dims: [usize; 3],
961    /// Word range `[lo, hi)` of its lane (absolute 16-bit word index).
962    pub words: (usize, usize),
963    /// Byte range of the whole tile payload.
964    pub bytes: (usize, usize),
965    /// Raw escapes `(tile-local index, f32 bits)`.
966    pub esc: Vec<(usize, u32)>,
967}
968
969/// A parsed v3 stream.
970#[derive(Clone, Debug)]
971pub struct Parsed3 {
972    /// Original dims.
973    pub dims: Vec<usize>,
974    /// 3D dims.
975    pub d3: [usize; 3],
976    /// Code-block extent.
977    pub cb: [usize; 3],
978    /// Residual contexts.
979    pub resctx: bool,
980    /// Grid exponent.
981    pub e: i32,
982    /// Mean on the grid.
983    pub mean_fix: i32,
984    /// Centroids.
985    pub dl: [u8; NDL],
986    /// Correction step.
987    pub corr: i32,
988    /// Predictors per key.
989    pub preds: Vec<[i32; NTAP]>,
990    /// Static model probabilities.
991    pub init: Vec<u32>,
992    /// The global shape (pyramid + bands with steps).
993    pub sh: Shape,
994    /// Code-blocks in coding order.
995    pub blocks: Vec<BandInfo>,
996    /// Band index of each block.
997    pub block_band: Vec<usize>,
998    /// Byte range of each block payload.
999    pub block_words: Vec<(usize, usize)>,
1000    /// Lanes per block.
1001    pub lanes: usize,
1002    /// Word ranges of each block's lanes.
1003    pub block_lanes: Vec<Vec<(usize, usize)>>,
1004    /// Outlier tiles.
1005    pub tiles: Vec<TileRef>,
1006    /// Learned model (None = static priors).
1007    pub net: Option<Net32>,
1008    /// Adaptive layer of the learned model.
1009    pub ad: AdCfg,
1010    /// Header + index bytes.
1011    pub header_bytes: usize,
1012    /// Decoder shipped in the stream (`TCS1`), replacing the built-in coding path.
1013    pub dec: Option<std::sync::Arc<crate::shipdec::Decoder>>,
1014}
1015
1016impl Parsed3 {
1017    /// Output scale.
1018    pub fn scale(&self) -> f32 {
1019        f32::from_bits(((self.e + 127) as u32) << 23)
1020    }
1021    /// Block index ranges per level, in decode order.
1022    pub fn level_groups(&self) -> Vec<(usize, usize)> {
1023        let mut v: Vec<(usize, usize)> = Vec::new();
1024        let mut cur = usize::MAX;
1025        for (i, &bi) in self.block_band.iter().enumerate() {
1026            let l = self.sh.p.bands[bi].level;
1027            if l != cur {
1028                v.push((i, i + 1));
1029                cur = l;
1030            } else {
1031                v.last_mut().unwrap().1 = i + 1;
1032            }
1033        }
1034        v
1035    }
1036}
1037
1038/// Parse a v3 stream, plain (`TCG3`) or carrying its decoder (`TCS1`, see
1039/// [`crate::shipdec`]). Offsets in the result index `bytes` either way.
1040pub fn parse3(bytes: &[u8]) -> Result<Parsed3, DecodeError> {
1041    let (dec, inner) = crate::shipdec::open(bytes)?;
1042    let Some(dec) = dec else {
1043        return parse3_inner(bytes, false);
1044    };
1045    let base = bytes.len() - inner.len();
1046    let mut ps = parse3_inner(inner, dec.mlp.is_some())?;
1047    for w in ps.block_words.iter_mut() {
1048        *w = (w.0 + base, w.1 + base);
1049    }
1050    for lv in ps.block_lanes.iter_mut() {
1051        for w in lv.iter_mut() {
1052            *w = (w.0 + base / 2, w.1 + base / 2);
1053        }
1054    }
1055    for t in ps.tiles.iter_mut() {
1056        t.words = (t.words.0 + base / 2, t.words.1 + base / 2);
1057        t.bytes = (t.bytes.0 + base, t.bytes.1 + base);
1058    }
1059    ps.header_bytes += base;
1060    ps.dec = Some(dec);
1061    Ok(ps)
1062}
1063
1064/// Parse a plain v3 stream; `ext_net`: the model is shipped as a graph, so
1065/// an unregistered fingerprint is not an error.
1066fn parse3_inner(bytes: &[u8], ext_net: bool) -> Result<Parsed3, DecodeError> {
1067    let mut r = ByteReader::new(bytes);
1068    if r.take(4)? != MAGIC3 {
1069        return Err(DecodeError::Invalid("not a tc-gpu v3 stream"));
1070    }
1071    let nd = r.varint()? as usize;
1072    if nd == 0 || nd > 3 {
1073        return Err(DecodeError::Invalid("rank"));
1074    }
1075    let dims: Vec<usize> = (0..nd)
1076        .map(|_| r.varint().map(|v| v as usize))
1077        .collect::<Result<_, _>>()?;
1078    let d3 = crate::wavelet::as3(&dims);
1079    let mut cb = [0usize; 3];
1080    let mut td = [0usize; 3];
1081    for v in cb.iter_mut().chain(td.iter_mut()) {
1082        *v = r.varint()? as usize;
1083        if *v == 0 {
1084            return Err(DecodeError::Invalid("block"));
1085        }
1086    }
1087    let resctx = r.u8()? != 0;
1088    let lv = r.varint()? as usize;
1089    let (lanes, lane_cells, axis, ad) = if lv & 1 << 9 != 0 {
1090        let t = r.u8()?;
1091        let ad = if lv & 1 << 11 != 0 {
1092            ad_unpack(r.varint()?)
1093        } else {
1094            AD_OFF
1095        };
1096        (
1097            lv & 511,
1098            if t > 0 { 1usize << (t - 1).min(30) } else { 0 },
1099            lv & 1 << 10 != 0,
1100            ad,
1101        )
1102    } else {
1103        (lv, 0, false, AD_OFF)
1104    };
1105    if lanes == 0 || lanes > 256 {
1106        return Err(DecodeError::Invalid("lanes"));
1107    }
1108    let net = match r.u8()? {
1109        0 => None,
1110        1 => {
1111            let b = r.take(8)?;
1112            let fp = u64::from_le_bytes(b.try_into().unwrap());
1113            match crate::learned::lookup(fp) {
1114                Some(n) => Some(Net32::new(&n)),
1115                None if ext_net => Some(crate::shipdec::placeholder_net()),
1116                None => return Err(DecodeError::Invalid("pretrained model not registered")),
1117            }
1118        }
1119        2 => {
1120            let b = r.take(4)?;
1121            let n = u32::from_le_bytes(b.try_into().unwrap()) as usize;
1122            Some(Net32::new(&crate::learned::Net::from_bytes(r.take(n)?)?))
1123        }
1124        _ => return Err(DecodeError::Invalid("model tag")),
1125    };
1126    let e = r.svarint()? as i32;
1127    let mean_fix = r.svarint()? as i32;
1128    let delta_g = r.f64()?;
1129    let mut dl = [0u8; NDL];
1130    dl.copy_from_slice(r.take(NDL)?);
1131    let corr = r.varint()? as i32;
1132    let mut sh = Shape::new(d3);
1133    sh.set_steps(delta_g);
1134    let (blocks, block_band) = blocks_of(&sh, cb, lanes, lane_cells, axis);
1135    let mut preds = vec![[0i32; NTAP]; crate::gpuwav::NKEY];
1136    let uk = used_keys3(&blocks);
1137    let has: Vec<bool> = if lane_cells > 0 {
1138        let bits: Vec<u8> = (0..uk.len().div_ceil(8))
1139            .map(|_| r.u8())
1140            .collect::<Result<_, _>>()?;
1141        (0..uk.len())
1142            .map(|i| bits[i / 8] >> (i % 8) & 1 != 0)
1143            .collect()
1144    } else {
1145        Vec::new()
1146    };
1147    for (i, &k) in uk.iter().enumerate() {
1148        if if lane_cells > 0 { has[i] } else { r.u8()? != 0 } {
1149            for t in preds[k].iter_mut() {
1150                let v = r.svarint()?;
1151                *t = if lane_cells > 0 {
1152                    (v.clamp(-32767 >> TAPQ, 32767 >> TAPQ) as i32) << TAPQ
1153                } else {
1154                    v as i32
1155                };
1156            }
1157        }
1158    }
1159    let init = read_priors(&mut r)?;
1160    let tg = grid(d3, td);
1161    let bsz: Vec<usize> = (0..blocks.len())
1162        .map(|_| r.varint().map(|v| v as usize))
1163        .collect::<Result<_, _>>()?;
1164    let tsz: Vec<usize> = (0..tg.len())
1165        .map(|_| r.varint().map(|v| v as usize))
1166        .collect::<Result<_, _>>()?;
1167    let header_bytes = r.position().next_multiple_of(2);
1168    let mut off = header_bytes;
1169    let mut block_words = Vec::with_capacity(blocks.len());
1170    let mut block_lanes = Vec::with_capacity(blocks.len());
1171    for (bi, &n) in bsz.iter().enumerate() {
1172        let end = off + n;
1173        if end > bytes.len() || n % 2 == 1 {
1174            return Err(DecodeError::Truncated);
1175        }
1176        let mut lv = Vec::with_capacity(lanes);
1177        if lanes == 1 {
1178            lv.push((off / 2, end / 2));
1179        } else {
1180            let mut br = ByteReader::new(&bytes[off..end]);
1181            let used = if lane_cells > 0 { blocks[bi].nl } else { lanes };
1182            let nv = if lane_cells > 0 { used - 1 } else { used };
1183            let mut nw = Vec::with_capacity(nv);
1184            let mut prev = 0i64;
1185            for _ in 0..nv {
1186                let k = if lane_cells > 0 {
1187                    prev + br.svarint()?
1188                } else {
1189                    br.varint()? as i64
1190                };
1191                if k < 0 {
1192                    return Err(DecodeError::Invalid("lane length"));
1193                }
1194                nw.push(k as usize);
1195                prev = k;
1196            }
1197            let mut w = (off + br.position().next_multiple_of(2)) / 2;
1198            for &k in &nw {
1199                lv.push((w, w + k));
1200                w += k;
1201            }
1202            if w * 2 > end {
1203                return Err(DecodeError::Truncated);
1204            }
1205            if nv < used {
1206                lv.push((w, end / 2));
1207                w = end / 2;
1208            }
1209            while lv.len() < lanes {
1210                lv.push((w, w));
1211            }
1212        }
1213        block_words.push((off, end));
1214        block_lanes.push(lv);
1215        off = end;
1216    }
1217    let mut tiles = Vec::with_capacity(tg.len());
1218    for (ti, (o, d)) in tg.iter().enumerate() {
1219        let end = off + tsz[ti];
1220        if end > bytes.len() {
1221            return Err(DecodeError::Truncated);
1222        }
1223        let mut br = ByteReader::new(&bytes[off..end]);
1224        let nw = br.varint()? as usize;
1225        let ne = br.varint()? as usize;
1226        let mut esc = Vec::with_capacity(ne);
1227        let mut last = 0;
1228        for i in 0..ne {
1229            let g = br.varint()? as usize;
1230            let idx = if i == 0 { g } else { last + g };
1231            last = idx;
1232            let b = br.take(4)?;
1233            esc.push((idx, u32::from_le_bytes([b[0], b[1], b[2], b[3]])));
1234        }
1235        let ws = (off + br.position().next_multiple_of(2)) / 2;
1236        if (ws + nw) * 2 > end {
1237            return Err(DecodeError::Truncated);
1238        }
1239        tiles.push(TileRef {
1240            origin: *o,
1241            dims: *d,
1242            words: (ws, ws + nw),
1243            bytes: (off, end),
1244            esc,
1245        });
1246        off = end;
1247    }
1248    Ok(Parsed3 {
1249        dims,
1250        d3,
1251        cb,
1252        resctx,
1253        e,
1254        mean_fix,
1255        dl,
1256        corr,
1257        preds,
1258        init,
1259        sh,
1260        blocks,
1261        block_band,
1262        block_words,
1263        lanes,
1264        block_lanes,
1265        tiles,
1266        header_bytes,
1267        net,
1268        ad,
1269        dec: None,
1270    })
1271}
1272
1273// ------------------------------------------------------------------ decode
1274
1275fn words(bytes: &[u8], a: usize, b: usize) -> Vec<u16> {
1276    (a..b)
1277        .map(|i| u16::from_le_bytes([bytes[2 * i], bytes[2 * i + 1]]))
1278        .collect()
1279}
1280
1281#[derive(Clone, Copy)]
1282struct Ptr(*mut i32);
1283unsafe impl Send for Ptr {}
1284unsafe impl Sync for Ptr {}
1285
1286impl Ptr {
1287    fn get(&self) -> *mut i32 {
1288        self.0
1289    }
1290}
1291
1292fn decode_block(
1293    ps: &Parsed3,
1294    bytes: &[u8],
1295    bi: usize,
1296    q: Ptr,
1297    rs: Ptr,
1298    n: usize,
1299    irb: Option<&IrBase>,
1300) -> Result<(), DecodeError> {
1301    if let Some(ib) = irb {
1302        let d = &ib.dec;
1303        let mut c = IrCtx {
1304            res: crate::irdec::CpuRes::new(&d.stat.0, &d.stat.1, false, [0, 0]),
1305            lres: if ps.ad.wa > 0 && ps.net.is_some() {
1306                crate::irdec::CpuRes::new_ad(&d.learned_ad.0, &d.learned_ad.1, ib.off, ps.ad)
1307            } else {
1308                crate::irdec::CpuRes::new(&d.learned.0, &d.learned.1, true, ib.off)
1309            },
1310            mlp: d.mlp_program(),
1311            lanes: ps.block_lanes[bi]
1312                .iter()
1313                .map(|&(a, b)| crate::irdec::Lane::new(&ib.sw, a as u32, b as u32))
1314                .collect(),
1315            adt: vec![crate::lmodel3::ad_init(); ps.lanes.max(1)],
1316            mt: &ib.mt,
1317            sw: &ib.sw,
1318        };
1319        let q = unsafe { std::slice::from_raw_parts_mut(q.get(), n) };
1320        let rs = unsafe { std::slice::from_raw_parts_mut(rs.get(), n) };
1321        let mut m = ps.init.clone();
1322        let mut sink = DecSink { lanes: Vec::new() };
1323        code_block(
1324            &ps.sh,
1325            &ps.blocks[bi],
1326            q,
1327            ps.resctx.then_some(rs),
1328            &ps.preds,
1329            &mut m,
1330            ps.lanes,
1331            ps.net.as_ref(),
1332            ps.ad,
1333            &mut sink,
1334            Some(&mut c),
1335        );
1336        if c.lanes.iter().any(|l| l.overrun()) {
1337            return Err(DecodeError::Truncated);
1338        }
1339        return Ok(());
1340    }
1341    let lw: Vec<Vec<u16>> = ps.block_lanes[bi]
1342        .iter()
1343        .map(|&(a, b)| words(bytes, a, b))
1344        .collect();
1345    let mut sink = DecSink {
1346        lanes: lw.iter().map(|w| RansDec::new(w)).collect(),
1347    };
1348    let mut m = ps.init.clone();
1349    // blocks write disjoint cells and read only coarser levels (decoded
1350    // before this level started) and their own cells
1351    let q = unsafe { std::slice::from_raw_parts_mut(q.get(), n) };
1352    let rs = unsafe { std::slice::from_raw_parts_mut(rs.get(), n) };
1353    code_block(
1354        &ps.sh,
1355        &ps.blocks[bi],
1356        q,
1357        ps.resctx.then_some(rs),
1358        &ps.preds,
1359        &mut m,
1360        ps.lanes,
1361        ps.net.as_ref(),
1362        ps.ad,
1363        &mut sink,
1364        None,
1365    );
1366    if sink.lanes.iter().any(|l| l.overrun()) {
1367        return Err(DecodeError::Truncated);
1368    }
1369    Ok(())
1370}
1371
1372fn run_par(
1373    items: &[usize],
1374    threads: usize,
1375    f: impl Fn(usize) -> Result<(), DecodeError> + Sync,
1376) -> Result<(), DecodeError> {
1377    use std::sync::atomic::{AtomicUsize, Ordering};
1378    use std::sync::Mutex;
1379    if threads <= 1 || items.len() <= 1 {
1380        for &i in items {
1381            f(i)?;
1382        }
1383        return Ok(());
1384    }
1385    let next = AtomicUsize::new(0);
1386    let err = Mutex::new(None);
1387    std::thread::scope(|s| {
1388        for _ in 0..threads.min(items.len()) {
1389            s.spawn(|| loop {
1390                let t = next.fetch_add(1, Ordering::Relaxed);
1391                if t >= items.len() {
1392                    break;
1393                }
1394                if let Err(e) = f(items[t]) {
1395                    *err.lock().unwrap() = Some(e);
1396                }
1397            });
1398        }
1399    });
1400    match err.into_inner().unwrap() {
1401        Some(e) => Err(e),
1402        None => Ok(()),
1403    }
1404}
1405
1406/// Half-open interval list per axis (a union of at most a few ranges).
1407type Need = [Vec<(usize, usize)>; 3];
1408
1409fn inside(v: &[(usize, usize)], i: usize) -> bool {
1410    v.iter().any(|&(a, b)| i >= a && i < b)
1411}
1412
1413fn hits(v: &[(usize, usize)], a: usize, b: usize) -> bool {
1414    v.iter().any(|&(c, d)| c < b && a < d)
1415}
1416
1417/// Per level: sample-domain need (`ext = lens[l]`) and coefficient-domain
1418/// need, for an output box `[lo, hi)`. Margin 3 per half-band per level.
1419fn needs(sh: &Shape, lo: [usize; 3], hi: [usize; 3]) -> (Vec<Need>, Vec<Need>) {
1420    let p = &sh.p;
1421    let mut samp: Vec<Need> = vec![[
1422        vec![(lo[0], hi[0])],
1423        vec![(lo[1], hi[1])],
1424        vec![(lo[2], hi[2])],
1425    ]];
1426    let mut coef: Vec<Need> = Vec::new();
1427    for l in 0..p.nlev() {
1428        let cur = samp[l].clone();
1429        let mut c: Need = [vec![], vec![], vec![]];
1430        let mut next: Need = [vec![], vec![], vec![]];
1431        for a in 0..3 {
1432            let n = p.lens[l][a];
1433            if !p.split[l][a] {
1434                c[a] = cur[a].clone();
1435                next[a] = cur[a].clone();
1436                continue;
1437            }
1438            let ns = n.div_ceil(2);
1439            let nd = n / 2;
1440            for &(x0, x1) in &cur[a] {
1441                let i0 = (x0 / 2).saturating_sub(3);
1442                let i1 = (x1.saturating_sub(1)) / 2 + 4;
1443                c[a].push((i0, i1.min(ns)));
1444                c[a].push((ns + i0.min(nd), ns + i1.min(nd)));
1445                next[a].push((i0, i1.min(ns)));
1446            }
1447        }
1448        coef.push(c);
1449        samp.push(next);
1450    }
1451    (samp, coef)
1452}
1453
1454/// Integer inverse on the whole field, optionally restricted to the lines
1455/// a box needs; lines are split over `threads`.
1456fn inverse_window(sh: &Shape, x: &mut [i32], win: Option<(&[Need], &[Need])>, threads: usize) {
1457    let p = &sh.p;
1458    let k = lift_consts();
1459    let st = [p.dims[1] * p.dims[2], p.dims[2], 1];
1460    let n = x.len();
1461    let xp = Ptr(x.as_mut_ptr());
1462    for l in (0..p.nlev()).rev() {
1463        let mut done = [false; 3];
1464        for a in [2usize, 1, 0] {
1465            if !p.split[l][a] {
1466                continue;
1467            }
1468            let ext = p.lens[l];
1469            let o: Vec<usize> = (0..3).filter(|&b| b != a).collect();
1470            let sel = |b: usize, i: usize| match win {
1471                None => true,
1472                Some((s, c)) => {
1473                    if done[b] {
1474                        inside(&s[l][b], i)
1475                    } else {
1476                        inside(&c[l][b], i)
1477                    }
1478                }
1479            };
1480            let (o0, o1) = (o[0], o[1]);
1481            let mut lines: Vec<usize> = Vec::new();
1482            for i in (0..ext[o0]).filter(|&i| sel(o0, i)) {
1483                for j in (0..ext[o1]).filter(|&j| sel(o1, j)) {
1484                    lines.push(i * st[o0] + j * st[o1]);
1485                }
1486            }
1487            let chunks: Vec<usize> = (0..threads.max(1)).collect();
1488            let per = lines.len().div_ceil(threads.max(1));
1489            let _ = run_par(&chunks, threads, |c| {
1490                let x = unsafe { std::slice::from_raw_parts_mut(xp.get(), n) };
1491                let mut line = Vec::new();
1492                let mut tmp = Vec::new();
1493                for &off in lines.iter().skip(c * per).take(per) {
1494                    line.clear();
1495                    line.extend((0..ext[a]).map(|t| x[off + t * st[a]]));
1496                    crate::gpuwav::inv97_line(&mut line, &mut tmp, &k);
1497                    for (t, &v) in line.iter().enumerate() {
1498                        x[off + t * st[a]] = v;
1499                    }
1500                }
1501                Ok(())
1502            });
1503            done[a] = true;
1504        }
1505    }
1506}
1507
1508fn apply_tile(ps: &Parsed3, bytes: &[u8], t: &TileRef, c: &mut [i32]) -> Result<(), DecodeError> {
1509    if ps.corr == 0 {
1510        return Ok(());
1511    }
1512    let w = words(bytes, t.words.0, t.words.1);
1513    let mut sink = DecSink {
1514        lanes: vec![RansDec::new(&w)],
1515    };
1516    let mut om = vec![MINIT; NOMODEL];
1517    let n = gamma::<_, true>(&mut sink, 0, &mut om, 0, 0) as usize;
1518    let mut last: i64 = -1;
1519    let d3 = ps.d3;
1520    let ncell: usize = t.dims.iter().product();
1521    for _ in 0..n {
1522        let g = gamma::<_, true>(&mut sink, 0, &mut om, 32, 0) as i64;
1523        let li = (last + 1 + g) as usize;
1524        last = li as i64;
1525        let neg = sink.raw(0, 0, 1) == 1;
1526        let k = gamma::<_, true>(&mut sink, 0, &mut om, 64, 0).wrapping_add(1) as i32;
1527        let k = if neg { -k } else { k };
1528        if li < ncell {
1529            let (z, y, x) = (
1530                li / (t.dims[1] * t.dims[2]),
1531                (li / t.dims[2]) % t.dims[1],
1532                li % t.dims[2],
1533            );
1534            let gi = ((t.origin[0] + z) * d3[1] + t.origin[1] + y) * d3[2] + t.origin[2] + x;
1535            c[gi] = c[gi].wrapping_add(k.wrapping_mul(ps.corr));
1536        }
1537    }
1538    if sink.lanes[0].overrun() {
1539        return Err(DecodeError::Truncated);
1540    }
1541    Ok(())
1542}
1543
1544/// Result of a (sub-box) decode.
1545pub struct Decoded3 {
1546    /// `f32` bits in box layout.
1547    pub bits: Vec<u32>,
1548    /// Stream bytes the decode had to read (header + blocks + tiles).
1549    pub bytes_read: usize,
1550    /// Code-blocks decoded.
1551    pub blocks: usize,
1552}
1553
1554/// Decode the box `[lo, hi)` (whole field: `[0, d3)`).
1555pub fn decode3_box(
1556    bytes: &[u8],
1557    lo: [usize; 3],
1558    hi: [usize; 3],
1559    threads: usize,
1560) -> Result<Decoded3, DecodeError> {
1561    let ps = parse3(bytes)?;
1562    decode3_box_parsed(&ps, bytes, lo, hi, threads)
1563}
1564
1565/// [`decode3_box`] on a parsed stream.
1566pub fn decode3_box_parsed(
1567    ps: &Parsed3,
1568    bytes: &[u8],
1569    lo: [usize; 3],
1570    hi: [usize; 3],
1571    threads: usize,
1572) -> Result<Decoded3, DecodeError> {
1573    let d3 = ps.d3;
1574    for a in 0..3 {
1575        if lo[a] >= hi[a] || hi[a] > d3[a] {
1576            return Err(DecodeError::Invalid("box"));
1577        }
1578    }
1579    let n: usize = d3.iter().product();
1580    let whole = lo == [0; 3] && hi == d3;
1581    let (samp, coef) = needs(&ps.sh, lo, hi);
1582    // blocks whose region meets the needed coefficients
1583    let nb = ps.blocks.len();
1584    let mut want = vec![whole; nb];
1585    if !whole {
1586        let p = &ps.sh.p;
1587        for (i, b) in ps.blocks.iter().enumerate() {
1588            let band = &p.bands[ps.block_band[i]];
1589            let need = if band.mask == 0 {
1590                &samp[p.nlev()]
1591            } else {
1592                &coef[band.level]
1593            };
1594            want[i] = (0..3).all(|a| hits(&need[a], b.start[a], b.end[a]));
1595        }
1596        // parent closure: a block needs the parent cells it conditions on
1597        for i in (0..nb).rev() {
1598            if !want[i] {
1599                continue;
1600            }
1601            let b = &ps.blocks[i];
1602            let Some(pi) = b.parent else { continue };
1603            let pb = &ps.sh.bands[pi];
1604            let mut plo = [0usize; 3];
1605            let mut phi = [0usize; 3];
1606            for a in 0..3 {
1607                plo[a] = (pb.start[a] + ((b.start[a] - b.org[a]) >> b.pshift[a]))
1608                    .min(pb.end[a] - 1)
1609                    .saturating_sub(1)
1610                    .max(pb.start[a]);
1611                phi[a] = (pb.start[a] + ((b.end[a] - 1 - b.org[a]) >> b.pshift[a]))
1612                    .min(pb.end[a] - 1)
1613                    + 2;
1614            }
1615            for j in 0..i {
1616                if ps.block_band[j] == pi && !want[j] {
1617                    let c = &ps.blocks[j];
1618                    if (0..3).all(|a| c.start[a] < phi[a] && plo[a] < c.end[a]) {
1619                        want[j] = true;
1620                    }
1621                }
1622            }
1623        }
1624    }
1625    let mut q = vec![0i32; n];
1626    let mut rs = vec![0i32; n];
1627    let (qp, rp) = (Ptr(q.as_mut_ptr()), Ptr(rs.as_mut_ptr()));
1628    let mut bytes_read = ps.header_bytes;
1629    let mut nblk = 0;
1630    let irb = IrBase::new(ps, bytes);
1631    for (g0, g1) in ps.level_groups() {
1632        let items: Vec<usize> = (g0..g1).filter(|&i| want[i]).collect();
1633        for &i in &items {
1634            bytes_read += ps.block_words[i].1 - ps.block_words[i].0;
1635        }
1636        nblk += items.len();
1637        run_par(&items, threads, |i| {
1638            decode_block(ps, bytes, i, qp, rp, n, irb.as_ref())
1639        })?;
1640    }
1641    drop(rs);
1642    // dequantize the decoded blocks
1643    let [_, ny, nx] = d3;
1644    for (i, b) in ps.blocks.iter().enumerate() {
1645        if !want[i] {
1646            continue;
1647        }
1648        let band = &ps.sh.bands[ps.block_band[i]];
1649        for z in b.start[0]..b.end[0] {
1650            for y in b.start[1]..b.end[1] {
1651                for x in b.start[2]..b.end[2] {
1652                    let j = (z * ny + y) * nx + x;
1653                    q[j] = dequant(q[j], band, &ps.dl);
1654                }
1655            }
1656        }
1657    }
1658    inverse_window(
1659        &ps.sh,
1660        &mut q,
1661        if whole { None } else { Some((&samp, &coef)) },
1662        threads,
1663    );
1664    let scale = ps.scale();
1665    let bx = [0, 1, 2].map(|a| hi[a] - lo[a]);
1666    for t in &ps.tiles {
1667        if (0..3).all(|a| t.origin[a] < hi[a] && t.origin[a] + t.dims[a] > lo[a]) {
1668            bytes_read += t.bytes.1 - t.bytes.0;
1669            apply_tile(ps, bytes, t, &mut q)?;
1670        }
1671    }
1672    let mut out = vec![0u32; bx.iter().product()];
1673    for z in lo[0]..hi[0] {
1674        for y in lo[1]..hi[1] {
1675            let src = (z * ny + y) * nx;
1676            let dst = ((z - lo[0]) * bx[1] + (y - lo[1])) * bx[2];
1677            for x in lo[2]..hi[2] {
1678                out[dst + x - lo[2]] = to_f32_bits(q[src + x].wrapping_add(ps.mean_fix), scale);
1679            }
1680        }
1681    }
1682    for t in &ps.tiles {
1683        for &(li, bits) in &t.esc {
1684            let (z, y, x) = (
1685                li / (t.dims[1] * t.dims[2]),
1686                (li / t.dims[2]) % t.dims[1],
1687                li % t.dims[2],
1688            );
1689            let g = [t.origin[0] + z, t.origin[1] + y, t.origin[2] + x];
1690            if (0..3).all(|a| g[a] >= lo[a] && g[a] < hi[a]) {
1691                out[((g[0] - lo[0]) * bx[1] + g[1] - lo[1]) * bx[2] + g[2] - lo[2]] = bits;
1692            }
1693        }
1694    }
1695    Ok(Decoded3 {
1696        bits: out,
1697        bytes_read,
1698        blocks: nblk,
1699    })
1700}
1701
1702/// Decode a whole v3 stream to `f32`.
1703pub fn decode3_f32(bytes: &[u8], threads: usize) -> Result<(Vec<usize>, Vec<f32>), DecodeError> {
1704    let ps = parse3(bytes)?;
1705    let d = decode3_box_parsed(&ps, bytes, [0; 3], ps.d3, threads)?;
1706    Ok((
1707        ps.dims.clone(),
1708        d.bits.into_iter().map(f32::from_bits).collect(),
1709    ))
1710}
1711
1712#[cfg(test)]
1713mod tests {
1714    use super::*;
1715
1716    fn field(d: [usize; 3]) -> Vec<f32> {
1717        let mut v = Vec::new();
1718        for i in 0..d[0] {
1719            for j in 0..d[1] {
1720                for k in 0..d[2] {
1721                    let (x, y, z) = (i as f64, j as f64, k as f64);
1722                    v.push(
1723                        ((0.3 * x).sin() + (0.11 * y * z / 8.0).cos() + 0.01 * x * y
1724                            - ((k * 7 + j * 3) % 11) as f64 * 0.003
1725                            + 3.0) as f32,
1726                    );
1727                }
1728            }
1729        }
1730        v
1731    }
1732
1733    fn err(x: &[f32], y: &[f32]) -> (f64, f64) {
1734        let mut se = 0.0;
1735        let mut mx = 0.0f64;
1736        for (a, b) in x.iter().zip(y) {
1737            let e = *a as f64 - *b as f64;
1738            se += e * e;
1739            mx = mx.max(e.abs());
1740        }
1741        ((se / x.len() as f64).sqrt(), mx)
1742    }
1743
1744    #[test]
1745    fn round_trip_and_random_access() {
1746        let d = [24, 37, 45];
1747        let x = field(d);
1748        for bound in [
1749            ErrorBound::Psnr(60.0),
1750            ErrorBound::Psnr(95.0),
1751            ErrorBound::MaxAbs(1e-3),
1752            ErrorBound::MaxAbs(2e-5),
1753        ] {
1754            for (block, resctx, lanes) in
1755                [(8, true, 1), (16, false, 1), (64, true, 1), (16, true, 4)]
1756            {
1757                let cfg = Gpu3Config {
1758                    block,
1759                    resctx,
1760                    lanes,
1761                    tile: 16,
1762                    ..Gpu3Config::new(bound)
1763                };
1764                let (bytes, st) = encode3_f32(&x, &d, &cfg, 2);
1765                let (_, y) = decode3_f32(&bytes, 1).unwrap();
1766                let (rmse, mx) = err(&x, &y);
1767                assert!(
1768                    (rmse - st.rmse).abs() <= 1e-12 * rmse.max(1e-30),
1769                    "{bound:?} {block}"
1770                );
1771                assert_eq!(mx, st.max_abs);
1772                if let ErrorBound::MaxAbs(b) = bound {
1773                    assert!(mx <= b);
1774                }
1775                let (_, y4) = decode3_f32(&bytes, 4).unwrap();
1776                assert!(y.iter().zip(&y4).all(|(a, b)| a.to_bits() == b.to_bits()));
1777                for (lo, hi) in [
1778                    ([3, 5, 7], [21, 29, 20]),
1779                    ([0, 0, 0], [1, 1, 1]),
1780                    ([23, 36, 44], [24, 37, 45]),
1781                    ([10, 0, 30], [14, 37, 31]),
1782                ] {
1783                    let sub = decode3_box(&bytes, lo, hi, 1).unwrap();
1784                    let mut k = 0;
1785                    for z in lo[0]..hi[0] {
1786                        for yy in lo[1]..hi[1] {
1787                            for xx in lo[2]..hi[2] {
1788                                assert_eq!(
1789                                    sub.bits[k],
1790                                    y[(z * d[1] + yy) * d[2] + xx].to_bits(),
1791                                    "{bound:?} {block} {lo:?}"
1792                                );
1793                                k += 1;
1794                            }
1795                        }
1796                    }
1797                }
1798            }
1799        }
1800    }
1801
1802    #[test]
1803    fn two_d() {
1804        let x2: Vec<f32> = (0..70 * 90)
1805            .map(|i| ((i % 90) as f32 * 0.07).sin() * ((i / 90) as f32 * 0.05).cos())
1806            .collect();
1807        let (b, st) = encode3_f32(
1808            &x2,
1809            &[70, 90],
1810            &Gpu3Config {
1811                block2d: 16,
1812                tile: 4,
1813                ..Gpu3Config::new(ErrorBound::Psnr(70.0))
1814            },
1815            1,
1816        );
1817        let (_, y) = decode3_f32(&b, 1).unwrap();
1818        assert!((err(&x2, &y).0 - st.rmse).abs() <= 1e-12 * st.rmse);
1819        let sub = decode3_box(&b, [0, 30, 40], [1, 50, 70], 1).unwrap();
1820        assert!(sub.bytes_read < b.len());
1821        for (k, (yy, xx)) in (30..50)
1822            .flat_map(|a| (40..70).map(move |c| (a, c)))
1823            .enumerate()
1824        {
1825            assert_eq!(sub.bits[k], y[yy * 90 + xx].to_bits());
1826        }
1827    }
1828}