code wiki / (root) / nx_av1_txfm2d.nx

nx_av1_txfm2d.nx source

↩ module page · 165 lines · 7253 B

1// nx_av1_txfm2d.nx -- the 2-D transform-type lattice shared VERBATIM with AV2. 2// 3// WHY THIS ONE, AND WHY NOW. AOMedia published AV2 v1.0.0 on 2026-05-28 and 4// announced it on 2026-06-09. The technical overview by Andrey Norkin lists 5// what AV2 REUSES from AV1 unchanged, and the first item is: 6// 7// "Four primary 1-D transform types producing 16 2-D combinations" 8// 9// So this lattice is not AV1 work that might carry over to AV2 -- it is AV2 10// work, already, by the spec authors' own account. Every byte here serves both 11// decoders. That matters because as of mid-2026 NO browser decodes AV2: 12// not Chrome, Firefox, Edge or Safari; no shipping hardware decoder exists; 13// dav2d is early and the reference software is not production-ready. Practical 14// support is expected around 2028. Building the shared core now is the whole 15// moat argument, and this module is the part of it with the least risk of 16// being invalidated by a spec detail. 17// 18// THE FOUR 1-D KINDS: DCT, ADST, FLIPADST, IDTX (identity). 19// 20// THE 16 COMBINATIONS are exactly the 4x4 grid of (column kind, row kind), 21// each appearing EXACTLY ONCE -- a bijection. That is the property the gate 22// proves, and it is strong: any mis-assignment of any one of the sixteen 23// types either duplicates a pair or leaves one uncovered, and both are caught. 24// Naming follows AV1: the FIRST token is the COLUMN (vertical) transform and 25// the second is the ROW (horizontal) one, so ADST_DCT means ADST down the 26// columns and DCT across the rows. Swapping that convention decodes -- it 27// produces a transposed-looking error that survives on symmetric content -- 28// which is exactly why it is pinned by test rather than by comment. 29// 30// FLIPADST is the ADST with its output reversed. It exists because a block 31// whose energy sits at the far edge is predicted better by a basis that ramps 32// the other way; substituting plain ADST for it does not error, it just puts 33// the gradient at the wrong end of the block. 34// 35// license_tier: ORIGINAL 36import "nx_syscalls.nx" 37import "nx_av1_txfm.nx" 38import "nx_av1_dct.nx" 39import "nx_av1_adst.nx" 40 41// ===== the four 1-D kinds ========================================= 42 43const NX_TX1D_DCT: i64 = 0 44const NX_TX1D_ADST: i64 = 1 45const NX_TX1D_FLIPADST: i64 = 2 46const NX_TX1D_IDTX: i64 = 3 47const NX_TX1D_N: i64 = 4 48 49// ===== the sixteen 2-D types (AV1 numbering, reused by AV2) ======== 50 51const NX_TX2D_DCT_DCT: i64 = 0 52const NX_TX2D_ADST_DCT: i64 = 1 53const NX_TX2D_DCT_ADST: i64 = 2 54const NX_TX2D_ADST_ADST: i64 = 3 55const NX_TX2D_FLIPADST_DCT: i64 = 4 56const NX_TX2D_DCT_FLIPADST: i64 = 5 57const NX_TX2D_FLIPADST_FLIPADST: i64 = 6 58const NX_TX2D_ADST_FLIPADST: i64 = 7 59const NX_TX2D_FLIPADST_ADST: i64 = 8 60const NX_TX2D_IDTX: i64 = 9 61const NX_TX2D_V_DCT: i64 = 10 62const NX_TX2D_H_DCT: i64 = 11 63const NX_TX2D_V_ADST: i64 = 12 64const NX_TX2D_H_ADST: i64 = 13 65const NX_TX2D_V_FLIPADST: i64 = 14 66const NX_TX2D_H_FLIPADST: i64 = 15 67const NX_TX2D_N: i64 = 16 68 69// Column (vertical) 1-D kind for a 2-D type. -1 for an unknown type: 70// a decoder handed a reserved value must refuse, not silently pick DCT. 71func nx_av1_tx2d_col_kind(t: i64) -> i64 { 72 if t == NX_TX2D_DCT_DCT { return NX_TX1D_DCT } 73 if t == NX_TX2D_ADST_DCT { return NX_TX1D_ADST } 74 if t == NX_TX2D_DCT_ADST { return NX_TX1D_DCT } 75 if t == NX_TX2D_ADST_ADST { return NX_TX1D_ADST } 76 if t == NX_TX2D_FLIPADST_DCT { return NX_TX1D_FLIPADST } 77 if t == NX_TX2D_DCT_FLIPADST { return NX_TX1D_DCT } 78 if t == NX_TX2D_FLIPADST_FLIPADST { return NX_TX1D_FLIPADST } 79 if t == NX_TX2D_ADST_FLIPADST { return NX_TX1D_ADST } 80 if t == NX_TX2D_FLIPADST_ADST { return NX_TX1D_FLIPADST } 81 if t == NX_TX2D_IDTX { return NX_TX1D_IDTX } 82 if t == NX_TX2D_V_DCT { return NX_TX1D_DCT } 83 if t == NX_TX2D_H_DCT { return NX_TX1D_IDTX } 84 if t == NX_TX2D_V_ADST { return NX_TX1D_ADST } 85 if t == NX_TX2D_H_ADST { return NX_TX1D_IDTX } 86 if t == NX_TX2D_V_FLIPADST { return NX_TX1D_FLIPADST } 87 if t == NX_TX2D_H_FLIPADST { return NX_TX1D_IDTX } 88 return 0 - 1 89} 90 91// Row (horizontal) 1-D kind for a 2-D type. 92func nx_av1_tx2d_row_kind(t: i64) -> i64 { 93 if t == NX_TX2D_DCT_DCT { return NX_TX1D_DCT } 94 if t == NX_TX2D_ADST_DCT { return NX_TX1D_DCT } 95 if t == NX_TX2D_DCT_ADST { return NX_TX1D_ADST } 96 if t == NX_TX2D_ADST_ADST { return NX_TX1D_ADST } 97 if t == NX_TX2D_FLIPADST_DCT { return NX_TX1D_DCT } 98 if t == NX_TX2D_DCT_FLIPADST { return NX_TX1D_FLIPADST } 99 if t == NX_TX2D_FLIPADST_FLIPADST { return NX_TX1D_FLIPADST } 100 if t == NX_TX2D_ADST_FLIPADST { return NX_TX1D_FLIPADST } 101 if t == NX_TX2D_FLIPADST_ADST { return NX_TX1D_ADST } 102 if t == NX_TX2D_IDTX { return NX_TX1D_IDTX } 103 if t == NX_TX2D_V_DCT { return NX_TX1D_IDTX } 104 if t == NX_TX2D_H_DCT { return NX_TX1D_DCT } 105 if t == NX_TX2D_V_ADST { return NX_TX1D_IDTX } 106 if t == NX_TX2D_H_ADST { return NX_TX1D_ADST } 107 if t == NX_TX2D_V_FLIPADST { return NX_TX1D_IDTX } 108 if t == NX_TX2D_H_FLIPADST { return NX_TX1D_FLIPADST } 109 return 0 - 1 110} 111 112// ===== the FLIPADST 1-D transform ================================= 113// 114// The ADST with its output reversed. Reversing IN PLACE with a single 115// index would clobber, so this stages through a scratch buffer. 116 117func nx_av1_iflipadst4(io: *i64) -> i64 { 118 let tmp: *i64 = sys_mmap(64) as *i64 119 var i: i64 = 0 120 nx_av1_iadst4(io) 121 while i < 4 { 122 tmp[i] = io[i] 123 i = i + 1 124 } 125 i = 0 126 while i < 4 { 127 io[i] = tmp[3 - i] 128 i = i + 1 129 } 130 return 1 131} 132 133// ===== the IDTX 1-D transform ===================================== 134// 135// Identity is not a no-op: it scales by sqrt(2) so that the 2-D pair 136// carries the same overall gain as a DCT pair. 5793 is sqrt(2) in 137// Q12 (sqrt(2) * 4096 = 5792.6). Treating identity as a literal copy 138// leaves every identity-coded block at 1/sqrt(2) amplitude per axis -- 139// a uniformly washed-out block, which reads as a coding artefact 140// rather than as a bug. 141 142const NX_TX_SQRT2_Q12: i64 = 5793 143 144func nx_av1_itx_identity4(io: *i64) -> i64 { 145 var i: i64 = 0 146 while i < 4 { 147 io[i] = nx_av1_round2(io[i] * NX_TX_SQRT2_Q12, NX_TX_COS_BITS) 148 i = i + 1 149 } 150 return 1 151} 152 153// ===== the 1-D dispatcher ========================================= 154// 155// Returns 0 and leaves io untouched for an unknown kind -- a decoder 156// must be able to tell "I did not transform this" from "I applied the 157// default", because the default silently produces a plausible block. 158 159func nx_av1_itx1d_4(kind: i64, cos_table: *i64, io: *i64) -> i64 { 160 if kind == NX_TX1D_DCT { nx_av1_idct4(cos_table, io); return 1 } 161 if kind == NX_TX1D_ADST { nx_av1_iadst4(io); return 1 } 162 if kind == NX_TX1D_FLIPADST { nx_av1_iflipadst4(io); return 1 } 163 if kind == NX_TX1D_IDTX { nx_av1_itx_identity4(io); return 1 } 164 return 0 165}