1+ #include "softfilter.h"
2+ #include <stdlib.h>
3+ #include <stdint.h>
4+ #include <string.h>
5+ #include <math.h>
6+
7+ #ifndef M_PI
8+ #define M_PI 3.14159265358979323846
9+ #endif
10+
11+ #ifdef RARCH_INTERNAL
12+ #define softfilter_get_implementation ntsc_get_implementation
13+ #define softfilter_thread_data ntsc_softfilter_thread_data
14+ #define filter_data ntsc_filter_data
15+ #endif
16+
17+ #define NTSC_SCALE_X 2
18+ #define NTSC_SCALE_Y 1
19+ #define NTSC_MAX_WIDTH 1024
20+ #define PHASE_MAX 16 // Max phases for Atari 2600
21+
22+ typedef struct { int Y , I , Q ; } yiq_t ;
23+
24+ struct softfilter_thread_data {
25+ void * out_data ;
26+ const void * in_data ;
27+ size_t out_pitch ;
28+ size_t in_pitch ;
29+ unsigned width ;
30+ unsigned height ;
31+ int first ;
32+ };
33+
34+ struct filter_data {
35+ unsigned threads ;
36+ struct softfilter_thread_data * workers ;
37+ unsigned in_fmt ;
38+ int hue_cos , hue_sin ;
39+ int saturation , sharpness , artifacts ;
40+ int huesat_identity , pal_mode , atari_mode , c64_mode ;
41+ int phase_count , nes_frame_count ;
42+ int lut_sin [PHASE_MAX ], lut_cos [PHASE_MAX ];
43+ };
44+
45+ static inline void rgb_to_yiq (int r , int g , int b , int * Y , int * I , int * Q ) {
46+ * Y = ( 77 * r + 150 * g + 29 * b ) >> 8 ;
47+ * I = ( 157 * r - 132 * g - 26 * b ) >> 8 ;
48+ * Q = ( -38 * r - 74 * g + 112 * b ) >> 8 ;
49+ }
50+
51+ static inline void yiq_to_rgb (int Y , int I , int Q , int * r , int * g , int * b ) {
52+ int rv = Y + ((292 * I ) >> 8 );
53+ int gv = Y - ((149 * I ) >> 8 ) - ((101 * Q ) >> 8 );
54+ int bv = Y + ((520 * Q ) >> 8 );
55+ * r = (rv < 0 ) ? 0 : (rv > 255 ? 255 : rv );
56+ * g = (gv < 0 ) ? 0 : (gv > 255 ? 255 : gv );
57+ * b = (bv < 0 ) ? 0 : (bv > 255 ? 255 : bv );
58+ }
59+
60+ static void ntsc_process_line (const struct filter_data * filt ,
61+ const struct softfilter_thread_data * thr , unsigned y ,
62+ int * cbuf , int * lineI , int * lineQ , int * lineY , yiq_t * yiq_cache ,
63+ void * dst_ptr ) {
64+ unsigned width = thr -> width ;
65+ unsigned ow = width * 2 ;
66+ int phases = filt -> phase_count ;
67+
68+ // C64 uses 2-phase steps per output pixel if sampled at 8 phases
69+ int phase_step = (filt -> c64_mode ) ? 2 : 4 ;
70+ int frame_offset = (filt -> nes_frame_count % 2 ) * (phases / 2 );
71+ int line_mult = (filt -> pal_mode ) ? (phase_step + 1 ) : phase_step ;
72+ int line_phase = (frame_offset + ((thr -> first + (int )y ) * line_mult )) % phases ;
73+
74+ for (unsigned x = 0 ; x < width ; x ++ ) {
75+ int r , g , b , Y , I , Q ;
76+ if (filt -> in_fmt == SOFTFILTER_FMT_RGB565 ) {
77+ uint16_t p = ((uint16_t * )thr -> in_data )[y * (thr -> in_pitch /2 ) + x ];
78+ r = ((p >> 11 ) & 0x1f ) << 3 ; g = ((p >> 5 ) & 0x3f ) << 2 ; b = (p & 0x1f ) << 3 ;
79+ } else {
80+ uint32_t p = ((uint32_t * )thr -> in_data )[y * (thr -> in_pitch /4 ) + x ];
81+ r = (p >> 16 ) & 0xFF ; g = (p >> 8 ) & 0xFF ; b = p & 0xFF ;
82+ }
83+ rgb_to_yiq (r , g , b , & Y , & I , & Q );
84+ yiq_cache [x ].Y = Y ; yiq_cache [x ].I = I ; yiq_cache [x ].Q = Q ;
85+
86+ for (int p_idx = 0 ; p_idx < 2 ; p_idx ++ ) {
87+ int ph = (line_phase + (x * phase_step * 2 ) + (p_idx * phase_step )) % phases ;
88+ cbuf [x * 2 + p_idx ] = Y + ((I * filt -> lut_cos [ph ] + Q * filt -> lut_sin [ph ]) >> 8 );
89+ }
90+ }
91+
92+ for (unsigned x = 0 ; x < ow ; x ++ ) {
93+ int accI = 0 , accQ = 0 ;
94+ int taps = (filt -> atari_mode ) ? 8 : (filt -> c64_mode ? 4 : 6 );
95+
96+ for (int t = - taps ; t < taps ; t ++ ) {
97+ int idx = (x + t < 0 ) ? 0 : (x + t >= (int )ow ? (int )ow - 1 : x + t );
98+ int ph = (line_phase + (x * phase_step ) + (t * phase_step )) % phases ;
99+ accI += cbuf [idx ] * filt -> lut_cos [ph ];
100+ accQ += cbuf [idx ] * filt -> lut_sin [ph ];
101+ }
102+ lineI [x ] = accI / (taps * 256 ); lineQ [x ] = accQ / (taps * 256 );
103+
104+ int i_m2 = (x > 1 ) ? (int )x - 2 : 0 , i_m1 = (x > 0 ) ? (int )x - 1 : 0 ;
105+ int i_p1 = (x < ow - 1 ) ? (int )x + 1 : (int )ow - 1 , i_p2 = (x < ow - 2 ) ? (int )x + 2 : (int )ow - 1 ;
106+
107+ // Notch filter optimized for system-specific bandwidth
108+ int notchedY = (filt -> c64_mode ) ?
109+ (cbuf [i_m1 ] + (cbuf [x ] << 1 ) + cbuf [i_p1 ]) >> 2 :
110+ (cbuf [i_m2 ] + (cbuf [i_m1 ] << 2 ) + (cbuf [x ] * 6 ) + (cbuf [i_p1 ] << 2 ) + cbuf [i_p2 ]) >> 4 ;
111+
112+ lineY [x ] = ((notchedY * (256 - filt -> artifacts )) + (cbuf [x ] * filt -> artifacts )) >> 8 ;
113+ }
114+
115+ for (unsigned x = 0 ; x < ow ; x ++ ) {
116+ int Y = lineY [x ], I = lineI [x ], Q = lineQ [x ];
117+ if (filt -> sharpness > 0 ) {
118+ int x_m1 = (x > 0 ) ? (int )x - 1 : 0 , x_p1 = (x < ow - 1 ) ? (int )x + 1 : (int )ow - 1 ;
119+ int edge = (Y << 1 ) - (lineY [x_m1 ] + lineY [x_p1 ]);
120+ Y += (edge * filt -> sharpness ) >> 9 ;
121+ Y = (Y < 0 ) ? 0 : (Y > 255 ? 255 : Y );
122+ }
123+ if (!filt -> huesat_identity ) {
124+ int Ir = (I * filt -> hue_cos - Q * filt -> hue_sin ) >> 8 ;
125+ int Qr = (I * filt -> hue_sin + Q * filt -> hue_cos ) >> 8 ;
126+ I = (Ir * filt -> saturation ) >> 8 ; Q = (Qr * filt -> saturation ) >> 8 ;
127+ }
128+ int r , g , b ;
129+ yiq_to_rgb (Y , I , Q , & r , & g , & b );
130+ if (filt -> in_fmt == SOFTFILTER_FMT_RGB565 )
131+ ((uint16_t * )dst_ptr )[x ] = ((r >> 3 ) << 11 ) | ((g >> 2 ) << 5 ) | (b >> 3 );
132+ else
133+ ((uint32_t * )dst_ptr )[x ] = 0xFF000000u | (r << 16 ) | (g << 8 ) | b ;
134+ }
135+ }
136+
137+ static void ntsc_work_cb (void * data , void * thread_data ) {
138+ struct filter_data * filt = (struct filter_data * )data ;
139+ struct softfilter_thread_data * thr = (struct softfilter_thread_data * )thread_data ;
140+ int cbuf [NTSC_MAX_WIDTH * 2 ], lineI [NTSC_MAX_WIDTH * 2 ], lineQ [NTSC_MAX_WIDTH * 2 ], lineY [NTSC_MAX_WIDTH * 2 ];
141+ yiq_t yiq_cache [NTSC_MAX_WIDTH ];
142+ for (unsigned y = 0 ; y < thr -> height ; y ++ ) {
143+ void * dst = (uint8_t * )thr -> out_data + (y * thr -> out_pitch );
144+ ntsc_process_line (filt , thr , y , cbuf , lineI , lineQ , lineY , yiq_cache , dst );
145+ }
146+ }
147+
148+ static void * ntsc_create (const struct softfilter_config * config ,
149+ unsigned in_fmt , unsigned out_fmt , unsigned max_width , unsigned max_height ,
150+ unsigned threads , softfilter_simd_mask_t simd , void * userdata ) {
151+ struct filter_data * filt = (struct filter_data * )calloc (1 , sizeof (* filt ));
152+ if (!filt ) return NULL ;
153+ float h = 0.0f , s = 1.0f , sh = 0.0f , art = 0.5f , pal = 0.0f , atari = 0.0f , c64 = 0.0f ;
154+ if (config ) {
155+ config -> get_float (userdata , "hue" , & h , 0.0f );
156+ config -> get_float (userdata , "saturation" , & s , 1.0f );
157+ config -> get_float (userdata , "sharpness" , & sh , 0.0f );
158+ config -> get_float (userdata , "artifacts" , & art , 0.5f );
159+ config -> get_float (userdata , "pal_mode" , & pal , 0.0f );
160+ config -> get_float (userdata , "atari_mode" , & atari , 0.0f );
161+ config -> get_float (userdata , "c64_mode" , & c64 , 0.0f );
162+ }
163+ filt -> in_fmt = in_fmt ;
164+ filt -> pal_mode = (pal != 0.0f );
165+ filt -> atari_mode = (atari != 0.0f );
166+ filt -> c64_mode = (c64 != 0.0f );
167+
168+ // Determine phase count: Atari (16), NES (12), C64/Generic (8)
169+ if (filt -> atari_mode ) filt -> phase_count = 16 ;
170+ else if (filt -> c64_mode ) filt -> phase_count = 8 ;
171+ else filt -> phase_count = 12 ;
172+
173+ filt -> hue_cos = (int )(cos (h * M_PI / 180.0 ) * 256.0 );
174+ filt -> hue_sin = (int )(sin (h * M_PI / 180.0 ) * 256.0 );
175+ filt -> saturation = (int )(s * 256.0 );
176+ filt -> sharpness = (int )(sh * 256.0f );
177+ filt -> artifacts = (int )(art * 256.0f );
178+ filt -> huesat_identity = (h == 0.0f && s == 1.0f );
179+ for (int i = 0 ; i < filt -> phase_count ; i ++ ) {
180+ float rad = (float )(2.0 * M_PI * i / filt -> phase_count );
181+ filt -> lut_sin [i ] = (int )(sin (rad ) * 256.0f );
182+ filt -> lut_cos [i ] = (int )(cos (rad ) * 256.0f );
183+ }
184+ filt -> threads = threads ;
185+ filt -> workers = (struct softfilter_thread_data * )calloc (threads , sizeof (struct softfilter_thread_data ));
186+ return filt ;
187+ }
188+
189+ static void ntsc_packets (void * data , struct softfilter_work_packet * packets ,
190+ void * output , size_t output_stride , const void * input , unsigned width ,
191+ unsigned height , size_t input_stride ) {
192+ struct filter_data * filt = (struct filter_data * )data ;
193+ filt -> nes_frame_count ++ ;
194+ for (unsigned i = 0 ; i < filt -> threads ; i ++ ) {
195+ struct softfilter_thread_data * thr = & filt -> workers [i ];
196+ unsigned y_start = (height * i ) / filt -> threads , y_end = (height * (i + 1 )) / filt -> threads ;
197+ thr -> in_data = (const uint8_t * )input + y_start * input_stride ;
198+ thr -> out_data = (uint8_t * )output + y_start * output_stride ;
199+ thr -> in_pitch = input_stride ; thr -> out_pitch = output_stride ;
200+ thr -> width = width ; thr -> height = y_end - y_start ;
201+ thr -> first = (int )y_start ;
202+ packets [i ].work = ntsc_work_cb ;
203+ packets [i ].thread_data = thr ;
204+ }
205+ }
206+
207+ static void ntsc_destroy (void * data ) {
208+ struct filter_data * f = (struct filter_data * )data ;
209+ if (f ) { free (f -> workers ); free (f ); }
210+ }
211+
212+ static void ntsc_output (void * data , unsigned * ow , unsigned * oh , unsigned w , unsigned h ) { * ow = w * 2 ; * oh = h ; }
213+ static unsigned ntsc_query_num_threads (void * data ) { return ((struct filter_data * )data )-> threads ; }
214+ static unsigned ntsc_input_fmts (void ) { return SOFTFILTER_FMT_XRGB8888 | SOFTFILTER_FMT_RGB565 ; }
215+ static unsigned ntsc_output_fmts (unsigned fmt ) { return fmt ; }
216+
217+ static const struct softfilter_implementation ntsc_impl = {
218+ ntsc_input_fmts , ntsc_output_fmts , ntsc_create , ntsc_destroy ,
219+ ntsc_query_num_threads , ntsc_output , ntsc_packets , SOFTFILTER_API_VERSION , "NTSC-Multi-System" , "ntsc" ,
220+ };
221+
222+ const struct softfilter_implementation * softfilter_get_implementation (softfilter_simd_mask_t simd ) {
223+ (void )simd ; return & ntsc_impl ;
224+ }
0 commit comments