Skip to content

Commit 8719da8

Browse files
authored
Add files via upload
1 parent 56a5396 commit 8719da8

2 files changed

Lines changed: 244 additions & 0 deletions

File tree

gfx/video_filters/ntsc.c

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
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+
}

gfx/video_filters/ntsc.filt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
filter = ntsc
2+
3+
# --- System Selection ---
4+
# Set only one to 1.0
5+
# If both are 0.0, defaults to 12-phase NES logic
6+
7+
ntsc_atari_mode = 0.0 # 16-phase Atari 2600 logic
8+
ntsc_c64_mode = 0.0 # 8-phase Commodore 64 logic
9+
10+
11+
# --- Global Settings ---
12+
# 1.0 for PAL/50Hz stability
13+
14+
ntsc_pal_mode = 0.0
15+
ntsc_hue = 0.0
16+
ntsc_saturation = 1.0
17+
18+
# Increase for stronger C64 color bleeding
19+
ntsc_artifacts = 0.0
20+
ntsc_sharpness = 0.3

0 commit comments

Comments
 (0)