code wiki / _hdl_build / nx_vcodec_deblock_gate.nx
nx_vcodec_deblock_gate.nx source
↩ module page · 99 lines · 6078 B
1import "nx_gate_base.nx"
2// nx_vcodec_deblock_gate.nx -- the in-loop DEBLOCK safety + quality gate. A deblocking filter is IN-LOOP:
3// it rewrites the recon that becomes the next frame's prev. If the encoder's deblock and the decoder's
4// deblock ever disagree by one bit, the P-reference diverges and the picture rots over seconds. So the
5// CARDINAL test: run a real P-sequence through vv_enc + vv_dec (both deblock internally) and assert the
6// decoder's recon == the encoder's recon on EVERY frame. Then confirm the filter actually REDUCES
7// block-edge energy (the point) without wrecking fidelity (MSE). license_tier: ORIGINAL
8import "nx_syscalls.nx"
9import "nx_video_codec_wasm.nx"
10
11func grow(name: *u8, ok: i64) -> i64 { if ok==1 { gw(" PASS " as *u8) } else { gw(" FAIL " as *u8) } gw(name); gw("
12" as *u8); return ok }
13func gn(v: i64) -> i64 {
14 let b: *u8=sys_mmap(28); var m: i64=v; if m<0{sys_write(1,"-" as *u8,1);m=0-m}
15 let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1} while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}
16 var i: i64=0; while i<k{b[i]=t[k-1-i];i=i+1} sys_write(1,b,k); return 0 }
17func ck(name: *u8, ok: i64, p: *i64, t: *i64) -> i64 {
18 t[0]=t[0]+1; gw(" " as *u8); gw(name); if ok==1 { gw(" PASS\n" as *u8); p[0]=p[0]+1 } else { gw(" FAIL\n" as *u8) } return 0 }
19func absi(x: i64) -> i64 { if x<0 { return 0-x } return x }
20func blockiness(p: *u8, W: i64, H: i64) -> i64 {
21 var bs: i64=0; var bn: i64=0; var isum: i64=0; var iN: i64=0; var y: i64=0
22 while y<H { var x: i64=1
23 while x<W { let d: i64=absi((p[y*W+x]&0xff)-(p[y*W+x-1]&0xff))
24 if (x&3)==0 { bs=bs+d; bn=bn+1 } else { isum=isum+d; iN=iN+1 } x=x+1 } y=y+1 }
25 if iN==0 { return 0 } if isum==0 { return 999 } return (bs*iN*100)/(isum*bn) }
26func mse(a: *u8, b: *u8, N: i64) -> i64 {
27 var se: i64=0; var i: i64=0; while i<N { let d: i64=(a[i]&0xff)-(b[i]&0xff); se=se+d*d; i=i+1 } return se/N }
28
29// fill a detailed frame; shift = horizontal pan (fakes motion for P-frames)
30func fill(yuv: *u8, W: i64, H: i64, shift: i64) -> i64 {
31 let N: i64=W*H; let C: i64=(W/2)*(H/2)
32 var y: i64=0
33 while y<H { var x: i64=0
34 while x<W { let sx: i64=x+shift
35 var v: i64=(sx*255)/W + (((sx/2+y/3)%7)*20-60)
36 if sx>W/2 { v=v+45 }
37 if v<0 {v=0} if v>255 {v=255}
38 yuv[y*W+x]=v as u8; x=x+1 } y=y+1 }
39 var i: i64=N; while i<N+C { yuv[i]=112 as u8; i=i+1 }
40 while i<N+2*C { yuv[i]=138 as u8; i=i+1 }
41 return 0 }
42
43func main() -> i64 {
44 gw("=== nx_vcodec_deblock_gate: in-loop deblock -- bit-exact P-chain + blockiness ===\n" as *u8)
45 let p: *i64=sys_mmap(16) as *i64; let t: *i64=sys_mmap(16) as *i64
46 let W: i64=256; let H: i64=192; let q: i64=24
47 let N: i64=W*H; let C: i64=(W/2)*(H/2); let sz: i64=N+2*C
48 let cur: *u8=sys_mmap(sz)
49 var prevE: *u8=sys_mmap(sz); var reconE: *u8=sys_mmap(sz)
50 var prevD: *u8=sys_mmap(sz); var reconD: *u8=sys_mmap(sz)
51 let stream: *u8=sys_mmap(262144)
52 let blk: *i64=sys_mmap(512) as *i64; let mv: *i64=sys_mmap(128) as *i64
53 var i: i64=0; while i<sz { prevE[i]=0 as u8; prevD[i]=0 as u8; i=i+1 }
54
55 var exact: i64=1
56 var bkLast: i64=0; var mseLast: i64=0
57 var fr: i64=0
58 while fr < 8 {
59 fill(cur, W, H, fr*3) // pan 3px/frame
60 let key: i64 = (fr==0) as i64
61 let kb: i64 = vv_enc(cur, prevE, reconE, W, H, q, key, 6016, stream, 262144, blk, mv)
62 if kb <= 0 { exact=0 }
63 vv_dec(prevD, reconD, W, H, q, stream, kb, blk, mv)
64 // CARDINAL: decoder recon must equal encoder recon (both deblocked), every byte, every frame
65 var j: i64=0
66 while j<sz { if (reconE[j]&0xff) != (reconD[j]&0xff) { exact=0; j=sz } else { j=j+1 } }
67 bkLast = blockiness(reconD, W, H); mseLast = mse(reconD, cur, N)
68 // ping-pong: this recon becomes next prev
69 let te: i64=prevE as i64; let se: i64=reconE as i64
70 prevE = se as *u8; reconE = te as *u8
71 let td: i64=prevD as i64; let sd: i64=reconD as i64
72 prevD = sd as *u8; reconD = td as *u8
73 fr = fr + 1
74 }
75 ck("bit-exact P-chain across 8 frames (enc recon == dec recon)\x00" as *u8, exact, p, t)
76 // HONEST A/B on ONE keyframe: decode the SAME stream two ways -- raw (no deblock) vs vv_dec (deblock).
77 fill(cur, W, H, 0)
78 let pz: *u8 = sys_mmap(sz); var z: i64=0; while z<sz { pz[z]=0 as u8; z=z+1 }
79 let kb2: i64 = vv_enc(cur, pz, reconE, W, H, q, 1, 6016, stream, 262144, blk, mv)
80 let reconRaw: *u8 = sys_mmap(sz)
81 let yb: i64 = vv_rd_u32b(stream, 0); let ub: i64 = vv_rd_u32b(stream, 4)
82 // raw arm MUST decode with the SAME transform vv_enc used (tf=1 DCT-II since build 781) -- the tf=0
83 // delegator here silently became a MISMATCHED decode when DCT went default, poisoning the A/B.
84 vc_dec_frame_packed_tf(pz, reconRaw, W, H, q, ((stream as i64)+12) as *u8, blk, mv, 1)
85 vc_dec_frame_packed_tf(((pz as i64)+N) as *u8, ((reconRaw as i64)+N) as *u8, W/2, H/2, q, ((stream as i64)+12+yb) as *u8, blk, mv, 1)
86 vc_dec_frame_packed_tf(((pz as i64)+N+C) as *u8, ((reconRaw as i64)+N+C) as *u8, W/2, H/2, q, ((stream as i64)+12+yb+ub) as *u8, blk, mv, 1)
87 let reconDB: *u8 = sys_mmap(sz)
88 vv_dec(pz, reconDB, W, H, q, stream, kb2, blk, mv)
89 let bkRaw: i64 = blockiness(reconRaw, W, H)
90 let bkDB: i64 = blockiness(reconDB, W, H)
91 let mseDB: i64 = mse(reconDB, cur, N)
92 gw(" keyframe blockiness: raw(no deblock)=" as *u8); gn(bkRaw); gw(" deblocked=" as *u8); gn(bkDB)
93 gw(" (-" as *u8); gn((bkRaw-bkDB)*100/bkRaw); gw("%) MSE(deblocked,orig)=" as *u8); gn(mseDB); gw("\n" as *u8)
94 ck("deblock REDUCES block-edge energy vs raw decode\x00" as *u8, (bkDB < bkRaw) as i64, p, t)
95 ck("fidelity preserved (MSE < 130 ~ >27dB, not over-smoothed)\x00" as *u8, (mseDB < 130) as i64, p, t)
96
97 gw("DEBLOCK-GATE: " as *u8); gn(p[0]); gw("/" as *u8); gn(t[0])
98 if p[0]==t[0] { gw(" GREEN -- in-loop deblock is bit-exact + de-blocks + keeps detail\n" as *u8); return 0 }
99 gw(" RED\n" as *u8); return 1 }