code wiki / (root) / nx_av1_adst.nx

nx_av1_adst.nx source

↩ module page · 102 lines · 3630 B

1// nx_av1_adst.nx -- AV1/AV2 inverse ADST (asymmetric discrete sine transform). 2// 3// AV1 picks between DCT and ADST per block and per axis, because they have 4// opposite shapes at the block edge: the DCT's first basis is FLAT, the ADST's 5// is a RISING RAMP. Intra-predicted residuals are typically small next to the 6// predicted edge and grow with distance from it, which is exactly the ADST's 7// basis-0 shape -- that is the whole reason it exists in the codec. 8// 9// SUBSTITUTING ONE FOR THE OTHER DECODES. That is the danger: a block coded 10// with ADST and reconstructed with DCT produces a plausible image with wrong 11// gradients near block edges, which reads as banding or a soft grid rather 12// than as corruption. Nothing errors. 13// 14// THE SINPI CONSTANTS ARE NOT THE COSINE TABLE. ADST uses sin(k*pi/9) at 15// 12-bit -- 1321, 2482, 3344, 3803 -- which appear nowhere in the DCT's 16// quarter-period cosine table. Reaching for cos128() here silently builds a 17// transform with the wrong basis entirely. 18// 19// THE OUTPUT OF A BASIS-0 IMPULSE IS THE CONSTANT SET ITSELF. Feeding 20// [4096,0,0,0] returns exactly [1321, 2482, 3344, 3803] -- the four constants, 21// in order. That makes the transform exactly checkable without reference 22// vectors, and it is what the gate asserts. 23// 24// genealogy_id: av1_spec_7_13_2_6_inverse_adst 25// lineage_id: nx_av1_adst_v1 26// license_tier: ORIGINAL 27 28import "nx_syscalls.nx" 29import "nx_av1_txfm.nx" 30 31const NX_SINPI_1_9: i64 = 1321 32const NX_SINPI_2_9: i64 = 2482 33const NX_SINPI_3_9: i64 = 3344 34const NX_SINPI_4_9: i64 = 3803 35 36// ===== 4-point inverse ADST ======================================= 37// 38// Operates in place on four coefficients. Every local is declared once -- 39// nx_cc uses flat function scope. 40 41func nx_av1_iadst4(io: *i64) -> i64 { 42 let t0: i64 = io[0] 43 let t1: i64 = io[1] 44 let t2: i64 = io[2] 45 let t3: i64 = io[3] 46 47 var s0: i64 = NX_SINPI_1_9 * t0 48 var s1: i64 = NX_SINPI_2_9 * t0 49 let s2in: i64 = NX_SINPI_3_9 * t1 50 let s3in: i64 = NX_SINPI_4_9 * t2 51 let s4: i64 = NX_SINPI_1_9 * t2 52 let s5: i64 = NX_SINPI_2_9 * t3 53 let s6: i64 = NX_SINPI_4_9 * t3 54 55 // the cross term: this is what makes the basis asymmetric 56 let a7: i64 = t0 - t2 57 let b7: i64 = a7 + t3 58 59 s0 = s0 + s3in 60 s1 = s1 - s4 61 let s3: i64 = s2in 62 let s2: i64 = NX_SINPI_3_9 * b7 63 s0 = s0 + s5 64 s1 = s1 - s6 65 66 let x0: i64 = s0 + s3 67 let x1: i64 = s1 + s3 68 let x2: i64 = s2 69 let x3: i64 = s0 + s1 - s3 70 71 io[0] = nx_av1_round2(x0, NX_TX_COS_BITS) 72 io[1] = nx_av1_round2(x1, NX_TX_COS_BITS) 73 io[2] = nx_av1_round2(x2, NX_TX_COS_BITS) 74 io[3] = nx_av1_round2(x3, NX_TX_COS_BITS) 75 return 1 76} 77 78// ===== the transform-type selector ================================ 79// 80// AV1 chooses a transform PER AXIS: a block can be ADST vertically and DCT 81// horizontally. The pair is what the tx_type names, and getting the axes 82// swapped produces a transposed-looking error that survives on square, 83// symmetric content and shows up on everything else. 84 85const NX_TXT_DCT_DCT: i64 = 0 86const NX_TXT_ADST_DCT: i64 = 1 87const NX_TXT_DCT_ADST: i64 = 2 88const NX_TXT_ADST_ADST: i64 = 3 89 90// 1 if this transform type uses ADST on the ROW (horizontal) pass 91func nx_av1_txt_row_is_adst(tx_type: i64) -> i64 { 92 if tx_type == NX_TXT_DCT_ADST { return 1 } 93 if tx_type == NX_TXT_ADST_ADST { return 1 } 94 return 0 95} 96 97// 1 if this transform type uses ADST on the COLUMN (vertical) pass 98func nx_av1_txt_col_is_adst(tx_type: i64) -> i64 { 99 if tx_type == NX_TXT_ADST_DCT { return 1 } 100 if tx_type == NX_TXT_ADST_ADST { return 1 } 101 return 0 102}