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}