From 13d279d1e61fc5fa165747e5508c1202d25ba90c Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 06:20:49 +0200 Subject: [PATCH 01/22] (Metal) Cleanups --- gfx/drivers/metal.m | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/gfx/drivers/metal.m b/gfx/drivers/metal.m index 7ba05c1ba25..e3e18b0e481 100644 --- a/gfx/drivers/metal.m +++ b/gfx/drivers/metal.m @@ -561,19 +561,17 @@ static bool metal_display_supports_edr(void) if (@available(macOS 10.15, *)) { NSScreen *screen = [NSScreen mainScreen]; - if (!screen) - return false; /* Potential, not current: the value reflects what the display *could* * produce if EDR were enabled, not what's being used right now. * That's the right signal for "is HDR an available mode". SDR-only * displays return exactly 1.0. */ - return screen.maximumPotentialExtendedDynamicRangeColorComponentValue > 1.0; + if (screen) + return screen.maximumPotentialExtendedDynamicRangeColorComponentValue > 1.0; } - return false; #elif defined(HAVE_COCOATOUCH) /* TARGET_OS_TV / TARGET_OS_IOS are always defined to 0 or 1, not * just present — have to test the value, not defined-ness. */ -# if TARGET_OS_TV +#if TARGET_OS_TV /* tvOS: no NSScreen/UIScreen EDR API parallel to macOS. The device * itself (Apple TV 4K) advertises HDR capability via AVDisplayCriteria * but that's AVFoundation, not a layer we can plumb into here without @@ -582,20 +580,16 @@ static bool metal_display_supports_edr(void) * is only shipping on HDR-capable hardware (Apple TV 4K). */ if (@available(tvOS 16.0, *)) return true; - return false; -# else +#else if (@available(iOS 16.0, *)) { UIScreen *screen = [UIScreen mainScreen]; - if (!screen) - return false; - return screen.potentialEDRHeadroom > 1.0; + if (screen) + return screen.potentialEDRHeadroom > 1.0; } - return false; -# endif -#else - return false; #endif +#endif + return false; } #endif /* METAL_HDR_AVAILABLE */ @@ -5602,7 +5596,7 @@ static bool metal_suppress_screensaver(void *data, bool disable) } static bool metal_set_shader(void *data, - enum rarch_shader_type type, const char *path) + enum rarch_shader_type type, const char *path) { #if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS) MetalDriver *md = (__bridge MetalDriver *)data; From 8645f04fa417737b4feb9700982fc6a1f3ec51dd Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 06:41:47 +0200 Subject: [PATCH 02/22] scaler/pixconv: fix conv_rgba4444_argb8888 MMX alpha handling The MMX fast path of conv_rgba4444_argb8888 hardcoded the alpha channel to 0xff instead of extracting it from the source's low nibble. The scalar fallback (lines 488-501) correctly does 'a = (col >> 0) & 0xf; a = (a << 4) | a' to expand the 4-bit source alpha into 8 bits. The MMX kernel kept the 'const __m64 a = _mm_set1_pi16(0x00ff)' constant from the sibling conv_rgb565_argb8888 / conv_0rgb1555_argb8888 functions (whose 16-bit sources have no alpha channel), so on x86 hosts where __MMX__ is defined (which includes all x86_64 builds, since gcc enables MMX by default alongside SSE2) RGBA4444 input was silently converted with full opacity regardless of source alpha. Same shape of bug as commit 9403719 (conv_rgb565_abgr8888 SSE2): SIMD kernel inherited from a sibling without adapting all the needed steps to the new format. Fix: extract alpha from the low nibble (in & 0x000f), expand to 8 bits via _mm_mullo_pi16(a, 0x0011) which computes a * 17 == (a << 4) | a for any 4-bit value. Verified bit-exact against the scalar fallback for every possible RGBA4444 input. Brute-force test of all 65,536 16-bit values: before fix: 61,440 of 65,536 outputs differ from scalar (94%; all inputs whose low nibble != 0xf produce wrong alpha) after fix: 0 of 65,536 differ Caller audit: hit by gfx/drivers/gl1.c:1810 (RGUI menu fallback expansion when GL_EXT_packed_pixels is unsupported -- triggers on ancient OpenGL implementations and the Vita gl1 build) and via the scaler chain when SCALER_FMT_RGBA4444 is paired with SCALER_FMT_ARGB8888 (libretro-common/gfx/scaler/scaler.c:183 and :232). On x86_64 the MMX path is hot in both contexts. --- libretro-common/gfx/scaler/pixconv.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libretro-common/gfx/scaler/pixconv.c b/libretro-common/gfx/scaler/pixconv.c index 909cdbf153d..2563c046027 100644 --- a/libretro-common/gfx/scaler/pixconv.c +++ b/libretro-common/gfx/scaler/pixconv.c @@ -442,10 +442,11 @@ void conv_rgba4444_argb8888(void *output_, const void *input_, const __m64 pix_mask_r = _mm_set1_pi16(0xf << 10); const __m64 pix_mask_g = _mm_set1_pi16(0xf << 8); const __m64 pix_mask_b = _mm_set1_pi16(0xf << 8); + const __m64 pix_mask_a = _mm_set1_pi16(0x000f); const __m64 mul16_r = _mm_set1_pi16(0x0440); const __m64 mul16_g = _mm_set1_pi16(0x1100); const __m64 mul16_b = _mm_set1_pi16(0x1100); - const __m64 a = _mm_set1_pi16(0x00ff); + const __m64 mul_a = _mm_set1_pi16(0x0011); int max_width = width - 3; #endif @@ -463,10 +464,15 @@ void conv_rgba4444_argb8888(void *output_, const void *input_, __m64 r = _mm_and_si64(_mm_srli_pi16(in, 2), pix_mask_r); __m64 g = _mm_and_si64(in, pix_mask_g); __m64 b = _mm_and_si64(_mm_slli_pi16(in, 4), pix_mask_b); + /* Source is rgba4444 -- alpha is the low nibble of each 16-bit + * input word. Expand 4-bit -> 8-bit via a*0x11 (== a<<4 | a), + * matching the scalar fallback. */ + __m64 a = _mm_and_si64(in, pix_mask_a); r = _mm_mulhi_pi16(r, mul16_r); g = _mm_mulhi_pi16(g, mul16_g); b = _mm_mulhi_pi16(b, mul16_b); + a = _mm_mullo_pi16(a, mul_a); res_lo_bg = _mm_unpacklo_pi8(b, g); res_hi_bg = _mm_unpackhi_pi8(b, g); From 181b550689911b6b49838b30bf4d7ddb70bce4d5 Mon Sep 17 00:00:00 2001 From: metallic77 <43163462+metallic77@users.noreply.github.com> Date: Fri, 1 May 2026 08:25:51 +0300 Subject: [PATCH 03/22] Add files via upload (#18987) * Add files via upload * Update crop_borders.c * Add EMMIR NTSC default "crt" variant compiles and runs perfectly fine on linux Mint. * Add files via upload --- gfx/video_filters/crop_borders.c | 193 +++++ gfx/video_filters/crop_borders.filt | 4 + gfx/video_filters/ntsc.c | 224 ++++++ gfx/video_filters/ntsc.filt | 20 + gfx/video_filters/ntsc_crt_filter/Makefile | 37 + gfx/video_filters/ntsc_crt_filter/crt_core.c | 666 ++++++++++++++++++ gfx/video_filters/ntsc_crt_filter/crt_core.h | 145 ++++ gfx/video_filters/ntsc_crt_filter/crt_ntsc.c | 331 +++++++++ gfx/video_filters/ntsc_crt_filter/crt_ntsc.h | 130 ++++ gfx/video_filters/ntsc_crt_filter/ntsc_crt.c | 388 ++++++++++ .../ntsc_crt_filter/ntsc_crt.filt | 25 + 11 files changed, 2163 insertions(+) create mode 100644 gfx/video_filters/crop_borders.c create mode 100644 gfx/video_filters/crop_borders.filt create mode 100644 gfx/video_filters/ntsc.c create mode 100644 gfx/video_filters/ntsc.filt create mode 100644 gfx/video_filters/ntsc_crt_filter/Makefile create mode 100644 gfx/video_filters/ntsc_crt_filter/crt_core.c create mode 100644 gfx/video_filters/ntsc_crt_filter/crt_core.h create mode 100644 gfx/video_filters/ntsc_crt_filter/crt_ntsc.c create mode 100644 gfx/video_filters/ntsc_crt_filter/crt_ntsc.h create mode 100644 gfx/video_filters/ntsc_crt_filter/ntsc_crt.c create mode 100644 gfx/video_filters/ntsc_crt_filter/ntsc_crt.filt diff --git a/gfx/video_filters/crop_borders.c b/gfx/video_filters/crop_borders.c new file mode 100644 index 00000000000..7c7e5292c39 --- /dev/null +++ b/gfx/video_filters/crop_borders.c @@ -0,0 +1,193 @@ +#include "softfilter.h" +#include +#include + +#ifdef RARCH_INTERNAL +#define softfilter_get_implementation crop_borders_get_implementation +#define softfilter_thread_data crop_borders_softfilter_thread_data +#define filter_data crop_borders_filter_data +#endif + +struct softfilter_thread_data +{ + void *out_data; + const void *in_data; + size_t out_pitch; + size_t in_pitch; + unsigned width; + unsigned height; +}; + +struct filter_data +{ + struct softfilter_thread_data *workers; + unsigned in_fmt; + float crop_x; + float crop_y; +}; + +static unsigned crop_borders_generic_input_fmts(void) +{ + return SOFTFILTER_FMT_RGB565 | SOFTFILTER_FMT_XRGB8888; +} + +static unsigned crop_borders_generic_output_fmts(unsigned input_fmts) +{ + return input_fmts; +} + +static unsigned crop_borders_generic_threads(void *data) +{ + return 1; +} + +static void crop_borders_initialize(struct filter_data *filt, + const struct softfilter_config *config, + void *userdata) +{ + /* RetroArch wull look at .filt for: crop_borders_crop_x */ + config->get_float(userdata, "crop_x", &filt->crop_x, 0.0f); + config->get_float(userdata, "crop_y", &filt->crop_y, 0.0f); +} + +static void *crop_borders_generic_create(const struct softfilter_config *config, + unsigned in_fmt, unsigned out_fmt, + unsigned max_width, unsigned max_height, + unsigned threads, softfilter_simd_mask_t simd, void *userdata) +{ + struct filter_data *filt = (struct filter_data*)calloc(1, sizeof(*filt)); + if (!filt) return NULL; + + if (!(filt->workers = (struct softfilter_thread_data*)calloc(1, sizeof(struct softfilter_thread_data)))) + { + free(filt); + return NULL; + } + + filt->in_fmt = in_fmt; + + crop_borders_initialize(filt, config, userdata); + + return filt; +} + +static void crop_borders_generic_output(void *data, + unsigned *out_width, unsigned *out_height, + unsigned width, unsigned height) +{ + *out_width = width; + *out_height = height; +} + +static void crop_borders_generic_destroy(void *data) +{ + struct filter_data *filt = (struct filter_data*)data; + if (!filt) return; + free(filt->workers); + free(filt); +} + +/* Rendering Logic with Float Coordinates */ +static void crop_borders_work_cb_xrgb8888(void *data, void *thread_data) +{ + struct filter_data *filt = (struct filter_data*)data; + struct softfilter_thread_data *thr = (struct softfilter_thread_data*)thread_data; + const uint32_t *input = (const uint32_t*)thr->in_data; + uint32_t *output = (uint32_t*)thr->out_data; + unsigned in_stride = (unsigned)(thr->in_pitch >> 2); + unsigned out_stride = (unsigned)(thr->out_pitch >> 2); + + float visible_w = (float)thr->width - (filt->crop_x * 2.0f); + float visible_h = (float)thr->height - (filt->crop_y * 2.0f); + + if (visible_w <= 0.0f || visible_h <= 0.0f) return; + + float step_x = visible_w / (float)thr->width; + float step_y = visible_h / (float)thr->height; + + for (unsigned y = 0; y < thr->height; y++) + { + unsigned i_y = (unsigned)(filt->crop_y + (y * step_y)); + for (unsigned x = 0; x < thr->width; x++) + { + unsigned i_x = (unsigned)(filt->crop_x + (x * step_x)); + output[y * out_stride + x] = input[i_y * in_stride + i_x]; + } + } +} + +static void crop_borders_work_cb_rgb565(void *data, void *thread_data) +{ + struct filter_data *filt = (struct filter_data*)data; + struct softfilter_thread_data *thr = (struct softfilter_thread_data*)thread_data; + const uint16_t *input = (const uint16_t*)thr->in_data; + uint16_t *output = (uint16_t*)thr->out_data; + unsigned in_stride = (unsigned)(thr->in_pitch >> 1); + unsigned out_stride = (unsigned)(thr->out_pitch >> 1); + + float visible_w = (float)thr->width - (filt->crop_x * 2.0f); + float visible_h = (float)thr->height - (filt->crop_y * 2.0f); + + if (visible_w <= 0.0f || visible_h <= 0.0f) return; + + float step_x = visible_w / (float)thr->width; + float step_y = visible_h / (float)thr->height; + + for (unsigned y = 0; y < thr->height; y++) + { + unsigned i_y = (unsigned)(filt->crop_y + (y * step_y)); + for (unsigned x = 0; x < thr->width; x++) + { + unsigned i_x = (unsigned)(filt->crop_x + (x * step_x)); + output[y * out_stride + x] = input[i_y * in_stride + i_x]; + } + } +} + +static void crop_borders_generic_packets(void *data, + struct softfilter_work_packet *packets, + void *output, size_t output_stride, + const void *input, unsigned width, unsigned height, size_t input_stride) +{ + struct filter_data *filt = (struct filter_data*)data; + struct softfilter_thread_data *thr = &filt->workers[0]; + + thr->out_data = output; + thr->in_data = input; + thr->out_pitch = output_stride; + thr->in_pitch = input_stride; + thr->width = width; + thr->height = height; + + if (filt->in_fmt == SOFTFILTER_FMT_RGB565) + packets[0].work = crop_borders_work_cb_rgb565; + else + packets[0].work = crop_borders_work_cb_xrgb8888; + + packets[0].thread_data = thr; +} + +static const struct softfilter_implementation crop_borders_generic = { + crop_borders_generic_input_fmts, + crop_borders_generic_output_fmts, + crop_borders_generic_create, + crop_borders_generic_destroy, + crop_borders_generic_threads, + crop_borders_generic_output, + crop_borders_generic_packets, + SOFTFILTER_API_VERSION, + "Crop Borders", + "crop_borders", +}; + +const struct softfilter_implementation *softfilter_get_implementation(softfilter_simd_mask_t simd) +{ + (void)simd; + return &crop_borders_generic; +} + +#ifdef RARCH_INTERNAL +#undef softfilter_get_implementation +#undef softfilter_thread_data +#undef filter_data +#endif diff --git a/gfx/video_filters/crop_borders.filt b/gfx/video_filters/crop_borders.filt new file mode 100644 index 00000000000..6322c33fa31 --- /dev/null +++ b/gfx/video_filters/crop_borders.filt @@ -0,0 +1,4 @@ +filter = crop_borders + +crop_borders_crop_x = 8.0 +crop_borders_crop_y = 8.0 \ No newline at end of file diff --git a/gfx/video_filters/ntsc.c b/gfx/video_filters/ntsc.c new file mode 100644 index 00000000000..5a90c2d6911 --- /dev/null +++ b/gfx/video_filters/ntsc.c @@ -0,0 +1,224 @@ +#include "softfilter.h" +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifdef RARCH_INTERNAL +#define softfilter_get_implementation ntsc_get_implementation +#define softfilter_thread_data ntsc_softfilter_thread_data +#define filter_data ntsc_filter_data +#endif + +#define NTSC_SCALE_X 2 +#define NTSC_SCALE_Y 1 +#define NTSC_MAX_WIDTH 1024 +#define PHASE_MAX 16 // Max phases for Atari 2600 + +typedef struct { int Y, I, Q; } yiq_t; + +struct softfilter_thread_data { + void *out_data; + const void *in_data; + size_t out_pitch; + size_t in_pitch; + unsigned width; + unsigned height; + int first; +}; + +struct filter_data { + unsigned threads; + struct softfilter_thread_data *workers; + unsigned in_fmt; + int hue_cos, hue_sin; + int saturation, sharpness, artifacts; + int huesat_identity, pal_mode, atari_mode, c64_mode; + int phase_count, nes_frame_count; + int lut_sin[PHASE_MAX], lut_cos[PHASE_MAX]; +}; + +static inline void rgb_to_yiq(int r, int g, int b, int *Y, int *I, int *Q) { + *Y = ( 77*r + 150*g + 29*b) >> 8; + *I = ( 157*r - 132*g - 26*b) >> 8; + *Q = ( -38*r - 74*g + 112*b) >> 8; +} + +static inline void yiq_to_rgb(int Y, int I, int Q, int *r, int *g, int *b) { + int rv = Y + ((292*I) >> 8); + int gv = Y - ((149*I) >> 8) - ((101*Q) >> 8); + int bv = Y + ((520*Q) >> 8); + *r = (rv < 0) ? 0 : (rv > 255 ? 255 : rv); + *g = (gv < 0) ? 0 : (gv > 255 ? 255 : gv); + *b = (bv < 0) ? 0 : (bv > 255 ? 255 : bv); +} + +static void ntsc_process_line(const struct filter_data *filt, + const struct softfilter_thread_data *thr, unsigned y, + int *cbuf, int *lineI, int *lineQ, int *lineY, yiq_t *yiq_cache, + void *dst_ptr) { + unsigned width = thr->width; + unsigned ow = width * 2; + int phases = filt->phase_count; + + // C64 uses 2-phase steps per output pixel if sampled at 8 phases + int phase_step = (filt->c64_mode) ? 2 : 4; + int frame_offset = (filt->nes_frame_count % 2) * (phases / 2); + int line_mult = (filt->pal_mode) ? (phase_step + 1) : phase_step; + int line_phase = (frame_offset + ((thr->first + (int)y) * line_mult)) % phases; + + for (unsigned x = 0; x < width; x++) { + int r, g, b, Y, I, Q; + if (filt->in_fmt == SOFTFILTER_FMT_RGB565) { + uint16_t p = ((uint16_t*)thr->in_data)[y * (thr->in_pitch/2) + x]; + r = ((p >> 11) & 0x1f) << 3; g = ((p >> 5) & 0x3f) << 2; b = (p & 0x1f) << 3; + } else { + uint32_t p = ((uint32_t*)thr->in_data)[y * (thr->in_pitch/4) + x]; + r = (p >> 16) & 0xFF; g = (p >> 8) & 0xFF; b = p & 0xFF; + } + rgb_to_yiq(r, g, b, &Y, &I, &Q); + yiq_cache[x].Y = Y; yiq_cache[x].I = I; yiq_cache[x].Q = Q; + + for (int p_idx = 0; p_idx < 2; p_idx++) { + int ph = (line_phase + (x * phase_step * 2) + (p_idx * phase_step)) % phases; + cbuf[x * 2 + p_idx] = Y + ((I * filt->lut_cos[ph] + Q * filt->lut_sin[ph]) >> 8); + } + } + + for (unsigned x = 0; x < ow; x++) { + int accI = 0, accQ = 0; + int taps = (filt->atari_mode) ? 8 : (filt->c64_mode ? 4 : 6); + + for (int t = -taps; t < taps; t++) { + int idx = (x + t < 0) ? 0 : (x + t >= (int)ow ? (int)ow - 1 : x + t); + int ph = (line_phase + (x * phase_step) + (t * phase_step)) % phases; + accI += cbuf[idx] * filt->lut_cos[ph]; + accQ += cbuf[idx] * filt->lut_sin[ph]; + } + lineI[x] = accI / (taps * 256); lineQ[x] = accQ / (taps * 256); + + int i_m2 = (x > 1) ? (int)x - 2 : 0, i_m1 = (x > 0) ? (int)x - 1 : 0; + int i_p1 = (x < ow - 1) ? (int)x + 1 : (int)ow - 1, i_p2 = (x < ow - 2) ? (int)x + 2 : (int)ow - 1; + + // Notch filter optimized for system-specific bandwidth + int notchedY = (filt->c64_mode) ? + (cbuf[i_m1] + (cbuf[x] << 1) + cbuf[i_p1]) >> 2 : + (cbuf[i_m2] + (cbuf[i_m1] << 2) + (cbuf[x] * 6) + (cbuf[i_p1] << 2) + cbuf[i_p2]) >> 4; + + lineY[x] = ((notchedY * (256 - filt->artifacts)) + (cbuf[x] * filt->artifacts)) >> 8; + } + + for (unsigned x = 0; x < ow; x++) { + int Y = lineY[x], I = lineI[x], Q = lineQ[x]; + if (filt->sharpness > 0) { + int x_m1 = (x > 0) ? (int)x - 1 : 0, x_p1 = (x < ow - 1) ? (int)x + 1 : (int)ow - 1; + int edge = (Y << 1) - (lineY[x_m1] + lineY[x_p1]); + Y += (edge * filt->sharpness) >> 9; + Y = (Y < 0) ? 0 : (Y > 255 ? 255 : Y); + } + if (!filt->huesat_identity) { + int Ir = (I * filt->hue_cos - Q * filt->hue_sin) >> 8; + int Qr = (I * filt->hue_sin + Q * filt->hue_cos) >> 8; + I = (Ir * filt->saturation) >> 8; Q = (Qr * filt->saturation) >> 8; + } + int r, g, b; + yiq_to_rgb(Y, I, Q, &r, &g, &b); + if (filt->in_fmt == SOFTFILTER_FMT_RGB565) + ((uint16_t*)dst_ptr)[x] = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + else + ((uint32_t*)dst_ptr)[x] = 0xFF000000u | (r << 16) | (g << 8) | b; + } +} + +static void ntsc_work_cb(void *data, void *thread_data) { + struct filter_data *filt = (struct filter_data*)data; + struct softfilter_thread_data *thr = (struct softfilter_thread_data*)thread_data; + int cbuf[NTSC_MAX_WIDTH * 2], lineI[NTSC_MAX_WIDTH * 2], lineQ[NTSC_MAX_WIDTH * 2], lineY[NTSC_MAX_WIDTH * 2]; + yiq_t yiq_cache[NTSC_MAX_WIDTH]; + for (unsigned y = 0; y < thr->height; y++) { + void *dst = (uint8_t*)thr->out_data + (y * thr->out_pitch); + ntsc_process_line(filt, thr, y, cbuf, lineI, lineQ, lineY, yiq_cache, dst); + } +} + +static void *ntsc_create(const struct softfilter_config *config, + unsigned in_fmt, unsigned out_fmt, unsigned max_width, unsigned max_height, + unsigned threads, softfilter_simd_mask_t simd, void *userdata) { + struct filter_data *filt = (struct filter_data*)calloc(1, sizeof(*filt)); + if (!filt) return NULL; + float h = 0.0f, s = 1.0f, sh = 0.0f, art = 0.5f, pal = 0.0f, atari = 0.0f, c64 = 0.0f; + if (config) { + config->get_float(userdata, "hue", &h, 0.0f); + config->get_float(userdata, "saturation", &s, 1.0f); + config->get_float(userdata, "sharpness", &sh, 0.0f); + config->get_float(userdata, "artifacts", &art, 0.5f); + config->get_float(userdata, "pal_mode", &pal, 0.0f); + config->get_float(userdata, "atari_mode", &atari, 0.0f); + config->get_float(userdata, "c64_mode", &c64, 0.0f); + } + filt->in_fmt = in_fmt; + filt->pal_mode = (pal != 0.0f); + filt->atari_mode = (atari != 0.0f); + filt->c64_mode = (c64 != 0.0f); + + // Determine phase count: Atari (16), NES (12), C64/Generic (8) + if (filt->atari_mode) filt->phase_count = 16; + else if (filt->c64_mode) filt->phase_count = 8; + else filt->phase_count = 12; + + filt->hue_cos = (int)(cos(h * M_PI / 180.0) * 256.0); + filt->hue_sin = (int)(sin(h * M_PI / 180.0) * 256.0); + filt->saturation = (int)(s * 256.0); + filt->sharpness = (int)(sh * 256.0f); + filt->artifacts = (int)(art * 256.0f); + filt->huesat_identity = (h == 0.0f && s == 1.0f); + for (int i = 0; i < filt->phase_count; i++) { + float rad = (float)(2.0 * M_PI * i / filt->phase_count); + filt->lut_sin[i] = (int)(sin(rad) * 256.0f); + filt->lut_cos[i] = (int)(cos(rad) * 256.0f); + } + filt->threads = threads; + filt->workers = (struct softfilter_thread_data*)calloc(threads, sizeof(struct softfilter_thread_data)); + return filt; +} + +static void ntsc_packets(void *data, struct softfilter_work_packet *packets, + void *output, size_t output_stride, const void *input, unsigned width, + unsigned height, size_t input_stride) { + struct filter_data *filt = (struct filter_data*)data; + filt->nes_frame_count++; + for (unsigned i = 0; i < filt->threads; i++) { + struct softfilter_thread_data *thr = &filt->workers[i]; + unsigned y_start = (height * i) / filt->threads, y_end = (height * (i + 1)) / filt->threads; + thr->in_data = (const uint8_t*)input + y_start * input_stride; + thr->out_data = (uint8_t*)output + y_start * output_stride; + thr->in_pitch = input_stride; thr->out_pitch = output_stride; + thr->width = width; thr->height = y_end - y_start; + thr->first = (int)y_start; + packets[i].work = ntsc_work_cb; + packets[i].thread_data = thr; + } +} + +static void ntsc_destroy(void *data) { + struct filter_data *f = (struct filter_data*)data; + if (f) { free(f->workers); free(f); } +} + +static void ntsc_output(void *data, unsigned *ow, unsigned *oh, unsigned w, unsigned h) { *ow = w*2; *oh = h; } +static unsigned ntsc_query_num_threads(void *data) { return ((struct filter_data*)data)->threads; } +static unsigned ntsc_input_fmts(void) { return SOFTFILTER_FMT_XRGB8888 | SOFTFILTER_FMT_RGB565; } +static unsigned ntsc_output_fmts(unsigned fmt) { return fmt; } + +static const struct softfilter_implementation ntsc_impl = { + ntsc_input_fmts, ntsc_output_fmts, ntsc_create, ntsc_destroy, + ntsc_query_num_threads, ntsc_output, ntsc_packets, SOFTFILTER_API_VERSION, "NTSC-Multi-System", "ntsc", +}; + +const struct softfilter_implementation *softfilter_get_implementation(softfilter_simd_mask_t simd) { + (void)simd; return &ntsc_impl; +} \ No newline at end of file diff --git a/gfx/video_filters/ntsc.filt b/gfx/video_filters/ntsc.filt new file mode 100644 index 00000000000..9ef5c4c7a3c --- /dev/null +++ b/gfx/video_filters/ntsc.filt @@ -0,0 +1,20 @@ +filter = ntsc + +# --- System Selection --- +# Set only one to 1.0 +# If both are 0.0, defaults to 12-phase NES logic + +ntsc_atari_mode = 0.0 # 16-phase Atari 2600 logic +ntsc_c64_mode = 0.0 # 8-phase Commodore 64 logic + + +# --- Global Settings --- +# 1.0 for PAL/50Hz stability + +ntsc_pal_mode = 0.0 +ntsc_hue = 0.0 +ntsc_saturation = 1.0 + +# Increase for stronger C64 color bleeding +ntsc_artifacts = 0.0 +ntsc_sharpness = 0.3 \ No newline at end of file diff --git a/gfx/video_filters/ntsc_crt_filter/Makefile b/gfx/video_filters/ntsc_crt_filter/Makefile new file mode 100644 index 00000000000..483351d0dd0 --- /dev/null +++ b/gfx/video_filters/ntsc_crt_filter/Makefile @@ -0,0 +1,37 @@ +# Makefile for ntsc_crt RetroArch softfilter +# +# Usage: +# make -> builds ntsc_crt.so (Linux) +# make WINDOWS=1 -> cross-compiles ntsc_crt.dll (requires mingw-w64) +# make install -> copies .so + .filt to FILTERDIR + +CC = gcc +CFLAGS = -O2 -std=c99 -Wall -fPIC -DCRT_SYSTEM=0 -I.. +LDFLAGS = -shared -lm + +# Adjust this to your RetroArch installation path +FILTERDIR = /usr/share/retroarch/filters/video + +SRC = ntsc_crt.c crt_core.c crt_ntsc.c +TARGET = ntsc_crt.so + +ifdef WINDOWS + CC = x86_64-w64-mingw32-gcc + TARGET = ntsc_crt.dll + CFLAGS += -D_WIN32 +endif + +all: $(TARGET) + +$(TARGET): $(SRC) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ + +install: $(TARGET) + install -d $(FILTERDIR) + install -m 644 $(TARGET) $(FILTERDIR)/ + install -m 644 ntsc_crt.filt $(FILTERDIR)/ + +clean: + rm -f $(TARGET) + +.PHONY: all install clean diff --git a/gfx/video_filters/ntsc_crt_filter/crt_core.c b/gfx/video_filters/ntsc_crt_filter/crt_core.c new file mode 100644 index 00000000000..3dfed0cec2f --- /dev/null +++ b/gfx/video_filters/ntsc_crt_filter/crt_core.c @@ -0,0 +1,666 @@ +/*****************************************************************************/ +/* + * NTSC/CRT - integer-only NTSC video signal encoding / decoding emulation + * + * by EMMIR 2018-2023 + * + * YouTube: https://www.youtube.com/@EMMIR_KC/videos + * Discord: https://discord.com/invite/hdYctSmyQJ + */ +/*****************************************************************************/ +#include "crt_core.h" + +#include +#include + +/* ensure negative values for x get properly modulo'd */ +#define POSMOD(x, n) (((x) % (n) + (n)) % (n)) + +static int sigpsin15[18] = { /* significant points on sine wave (15-bit) */ + 0x0000, + 0x0c88,0x18f8,0x2528,0x30f8,0x3c50,0x4718,0x5130,0x5a80, + 0x62f0,0x6a68,0x70e0,0x7640,0x7a78,0x7d88,0x7f60,0x8000, + 0x7f60 +}; + +static int +sintabil8(int n) +{ + int f, i, a, b; + + /* looks scary but if you don't change T14_2PI + * it won't cause out of bounds memory reads + */ + f = n >> 0 & 0xff; + i = n >> 8 & 0xff; + a = sigpsin15[i]; + b = sigpsin15[i + 1]; + return (a + ((b - a) * f >> 8)); +} + +/* 14-bit interpolated sine/cosine */ +extern void +crt_sincos14(int *s, int *c, int n) +{ + int h; + + n &= T14_MASK; + h = n & ((T14_2PI >> 1) - 1); + + if (h > ((T14_2PI >> 2) - 1)) { + *c = -sintabil8(h - (T14_2PI >> 2)); + *s = sintabil8((T14_2PI >> 1) - h); + } else { + *c = sintabil8((T14_2PI >> 2) - h); + *s = sintabil8(h); + } + if (n > ((T14_2PI >> 1) - 1)) { + *c = -*c; + *s = -*s; + } +} + +extern int +crt_bpp4fmt(int format) +{ + switch (format) { + case CRT_PIX_FORMAT_RGB: + case CRT_PIX_FORMAT_BGR: + return 3; + case CRT_PIX_FORMAT_ARGB: + case CRT_PIX_FORMAT_RGBA: + case CRT_PIX_FORMAT_ABGR: + case CRT_PIX_FORMAT_BGRA: + return 4; + default: + return 0; + } +} + +/*****************************************************************************/ +/********************************* FILTERS ***********************************/ +/*****************************************************************************/ + +/* convolution is much faster but the EQ looks softer, more authentic, and more analog */ +#define USE_CONVOLUTION 0 +#define USE_7_SAMPLE_KERNEL 1 +#define USE_6_SAMPLE_KERNEL 0 +#define USE_5_SAMPLE_KERNEL 0 + +#if (CRT_CC_SAMPLES != 4) +/* the current convolutions do not filter properly at > 4 samples */ +#undef USE_CONVOLUTION +#define USE_CONVOLUTION 0 +#endif + +#if USE_CONVOLUTION + +/* NOT 3 band equalizer, faster convolution instead. + * eq function names preserved to keep code clean + */ +static struct EQF { + int h[7]; +} eqY, eqI, eqQ; + +/* params unused to keep the function the same */ +static void +init_eq(struct EQF *f, + int f_lo, int f_hi, int rate, + int g_lo, int g_mid, int g_hi) +{ + memset(f, 0, sizeof(struct EQF)); +} + +static void +reset_eq(struct EQF *f) +{ + memset(f->h, 0, sizeof(f->h)); +} + +static int +eqf(struct EQF *f, int s) +{ + int i; + int *h = f->h; + + for (i = 6; i > 0; i--) { + h[i] = h[i - 1]; + } + h[0] = s; +#if USE_7_SAMPLE_KERNEL + /* index : 0 1 2 3 4 5 6 */ + /* weight: 1 4 7 8 7 4 1 */ + return (s + h[6] + ((h[1] + h[5]) * 4) + ((h[2] + h[4]) * 7) + (h[3] * 8)) >> 5; +#elif USE_6_SAMPLE_KERNEL + /* index : 0 1 2 3 4 5 */ + /* weight: 1 3 4 4 3 1 */ + return (s + h[5] + 3 * (h[1] + h[4]) + 4 * (h[2] + h[3])) >> 4; +#elif USE_5_SAMPLE_KERNEL + /* index : 0 1 2 3 4 */ + /* weight: 1 2 2 2 1 */ + return (s + h[4] + ((h[1] + h[2] + h[3]) << 1)) >> 3; +#else + /* index : 0 1 2 3 */ + /* weight: 1 1 1 1*/ + return (s + h[3] + h[1] + h[2]) >> 2; +#endif +} + +#else + +#define HISTLEN 3 +#define HISTOLD (HISTLEN - 1) /* oldest entry */ +#define HISTNEW 0 /* newest entry */ + +#define EQ_P 16 /* if changed, the gains will need to be adjusted */ +#define EQ_R (1 << (EQ_P - 1)) /* rounding */ +/* three band equalizer */ +static struct EQF { + int lf, hf; /* fractions */ + int g[3]; /* gains */ + int fL[4]; + int fH[4]; + int h[HISTLEN]; /* history */ +} eqY, eqI, eqQ; + +/* f_lo - low cutoff frequency + * f_hi - high cutoff frequency + * rate - sampling rate + * g_lo, g_mid, g_hi - gains + */ +static void +init_eq(struct EQF *f, + int f_lo, int f_hi, int rate, + int g_lo, int g_mid, int g_hi) +{ + int sn, cs; + + memset(f, 0, sizeof(struct EQF)); + + f->g[0] = g_lo; + f->g[1] = g_mid; + f->g[2] = g_hi; + + crt_sincos14(&sn, &cs, T14_PI * f_lo / rate); +#if (EQ_P >= 15) + f->lf = 2 * (sn << (EQ_P - 15)); +#else + f->lf = 2 * (sn >> (15 - EQ_P)); +#endif + crt_sincos14(&sn, &cs, T14_PI * f_hi / rate); +#if (EQ_P >= 15) + f->hf = 2 * (sn << (EQ_P - 15)); +#else + f->hf = 2 * (sn >> (15 - EQ_P)); +#endif +} + +static void +reset_eq(struct EQF *f) +{ + memset(f->fL, 0, sizeof(f->fL)); + memset(f->fH, 0, sizeof(f->fH)); + memset(f->h, 0, sizeof(f->h)); +} + +static int +eqf(struct EQF *f, int s) +{ + int i, r[3]; + + f->fL[0] += (f->lf * (s - f->fL[0]) + EQ_R) >> EQ_P; + f->fH[0] += (f->hf * (s - f->fH[0]) + EQ_R) >> EQ_P; + + for (i = 1; i < 4; i++) { + f->fL[i] += (f->lf * (f->fL[i - 1] - f->fL[i]) + EQ_R) >> EQ_P; + f->fH[i] += (f->hf * (f->fH[i - 1] - f->fH[i]) + EQ_R) >> EQ_P; + } + + r[0] = f->fL[3]; + r[1] = f->fH[3] - f->fL[3]; + r[2] = f->h[HISTOLD] - f->fH[3]; + + for (i = 0; i < 3; i++) { + r[i] = (r[i] * f->g[i]) >> EQ_P; + } + + for (i = HISTOLD; i > 0; i--) { + f->h[i] = f->h[i - 1]; + } + f->h[HISTNEW] = s; + + return (r[0] + r[1] + r[2]); +} + +#endif + +/*****************************************************************************/ +/***************************** PUBLIC FUNCTIONS ******************************/ +/*****************************************************************************/ + +extern void +crt_resize(struct CRT *v, int w, int h, int f, unsigned char *out) +{ + v->outw = w; + v->outh = h; + v->out_format = f; + v->out = out; +} + +extern void +crt_reset(struct CRT *v) +{ + v->hue = 0; + v->saturation = 10; + v->brightness = 0; + v->contrast = 180; + v->black_point = 0; + v->white_point = 100; + v->hsync = 0; + v->vsync = 0; +} + +extern void +crt_init(struct CRT *v, int w, int h, int f, unsigned char *out) +{ + memset(v, 0, sizeof(struct CRT)); + crt_resize(v, w, h, f, out); + crt_reset(v); + v->rn = 194; + + /* kilohertz to line sample conversion */ +#define kHz2L(kHz) (CRT_HRES * (kHz * 100) / L_FREQ) + + /* band gains are pre-scaled as 16-bit fixed point + * if you change the EQ_P define, you'll need to update these gains too + */ +#if (CRT_CC_SAMPLES == 4) + init_eq(&eqY, kHz2L(1500), kHz2L(3000), CRT_HRES, 65536, 8192, 9175); + init_eq(&eqI, kHz2L(80), kHz2L(1150), CRT_HRES, 65536, 65536, 1311); + init_eq(&eqQ, kHz2L(80), kHz2L(1000), CRT_HRES, 65536, 65536, 0); +#elif (CRT_CC_SAMPLES == 5) + init_eq(&eqY, kHz2L(1500), kHz2L(3000), CRT_HRES, 65536, 12192, 7775); + init_eq(&eqI, kHz2L(80), kHz2L(1150), CRT_HRES, 65536, 65536, 1311); + init_eq(&eqQ, kHz2L(80), kHz2L(1000), CRT_HRES, 65536, 65536, 0); +#else +#error "NTSC-CRT currently only supports 4 or 5 samples per chroma period." +#endif + +} + +extern void +crt_demodulate(struct CRT *v, int noise) +{ + /* made static so all this data does not go on the stack */ + static struct { + int y, i, q; + } out[AV_LEN + 1], *yiqA, *yiqB; + int i, j, line, rn; + signed char *sig; + int s = 0; + int field, ratio; + int *ccr; /* color carrier signal */ + int huesn, huecs; + int xnudge = -3, ynudge = 3; + int bright = v->brightness - (BLACK_LEVEL + v->black_point); + int bpp, pitch; +#if CRT_DO_BLOOM + int prev_e; /* filtered beam energy per scan line */ + int max_e; /* approx maximum energy in a scan line */ +#endif + + bpp = crt_bpp4fmt(v->out_format); + if (bpp == 0) { + return; + } + pitch = v->outw * bpp; + + crt_sincos14(&huesn, &huecs, ((v->hue % 360) + 33) * 8192 / 180); + huesn >>= 11; /* make 4-bit */ + huecs >>= 11; + + rn = v->rn; +#if !CRT_DO_VSYNC + /* determine field before we add noise, + * otherwise it's not reliably recoverable + */ + for (i = -CRT_VSYNC_WINDOW; i < CRT_VSYNC_WINDOW; i++) { + line = POSMOD(v->vsync + i, CRT_VRES); + sig = v->analog + line * CRT_HRES; + s = 0; + for (j = 0; j < CRT_HRES; j++) { + s += sig[j]; + if (s <= (CRT_VSYNC_THRESH * SYNC_LEVEL)) { + goto found_field; + } + } + } +found_field: + /* if vsync signal was in second half of line, odd field */ + field = (j > (CRT_HRES / 2)); + v->vsync = -3; +#endif +#if ((CRT_SYSTEM == CRT_SYSTEM_NTSCVHS) && CRT_VHS_NOISE) + line = ((rand() % 8) - 4) + 14; +#endif + for (i = 0; i < CRT_INPUT_SIZE; i++) { + int nn = noise; +#if ((CRT_SYSTEM == CRT_SYSTEM_NTSCVHS) && CRT_VHS_NOISE) + rn = rand(); + if (i > (CRT_INPUT_SIZE - CRT_HRES * (16 + ((rand() % 20) - 10))) && + i < (CRT_INPUT_SIZE - CRT_HRES * (5 + ((rand() % 8) - 4)))) { + int ln, sn, cs; + + ln = (i * line) / CRT_HRES; + crt_sincos14(&sn, &cs, ln * 8192 / 180); + nn = cs >> 8; + } +#else + rn = (214019 * rn + 140327895); +#endif + /* signal + noise */ + s = v->analog[i] + (((((rn >> 16) & 0xff) - 0x7f) * nn) >> 8); + if (s > 127) { s = 127; } + if (s < -127) { s = -127; } + v->inp[i] = s; + } + v->rn = rn; + +#if CRT_DO_VSYNC + /* Look for vertical sync. + * + * This is done by integrating the signal and + * seeing if it exceeds a threshold. The threshold of + * the vertical sync pulse is much higher because the + * vsync pulse is a lot longer than the hsync pulse. + * The signal needs to be integrated to lessen + * the noise in the signal. + */ + for (i = -CRT_VSYNC_WINDOW; i < CRT_VSYNC_WINDOW; i++) { + line = POSMOD(v->vsync + i, CRT_VRES); + sig = v->inp + line * CRT_HRES; + s = 0; + for (j = 0; j < CRT_HRES; j++) { + s += sig[j]; + /* increase the multiplier to make the vsync + * more stable when there is a lot of noise + */ + if (s <= (CRT_VSYNC_THRESH * SYNC_LEVEL)) { + goto vsync_found; + } + } + } +vsync_found: + v->vsync = line; /* vsync found (or gave up) at this line */ + /* if vsync signal was in second half of line, odd field */ + field = (j > (CRT_HRES / 2)); +#endif + +#if CRT_DO_BLOOM + max_e = (128 + (noise / 2)) * AV_LEN; + prev_e = (16384 / 8); +#endif + /* ratio of output height to active video lines in the signal */ + ratio = (v->outh << 16) / CRT_LINES; + ratio = (ratio + 32768) >> 16; + + field = (field * (ratio / 2)); + + for (line = CRT_TOP; line < CRT_BOT; line++) { + unsigned pos, ln, scanR; + int scanL, dx; + int L, R; + unsigned char *cL, *cR; +#if (CRT_CC_SAMPLES == 4) + int wave[CRT_CC_SAMPLES]; +#else + int waveI[CRT_CC_SAMPLES]; + int waveQ[CRT_CC_SAMPLES]; +#endif + int dci, dcq; /* decoded I, Q */ + int xpos, ypos; + int beg, end; + int phasealign; +#if CRT_DO_BLOOM + int line_w; +#endif + + beg = (line - CRT_TOP + 0) * (v->outh + v->v_fac) / CRT_LINES + field; + end = (line - CRT_TOP + 1) * (v->outh + v->v_fac) / CRT_LINES + field; + + if (beg >= v->outh) { continue; } + if (end > v->outh) { end = v->outh; } + + /* Look for horizontal sync. + * See comment above regarding vertical sync. + */ + ln = (POSMOD(line + v->vsync, CRT_VRES)) * CRT_HRES; + sig = v->inp + ln + v->hsync; + s = 0; + for (i = -CRT_HSYNC_WINDOW; i < CRT_HSYNC_WINDOW; i++) { + s += sig[SYNC_BEG + i]; + if (s <= (CRT_HSYNC_THRESH * SYNC_LEVEL)) { + break; + } + } +#if CRT_DO_HSYNC + v->hsync = POSMOD(i + v->hsync, CRT_HRES); +#else + v->hsync = 0; +#endif + + xpos = POSMOD(AV_BEG + v->hsync + xnudge, CRT_HRES); + ypos = POSMOD(line + v->vsync + ynudge, CRT_VRES); + pos = xpos + ypos * CRT_HRES; + + ccr = v->ccf[ypos % CRT_CC_VPER]; +#if (CRT_CC_SAMPLES == 4) + sig = v->inp + ln + (v->hsync & ~3); /* faster */ +#else + sig = v->inp + ln + (v->hsync - (v->hsync % CRT_CC_SAMPLES)); +#endif + for (i = CB_BEG; i < CB_BEG + (CB_CYCLES * CRT_CB_FREQ); i++) { + int p, n; + p = ccr[i % CRT_CC_SAMPLES] * 127 / 128; /* fraction of the previous */ + n = sig[i]; /* mixed with the new sample */ + ccr[i % CRT_CC_SAMPLES] = p + n; + } + + phasealign = POSMOD(v->hsync, CRT_CC_SAMPLES); + +#if (CRT_CC_SAMPLES == 4) + /* amplitude of carrier = saturation, phase difference = hue */ + dci = ccr[(phasealign + 1) & 3] - ccr[(phasealign + 3) & 3]; + dcq = ccr[(phasealign + 2) & 3] - ccr[(phasealign + 0) & 3]; + + wave[0] = ((dci * huecs - dcq * huesn) >> 4) * v->saturation; + wave[1] = ((dcq * huecs + dci * huesn) >> 4) * v->saturation; + wave[2] = -wave[0]; + wave[3] = -wave[1]; +#elif (CRT_CC_SAMPLES == 5) + { + int dciA, dciB; + int dcqA, dcqB; + int ang = (v->hue % 360); + int off180 = CRT_CC_SAMPLES / 2; + int off90 = CRT_CC_SAMPLES / 4; + int peakA = phasealign + off90; + int peakB = phasealign + 0; + dciA = dciB = dcqA = dcqB = 0; + /* amplitude of carrier = saturation, phase difference = hue */ + dciA = ccr[(peakA) % CRT_CC_SAMPLES]; + /* average */ + dciB = (ccr[(peakA + off180) % CRT_CC_SAMPLES] + + ccr[(peakA + off180 + 1) % CRT_CC_SAMPLES]) / 2; + dcqA = ccr[(peakB + off180) % CRT_CC_SAMPLES]; + dcqB = ccr[(peakB) % CRT_CC_SAMPLES]; + dci = dciA - dciB; + dcq = dcqA - dcqB; + /* create wave tables and rotate them by the hue adjustment angle */ + for (i = 0; i < CRT_CC_SAMPLES; i++) { + int sn, cs; + crt_sincos14(&sn, &cs, ang * 8192 / 180); + waveI[i] = ((dci * cs + dcq * sn) >> 15) * v->saturation; + /* Q is offset by 90 */ + crt_sincos14(&sn, &cs, (ang + 90) * 8192 / 180); + waveQ[i] = ((dci * cs + dcq * sn) >> 15) * v->saturation; + ang += (360 / CRT_CC_SAMPLES); + } + } +#endif + sig = v->inp + pos; +#if CRT_DO_BLOOM + s = 0; + for (i = 0; i < AV_LEN; i++) { + s += sig[i]; /* sum up the scan line */ + } + /* bloom emulation */ + prev_e = (prev_e * 123 / 128) + ((((max_e >> 1) - s) << 10) / max_e); + line_w = (AV_LEN * 112 / 128) + (prev_e >> 9); + + dx = (line_w << 12) / v->outw; + scanL = ((AV_LEN / 2) - (line_w >> 1) + 8) << 12; + scanR = (AV_LEN - 1) << 12; + + L = (scanL >> 12); + R = (scanR >> 12); +#else + dx = ((AV_LEN - 1) << 12) / v->outw; + scanL = 0; + scanR = (AV_LEN - 1) << 12; + L = 0; + R = AV_LEN; +#endif + reset_eq(&eqY); + reset_eq(&eqI); + reset_eq(&eqQ); + +#if (CRT_CC_SAMPLES == 4) + for (i = L; i < R; i++) { + out[i].y = eqf(&eqY, sig[i] + bright) << 4; + out[i].i = eqf(&eqI, sig[i] * wave[(i + 0) & 3] >> 9) >> 3; + out[i].q = eqf(&eqQ, sig[i] * wave[(i + 3) & 3] >> 9) >> 3; + } +#else + for (i = L; i < R; i++) { + out[i].y = eqf(&eqY, sig[i] + bright) << 4; + out[i].i = eqf(&eqI, sig[i] * waveI[i % CRT_CC_SAMPLES] >> 9) >> 3; + out[i].q = eqf(&eqQ, sig[i] * waveQ[i % CRT_CC_SAMPLES] >> 9) >> 3; + } +#endif + + cL = v->out + (beg * pitch); + cR = cL + pitch; + + for (pos = scanL; pos < scanR && cL < cR; pos += dx) { + int y, i, q; + int r, g, b; + int aa, bb; + + R = pos & 0xfff; + L = 0xfff - R; + s = pos >> 12; + + yiqA = out + s; + yiqB = out + s + 1; + + /* interpolate between samples if needed */ + y = ((yiqA->y * L) >> 2) + ((yiqB->y * R) >> 2); + i = ((yiqA->i * L) >> 14) + ((yiqB->i * R) >> 14); + q = ((yiqA->q * L) >> 14) + ((yiqB->q * R) >> 14); + + /* YIQ to RGB */ + r = (((y + 3879 * i + 2556 * q) >> 12) * v->contrast) >> 8; + g = (((y - 1126 * i - 2605 * q) >> 12) * v->contrast) >> 8; + b = (((y - 4530 * i + 7021 * q) >> 12) * v->contrast) >> 8; + + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + + if (v->blend) { + aa = (r << 16 | g << 8 | b); + + switch (v->out_format) { + case CRT_PIX_FORMAT_RGB: + case CRT_PIX_FORMAT_RGBA: + bb = cL[0] << 16 | cL[1] << 8 | cL[2]; + break; + case CRT_PIX_FORMAT_BGR: + case CRT_PIX_FORMAT_BGRA: + bb = cL[2] << 16 | cL[1] << 8 | cL[0]; + break; + case CRT_PIX_FORMAT_ARGB: + bb = cL[1] << 16 | cL[2] << 8 | cL[3]; + break; + case CRT_PIX_FORMAT_ABGR: + bb = cL[3] << 16 | cL[2] << 8 | cL[1]; + break; + default: + bb = 0; + break; + } + + /* blend with previous color there */ + bb = (((aa & 0xfefeff) >> 1) + ((bb & 0xfefeff) >> 1)); + } else { + bb = (r << 16 | g << 8 | b); + } + + switch (v->out_format) { + case CRT_PIX_FORMAT_RGB: + cL[0] = bb >> 16 & 0xff; + cL[1] = bb >> 8 & 0xff; + cL[2] = bb >> 0 & 0xff; + break; + + case CRT_PIX_FORMAT_RGBA: + cL[0] = bb >> 16 & 0xff; + cL[1] = bb >> 8 & 0xff; + cL[2] = bb >> 0 & 0xff; + cL[3] = 0xff; + break; + + case CRT_PIX_FORMAT_BGR: + cL[0] = bb >> 0 & 0xff; + cL[1] = bb >> 8 & 0xff; + cL[2] = bb >> 16 & 0xff; + break; + + case CRT_PIX_FORMAT_BGRA: + cL[0] = bb >> 0 & 0xff; + cL[1] = bb >> 8 & 0xff; + cL[2] = bb >> 16 & 0xff; + cL[3] = 0xff; + break; + + case CRT_PIX_FORMAT_ARGB: + cL[0] = 0xff; + cL[1] = bb >> 16 & 0xff; + cL[2] = bb >> 8 & 0xff; + cL[3] = bb >> 0 & 0xff; + break; + + case CRT_PIX_FORMAT_ABGR: + cL[0] = 0xff; + cL[1] = bb >> 0 & 0xff; + cL[2] = bb >> 8 & 0xff; + cL[3] = bb >> 16 & 0xff; + break; + + default: + break; + } + + cL += bpp; + } + + /* duplicate extra lines */ + for (s = beg + 1; s < (end - v->scanlines); s++) { + memcpy(v->out + s * pitch, v->out + (s - 1) * pitch, pitch); + } + } +} diff --git a/gfx/video_filters/ntsc_crt_filter/crt_core.h b/gfx/video_filters/ntsc_crt_filter/crt_core.h new file mode 100644 index 00000000000..456191bb2a6 --- /dev/null +++ b/gfx/video_filters/ntsc_crt_filter/crt_core.h @@ -0,0 +1,145 @@ +/*****************************************************************************/ +/* + * NTSC/CRT - integer-only NTSC video signal encoding / decoding emulation + * + * by EMMIR 2018-2023 + * + * YouTube: https://www.youtube.com/@EMMIR_KC/videos + * Discord: https://discord.com/invite/hdYctSmyQJ + */ +/*****************************************************************************/ +#ifndef _CRT_CORE_H_ +#define _CRT_CORE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* crt_core.h + * + * The demodulator. This is also where you can define which system to emulate. + * + */ + +/* library version */ +#define CRT_MAJOR 2 +#define CRT_MINOR 3 +#define CRT_PATCH 2 + + +#define CRT_SYSTEM_NTSC 0 /* standard NTSC */ +#define CRT_SYSTEM_NES 1 /* decode 6 or 9-bit NES pixels */ +#define CRT_SYSTEM_PV1K 2 /* Casio PV-1000 */ +#define CRT_SYSTEM_SNES 3 /* SNES - uses RGB */ +#define CRT_SYSTEM_TEMP 4 /* template implementation */ +#define CRT_SYSTEM_NTSCVHS 5 /* standard NTSC VHS */ +#define CRT_SYSTEM_NESRGB 6 /* encode RGB image with NES artifacts */ + +/* the system to be compiled */ +#ifndef CRT_SYSTEM +#define CRT_SYSTEM CRT_SYSTEM_NTSC +#endif + +#if (CRT_SYSTEM == CRT_SYSTEM_NES) +#include "crt_nes.h" +#elif (CRT_SYSTEM == CRT_SYSTEM_SNES) +#include "crt_snes.h" +#elif (CRT_SYSTEM == CRT_SYSTEM_NTSC) +#include "crt_ntsc.h" +#elif (CRT_SYSTEM == CRT_SYSTEM_PV1K) +#include "crt_pv1k.h" +#elif (CRT_SYSTEM == CRT_SYSTEM_TEMP) +#include "crt_template.h" +#elif (CRT_SYSTEM == CRT_SYSTEM_NTSCVHS) +#include "crt_ntscvhs.h" +#elif (CRT_SYSTEM == CRT_SYSTEM_NESRGB) +#include "crt_nesrgb.h" +#else +#error No system defined +#endif + +/* NOTE: this library does not use the alpha channel at all */ +#define CRT_PIX_FORMAT_RGB 0 /* 3 bytes per pixel [R,G,B,R,G,B,R,G,B...] */ +#define CRT_PIX_FORMAT_BGR 1 /* 3 bytes per pixel [B,G,R,B,G,R,B,G,R...] */ +#define CRT_PIX_FORMAT_ARGB 2 /* 4 bytes per pixel [A,R,G,B,A,R,G,B...] */ +#define CRT_PIX_FORMAT_RGBA 3 /* 4 bytes per pixel [R,G,B,A,R,G,B,A...] */ +#define CRT_PIX_FORMAT_ABGR 4 /* 4 bytes per pixel [A,B,G,R,A,B,G,R...] */ +#define CRT_PIX_FORMAT_BGRA 5 /* 4 bytes per pixel [B,G,R,A,B,G,R,A...] */ + +/* do bloom emulation (side effect: makes screen have black borders) */ +#define CRT_DO_BLOOM 0 /* does not work for NES */ +#define CRT_DO_VSYNC 1 /* look for VSYNC */ +#define CRT_DO_HSYNC 1 /* look for HSYNC */ + +struct CRT { + signed char analog[CRT_INPUT_SIZE]; + signed char inp[CRT_INPUT_SIZE]; /* CRT input, can be noisy */ + + int outw, outh; /* output width/height */ + int out_format; /* output pixel format (one of the CRT_PIX_FORMATs) */ + unsigned char *out; /* output image */ + + int hue, brightness, contrast, saturation; /* common monitor settings */ + int black_point, white_point; /* user-adjustable */ + int scanlines; /* leave gaps between lines if necessary */ + int blend; /* blend new field onto previous image */ + unsigned v_fac; /* factor to stretch img vertically onto the output img */ + + /* internal data */ + int ccf[CRT_CC_VPER][CRT_CC_SAMPLES]; /* faster color carrier convergence */ + int hsync, vsync; /* keep track of sync over frames */ + int rn; /* seed for the 'random' noise */ +}; + +/* Initializes the library. Sets up filters. + * w - width of the output image + * h - height of the output image + * f - format of the output image + * out - pointer to output image data + */ +extern void crt_init(struct CRT *v, int w, int h, int f, unsigned char *out); + +/* Updates the output image parameters + * w - width of the output image + * h - height of the output image + * f - format of the output image + * out - pointer to output image data + */ +extern void crt_resize(struct CRT *v, int w, int h, int f, unsigned char *out); + +/* Resets the CRT settings back to their defaults */ +extern void crt_reset(struct CRT *v); + +/* Modulates RGB image into an analog NTSC signal + * s - struct containing settings to apply to this field + */ +extern void crt_modulate(struct CRT *v, struct NTSC_SETTINGS *s); + +/* Demodulates the NTSC signal generated by crt_modulate() + * noise - the amount of noise added to the signal (0 - inf) + */ +extern void crt_demodulate(struct CRT *v, int noise); + +/* Get the bytes per pixel for a certain CRT_PIX_FORMAT_ + * + * format - the format to get the bytes per pixel for + * + * returns 0 if the specified format does not exist + */ +extern int crt_bpp4fmt(int format); + +/*****************************************************************************/ +/*************************** FIXED POINT SIN/COS *****************************/ +/*****************************************************************************/ + +#define T14_2PI 16384 +#define T14_MASK (T14_2PI - 1) +#define T14_PI (T14_2PI / 2) + +extern void crt_sincos14(int *s, int *c, int n); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gfx/video_filters/ntsc_crt_filter/crt_ntsc.c b/gfx/video_filters/ntsc_crt_filter/crt_ntsc.c new file mode 100644 index 00000000000..107a2eace93 --- /dev/null +++ b/gfx/video_filters/ntsc_crt_filter/crt_ntsc.c @@ -0,0 +1,331 @@ +/*****************************************************************************/ +/* + * NTSC/CRT - integer-only NTSC video signal encoding / decoding emulation + * + * by EMMIR 2018-2023 + * + * YouTube: https://www.youtube.com/@EMMIR_KC/videos + * Discord: https://discord.com/invite/hdYctSmyQJ + */ +/*****************************************************************************/ + +#include "crt_core.h" + +#if (CRT_SYSTEM == CRT_SYSTEM_NTSC) +#include +#include + +#if (CRT_CHROMA_PATTERN == 1) +/* 227.5 subcarrier cycles per line means every other line has reversed phase */ +#define CC_PHASE(ln) (((ln) & 1) ? -1 : 1) +#else +#define CC_PHASE(ln) (1) +#endif + +#define EXP_P 11 +#define EXP_ONE (1 << EXP_P) +#define EXP_MASK (EXP_ONE - 1) +#define EXP_PI 6434 +#define EXP_MUL(x, y) (((x) * (y)) >> EXP_P) +#define EXP_DIV(x, y) (((x) << EXP_P) / (y)) + +static int e11[] = { + EXP_ONE, + 5567, /* e */ + 15133, /* e^2 */ + 41135, /* e^3 */ + 111817 /* e^4 */ +}; + +/* fixed point e^x */ +static int +expx(int n) +{ + int neg, idx, res; + int nxt, acc, del; + int i; + + if (n == 0) { + return EXP_ONE; + } + neg = n < 0; + if (neg) { + n = -n; + } + idx = n >> EXP_P; + res = EXP_ONE; + for (i = 0; i < idx / 4; i++) { + res = EXP_MUL(res, e11[4]); + } + idx &= 3; + if (idx > 0) { + res = EXP_MUL(res, e11[idx]); + } + + n &= EXP_MASK; + nxt = EXP_ONE; + acc = 0; + del = 1; + for (i = 1; i < 17; i++) { + acc += nxt / del; + nxt = EXP_MUL(nxt, n); + del *= i; + if (del > nxt || nxt <= 0 || del <= 0) { + break; + } + } + res = EXP_MUL(res, acc); + + if (neg) { + res = EXP_DIV(EXP_ONE, res); + } + return res; +} + +/*****************************************************************************/ +/********************************* FILTERS ***********************************/ +/*****************************************************************************/ + +/* infinite impulse response low pass filter for bandlimiting YIQ */ +static struct IIRLP { + int c; + int h; /* history */ +} iirY, iirI, iirQ; + +/* freq - total bandwidth + * limit - max frequency + */ +static void +init_iir(struct IIRLP *f, int freq, int limit) +{ + int rate; /* cycles/pixel rate */ + + memset(f, 0, sizeof(struct IIRLP)); + rate = (freq << 9) / limit; + f->c = EXP_ONE - expx(-((EXP_PI << 9) / rate)); +} + +static void +reset_iir(struct IIRLP *f) +{ + f->h = 0; +} + +/* hi-pass for debugging */ +#define HIPASS 0 + +static int +iirf(struct IIRLP *f, int s) +{ + f->h += EXP_MUL(s - f->h, f->c); +#if HIPASS + return s - f->h; +#else + return f->h; +#endif +} + +extern void +crt_modulate(struct CRT *v, struct NTSC_SETTINGS *s) +{ + int x, y, xo, yo; + int destw = AV_LEN; + int desth = ((CRT_LINES * 64500) >> 16); + int iccf[CRT_CC_SAMPLES]; + int ccmodI[CRT_CC_SAMPLES]; /* color phase for mod */ + int ccmodQ[CRT_CC_SAMPLES]; /* color phase for mod */ + int ccburst[CRT_CC_SAMPLES]; /* color phase for burst */ + int sn, cs, n, ph; + int inv_phase = 0; + int bpp; + + if (!s->iirs_initialized) { + init_iir(&iirY, L_FREQ, Y_FREQ); + init_iir(&iirI, L_FREQ, I_FREQ); + init_iir(&iirQ, L_FREQ, Q_FREQ); + s->iirs_initialized = 1; + } +#if CRT_DO_BLOOM + if (s->raw) { + destw = s->w; + desth = s->h; + if (destw > ((AV_LEN * 55500) >> 16)) { + destw = ((AV_LEN * 55500) >> 16); + } + if (desth > ((CRT_LINES * 63500) >> 16)) { + desth = ((CRT_LINES * 63500) >> 16); + } + } else { + destw = (AV_LEN * 55500) >> 16; + desth = (CRT_LINES * 63500) >> 16; + } +#else + if (s->raw) { + destw = s->w; + desth = s->h; + if (destw > AV_LEN) { + destw = AV_LEN; + } + if (desth > ((CRT_LINES * 64500) >> 16)) { + desth = ((CRT_LINES * 64500) >> 16); + } + } +#endif + if (s->as_color) { + for (x = 0; x < CRT_CC_SAMPLES; x++) { + n = s->hue + x * (360 / CRT_CC_SAMPLES); + crt_sincos14(&sn, &cs, (n + 33) * 8192 / 180); + ccburst[x] = sn >> 10; + crt_sincos14(&sn, &cs, n * 8192 / 180); + ccmodI[x] = sn >> 10; + crt_sincos14(&sn, &cs, (n - 90) * 8192 / 180); + ccmodQ[x] = sn >> 10; + } + } else { + memset(ccburst, 0, sizeof(ccburst)); + memset(ccmodI, 0, sizeof(ccmodI)); + memset(ccmodQ, 0, sizeof(ccmodQ)); + } + + bpp = crt_bpp4fmt(s->format); + if (bpp == 0) { + return; /* just to be safe */ + } + xo = AV_BEG + s->xoffset + (AV_LEN - destw) / 2; + yo = CRT_TOP + s->yoffset + (CRT_LINES - desth) / 2; + + s->field &= 1; + s->frame &= 1; + inv_phase = (s->field == s->frame); + ph = CC_PHASE(inv_phase); + + /* align signal */ + xo = (xo & ~3); + + for (n = 0; n < CRT_VRES; n++) { + int t; /* time */ + signed char *line = &v->analog[n * CRT_HRES]; + + t = LINE_BEG; + + if (n <= 3 || (n >= 7 && n <= 9)) { + /* equalizing pulses - small blips of sync, mostly blank */ + while (t < (4 * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; + while (t < (50 * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; + while (t < (54 * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; + while (t < (100 * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; + } else if (n >= 4 && n <= 6) { + int even[4] = { 46, 50, 96, 100 }; + int odd[4] = { 4, 50, 96, 100 }; + int *offs = even; + if (s->field == 1) { + offs = odd; + } + /* vertical sync pulse - small blips of blank, mostly sync */ + while (t < (offs[0] * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; + while (t < (offs[1] * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; + while (t < (offs[2] * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; + while (t < (offs[3] * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; + } else { + int cb; + + /* video line */ + while (t < SYNC_BEG) line[t++] = BLANK_LEVEL; /* FP */ + while (t < BW_BEG) line[t++] = SYNC_LEVEL; /* SYNC */ + while (t < AV_BEG) line[t++] = BLANK_LEVEL; /* BW + CB + BP */ + if (n < CRT_TOP) { + while (t < CRT_HRES) line[t++] = BLANK_LEVEL; + } + + /* CB_CYCLES of color burst at 3.579545 Mhz */ + for (t = CB_BEG; t < CB_BEG + (CB_CYCLES * CRT_CB_FREQ); t++) { +#if (CRT_CHROMA_PATTERN == 1) + int off180 = CRT_CC_SAMPLES / 2; + cb = ccburst[(t + inv_phase * off180) % CRT_CC_SAMPLES]; +#else + cb = ccburst[t % CRT_CC_SAMPLES]; +#endif + line[t] = (BLANK_LEVEL + (cb * BURST_LEVEL)) >> 5; + iccf[t % CRT_CC_SAMPLES] = line[t]; + } + } + } + + for (y = 0; y < desth; y++) { + int field_offset; + int sy; + + field_offset = (s->field * s->h + desth) / desth / 2; + sy = (y * s->h) / desth; + + sy += field_offset; + + if (sy >= s->h) sy = s->h; + + sy *= s->w; + + reset_iir(&iirY); + reset_iir(&iirI); + reset_iir(&iirQ); + + for (x = 0; x < destw; x++) { + int fy, fi, fq; + int rA, gA, bA; + const unsigned char *pix; + int ire; /* composite signal */ + int xoff; + + pix = s->data + ((((x * s->w) / destw) + sy) * bpp); + switch (s->format) { + case CRT_PIX_FORMAT_RGB: + case CRT_PIX_FORMAT_RGBA: + rA = pix[0]; + gA = pix[1]; + bA = pix[2]; + break; + case CRT_PIX_FORMAT_BGR: + case CRT_PIX_FORMAT_BGRA: + rA = pix[2]; + gA = pix[1]; + bA = pix[0]; + break; + case CRT_PIX_FORMAT_ARGB: + rA = pix[1]; + gA = pix[2]; + bA = pix[3]; + break; + case CRT_PIX_FORMAT_ABGR: + rA = pix[3]; + gA = pix[2]; + bA = pix[1]; + break; + default: + rA = gA = bA = 0; + break; + } + + /* RGB to YIQ */ + fy = (19595 * rA + 38470 * gA + 7471 * bA) >> 14; + fi = (39059 * rA - 18022 * gA - 21103 * bA) >> 14; + fq = (13894 * rA - 34275 * gA + 20382 * bA) >> 14; + ire = BLACK_LEVEL + v->black_point; + + xoff = (x + xo) % CRT_CC_SAMPLES; + /* bandlimit Y,I,Q */ + fy = iirf(&iirY, fy); + fi = iirf(&iirI, fi) * ph * ccmodI[xoff] >> 4; + fq = iirf(&iirQ, fq) * ph * ccmodQ[xoff] >> 4; + ire += (fy + fi + fq) * (WHITE_LEVEL * v->white_point / 100) >> 10; + if (ire < 0) ire = 0; + if (ire > 110) ire = 110; + + v->analog[(x + xo) + (y + yo) * CRT_HRES] = ire; + } + } + for (n = 0; n < CRT_CC_VPER; n++) { + for (x = 0; x < CRT_CC_SAMPLES; x++) { + v->ccf[n][x] = iccf[x] << 7; + } + } +} +#endif diff --git a/gfx/video_filters/ntsc_crt_filter/crt_ntsc.h b/gfx/video_filters/ntsc_crt_filter/crt_ntsc.h new file mode 100644 index 00000000000..2a6f0e94770 --- /dev/null +++ b/gfx/video_filters/ntsc_crt_filter/crt_ntsc.h @@ -0,0 +1,130 @@ +/*****************************************************************************/ +/* + * NTSC/CRT - integer-only NTSC video signal encoding / decoding emulation + * + * by EMMIR 2018-2023 + * + * YouTube: https://www.youtube.com/@EMMIR_KC/videos + * Discord: https://discord.com/invite/hdYctSmyQJ + */ +/*****************************************************************************/ +#ifndef _CRT_NTSC_H_ +#define _CRT_NTSC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* crt_ntsc.h + * + * An interface to convert a digital image to an analog NTSC signal. + * + */ +/* 0 = vertical chroma (228 chroma clocks per line) */ +/* 1 = checkered chroma (227.5 chroma clocks per line) */ +#define CRT_CHROMA_PATTERN 1 + +/* chroma clocks (subcarrier cycles) per line */ +#if (CRT_CHROMA_PATTERN == 1) +#define CRT_CC_LINE 2275 +#else +/* this will give the 'rainbow' effect in the famous waterfall scene */ +#define CRT_CC_LINE 2280 +#endif + +/* NOTE, in general, increasing CRT_CB_FREQ reduces blur and bleed */ +#define CRT_CB_FREQ 4 /* carrier frequency relative to sample rate */ +#define CRT_HRES (CRT_CC_LINE * CRT_CB_FREQ / 10) /* horizontal res */ +#define CRT_VRES 262 /* vertical resolution */ +#define CRT_INPUT_SIZE (CRT_HRES * CRT_VRES) + +#define CRT_TOP 21 /* first line with active video */ +#define CRT_BOT 261 /* final line with active video */ +#define CRT_LINES (CRT_BOT - CRT_TOP) /* number of active video lines */ + +#define CRT_CC_SAMPLES 4 /* samples per chroma period (samples per 360 deg) */ +#define CRT_CC_VPER 1 /* vertical period in which the artifacts repeat */ + +/* search windows, in samples */ +#define CRT_HSYNC_WINDOW 8 +#define CRT_VSYNC_WINDOW 8 + +/* accumulated signal threshold required for sync detection. + * Larger = more stable, until it's so large that it is never reached in which + * case the CRT won't be able to sync + */ +#define CRT_HSYNC_THRESH 4 +#define CRT_VSYNC_THRESH 94 + +/* + * FULL HORIZONTAL LINE SIGNAL (~63500 ns) + * |---------------------------------------------------------------------------| + * HBLANK (~10900 ns) ACTIVE VIDEO (~52600 ns) + * |-------------------||------------------------------------------------------| + * + * + * WITHIN HBLANK PERIOD: + * + * FP (~1500 ns) SYNC (~4700 ns) BW (~600 ns) CB (~2500 ns) BP (~1600 ns) + * |--------------||---------------||------------||-------------||-------------| + * BLANK SYNC BLANK BLANK BLANK + * + */ +#define LINE_BEG 0 +#define FP_ns 1500 /* front porch */ +#define SYNC_ns 4700 /* sync tip */ +#define BW_ns 600 /* breezeway */ +#define CB_ns 2500 /* color burst */ +#define BP_ns 1600 /* back porch */ +#define AV_ns 52600 /* active video */ +#define HB_ns (FP_ns + SYNC_ns + BW_ns + CB_ns + BP_ns) /* h blank */ +/* line duration should be ~63500 ns */ +#define LINE_ns (FP_ns + SYNC_ns + BW_ns + CB_ns + BP_ns + AV_ns) + +/* convert nanosecond offset to its corresponding point on the sampled line */ +#define ns2pos(ns) ((ns) * CRT_HRES / LINE_ns) +/* starting points for all the different pulses */ +#define FP_BEG ns2pos(0) +#define SYNC_BEG ns2pos(FP_ns) +#define BW_BEG ns2pos(FP_ns + SYNC_ns) +#define CB_BEG ns2pos(FP_ns + SYNC_ns + BW_ns) +#define BP_BEG ns2pos(FP_ns + SYNC_ns + BW_ns + CB_ns) +#define AV_BEG ns2pos(HB_ns) +#define AV_LEN ns2pos(AV_ns) + +/* somewhere between 7 and 12 cycles */ +#define CB_CYCLES 10 + +/* frequencies for bandlimiting */ +#define L_FREQ 1431818 /* full line */ +#define Y_FREQ 420000 /* Luma (Y) 4.2 MHz of the 14.31818 MHz */ +#define I_FREQ 150000 /* Chroma (I) 1.5 MHz of the 14.31818 MHz */ +#define Q_FREQ 55000 /* Chroma (Q) 0.55 MHz of the 14.31818 MHz */ + +/* IRE units (100 = 1.0V, -40 = 0.0V) */ +#define WHITE_LEVEL 100 +#define BURST_LEVEL 20 +#define BLACK_LEVEL 7 +#define BLANK_LEVEL 0 +#define SYNC_LEVEL -40 + +struct NTSC_SETTINGS { + const unsigned char *data; /* image data */ + int format; /* pix format (one of the CRT_PIX_FORMATs in crt_core.h) */ + int w, h; /* width and height of image */ + int raw; /* 0 = scale image to fit monitor, 1 = don't scale */ + int as_color; /* 0 = monochrome, 1 = full color */ + int field; /* 0 = even, 1 = odd */ + int frame; /* 0 = even, 1 = odd */ + int hue; /* 0-359 */ + int xoffset; /* x offset in sample space. 0 is minimum value */ + int yoffset; /* y offset in # of lines. 0 is minimum value */ + /* make sure your NTSC_SETTINGS struct is zeroed out before you do anything */ + int iirs_initialized; /* internal state */ +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gfx/video_filters/ntsc_crt_filter/ntsc_crt.c b/gfx/video_filters/ntsc_crt_filter/ntsc_crt.c new file mode 100644 index 00000000000..2c70d8bec76 --- /dev/null +++ b/gfx/video_filters/ntsc_crt_filter/ntsc_crt.c @@ -0,0 +1,388 @@ +/* RetroArch CPU Filter - NTSC/CRT + * + * Original NTSC/CRT library by EMMIR 2018-2023 + * https://www.youtube.com/@EMMIR_KC/videos + * + * RetroArch softfilter wrapper 2024 + * + * Compile (Linux): + * gcc -O2 -shared -fPIC -std=c99 \ + * -o ntsc_crt.so \ + * ntsc_crt.c crt_core.c crt_ntsc.c \ + * -lm + * + * Compile (Windows cross): + * x86_64-w64-mingw32-gcc -O2 -shared -std=c99 \ + * -o ntsc_crt.dll \ + * ntsc_crt.c crt_core.c crt_ntsc.c \ + * -lm + * + * Install: + * Copy ntsc_crt.so (or .dll) + ntsc_crt.filt to: + * /filters/video/ + */ + +#include "softfilter.h" +#include "crt_core.h" + +#include +#include +#include + +#ifdef RARCH_INTERNAL +#define softfilter_get_implementation ntsc_crt_get_implementation +#define softfilter_thread_data ntsc_crt_softfilter_thread_data +#define filter_data ntsc_crt_filter_data +#endif + +/* ----------------------------------------------------------------------- + * Default tuning — these become the starting values shown in the .filt + * ----------------------------------------------------------------------- */ +#define DEFAULT_NOISE 2 /* 0 = clean signal, ~10 = noisy */ +#define DEFAULT_HUE 0 /* 0-359 */ +#define DEFAULT_BRIGHTNESS 0 /* monitor brightness offset */ +#define DEFAULT_CONTRAST 180 /* monitor contrast */ +#define DEFAULT_SATURATION 10 /* color saturation */ +#define DEFAULT_SCANLINES 0 /* 1 = dark gap between scanlines */ +#define DEFAULT_BLEND 1 /* 1 = blend fields (less flicker) */ +#define DEFAULT_AS_COLOR 1 /* 0 = monochrome, 1 = color */ +#define DEFAULT_BLACK_PT 0 /* black point adjustment */ +#define DEFAULT_WHITE_PT 100 /* white point adjustment */ + +/* ----------------------------------------------------------------------- + * Per-thread data (same pattern as scanline2x) + * ----------------------------------------------------------------------- */ +struct softfilter_thread_data +{ + void *out_data; + const void *in_data; + size_t out_pitch; + size_t in_pitch; + unsigned colfmt; + unsigned width; + unsigned height; + int first; + int last; +}; + +/* ----------------------------------------------------------------------- + * Main filter state + * ----------------------------------------------------------------------- */ +struct filter_data +{ + unsigned threads; + struct softfilter_thread_data *workers; + unsigned in_fmt; + + /* CRT library state */ + struct CRT crt; + struct NTSC_SETTINGS ntsc; + + /* output buffer (XRGB8888) */ + unsigned char *out_buf; + unsigned out_buf_w; + unsigned out_buf_h; + + /* user-settable params */ + int noise; + int hue; + int scanlines; + int blend; + int as_color; + + /* frame/field counter */ + int frame; + int field; +}; + +/* ----------------------------------------------------------------------- + * Helpers: XRGB8888 ↔ RGB packing + * ----------------------------------------------------------------------- */ + +/* Convert XRGB8888 source row to a packed RGB byte array for crt_modulate */ +static void xrgb8888_to_rgb(const uint32_t *src, unsigned char *dst, + unsigned w) +{ + unsigned x; + for (x = 0; x < w; x++) { + uint32_t c = src[x]; + dst[x * 3 + 0] = (c >> 16) & 0xff; /* R */ + dst[x * 3 + 1] = (c >> 8) & 0xff; /* G */ + dst[x * 3 + 2] = (c >> 0) & 0xff; /* B */ + } +} + +static void rgb565_to_rgb(const uint16_t *src, unsigned char *dst, + unsigned w) +{ + unsigned x; + for (x = 0; x < w; x++) { + uint16_t c = src[x]; + /* expand to 8-bit */ + dst[x * 3 + 0] = ((c >> 11) & 0x1f) << 3; + dst[x * 3 + 1] = ((c >> 5) & 0x3f) << 2; + dst[x * 3 + 2] = ((c >> 0) & 0x1f) << 3; + } +} + +/* Convert CRT XRGB output back to RetroArch XRGB8888 scanline */ +static void rgb_to_xrgb8888(const unsigned char *src, uint32_t *dst, + unsigned w) +{ + unsigned x; + for (x = 0; x < w; x++) { + dst[x] = 0xff000000u + | ((uint32_t)src[x * 3 + 0] << 16) + | ((uint32_t)src[x * 3 + 1] << 8) + | ((uint32_t)src[x * 3 + 2] << 0); + } +} + +/* ----------------------------------------------------------------------- + * softfilter API + * ----------------------------------------------------------------------- */ + +static unsigned ntsc_crt_input_fmts(void) +{ + return SOFTFILTER_FMT_XRGB8888 | SOFTFILTER_FMT_RGB565; +} + +static unsigned ntsc_crt_output_fmts(unsigned input_fmts) +{ + /* We always output XRGB8888 (CRT lib works in RGB) */ + (void)input_fmts; + return SOFTFILTER_FMT_XRGB8888; +} + +static unsigned ntsc_crt_threads(void *data) +{ + struct filter_data *filt = (struct filter_data*)data; + return filt->threads; +} + +/* Output is the same size as input — the CRT effect is spatial, not scaling */ +static void ntsc_crt_output_size(void *data, + unsigned *out_width, unsigned *out_height, + unsigned width, unsigned height) +{ + (void)data; + *out_width = width; + *out_height = height; +} + +static void *ntsc_crt_create(const struct softfilter_config *config, + unsigned in_fmt, unsigned out_fmt, + unsigned max_width, unsigned max_height, + unsigned threads, softfilter_simd_mask_t simd, void *userdata) +{ + struct filter_data *filt; + unsigned char *out_buf; + (void)out_fmt; + (void)simd; + (void)userdata; + + filt = (struct filter_data*)calloc(1, sizeof(*filt)); + if (!filt) return NULL; + + filt->workers = (struct softfilter_thread_data*) + calloc(1, sizeof(struct softfilter_thread_data)); + if (!filt->workers) { free(filt); return NULL; } + + /* single-threaded — CRT state is not thread-safe */ + filt->threads = 1; + filt->in_fmt = in_fmt; + + /* Allocate CRT output buffer: RGB (3 bpp) */ + out_buf = (unsigned char*)malloc(max_width * max_height * 3); + if (!out_buf) { free(filt->workers); free(filt); return NULL; } + + filt->out_buf = out_buf; + filt->out_buf_w = max_width; + filt->out_buf_h = max_height; + + /* Init CRT library */ + crt_init(&filt->crt, max_width, max_height, + CRT_PIX_FORMAT_RGB, out_buf); + + /* Read tunable params from .filt config (falls back to defaults) */ + if (config) { + config->get_int(userdata, "noise", &filt->noise, DEFAULT_NOISE); + config->get_int(userdata, "hue", &filt->hue, DEFAULT_HUE); + config->get_int(userdata, "as_color", &filt->as_color, DEFAULT_AS_COLOR); + config->get_int(userdata, "scanlines", &filt->scanlines, DEFAULT_SCANLINES); + config->get_int(userdata, "blend", &filt->blend, DEFAULT_BLEND); + config->get_int(userdata, "brightness", &filt->crt.brightness, DEFAULT_BRIGHTNESS); + config->get_int(userdata, "contrast", &filt->crt.contrast, DEFAULT_CONTRAST); + config->get_int(userdata, "saturation", &filt->crt.saturation, DEFAULT_SATURATION); + config->get_int(userdata, "black_point",&filt->crt.black_point, DEFAULT_BLACK_PT); + config->get_int(userdata, "white_point",&filt->crt.white_point, DEFAULT_WHITE_PT); + } else { + filt->noise = DEFAULT_NOISE; + filt->hue = DEFAULT_HUE; + filt->as_color = DEFAULT_AS_COLOR; + filt->scanlines = DEFAULT_SCANLINES; + filt->blend = DEFAULT_BLEND; + filt->crt.brightness = DEFAULT_BRIGHTNESS; + filt->crt.contrast = DEFAULT_CONTRAST; + filt->crt.saturation = DEFAULT_SATURATION; + filt->crt.black_point = DEFAULT_BLACK_PT; + filt->crt.white_point = DEFAULT_WHITE_PT; + } + + filt->crt.scanlines = filt->scanlines; + filt->crt.blend = filt->blend; + filt->crt.hue = filt->hue; + + /* NTSC_SETTINGS — zeroed by calloc, just set fields */ + memset(&filt->ntsc, 0, sizeof(filt->ntsc)); + filt->ntsc.as_color = filt->as_color; + filt->ntsc.hue = filt->hue; + filt->ntsc.format = CRT_PIX_FORMAT_RGB; + + filt->frame = 0; + filt->field = 0; + + return filt; +} + +static void ntsc_crt_destroy(void *data) +{ + struct filter_data *filt = (struct filter_data*)data; + if (!filt) return; + if (filt->out_buf) free(filt->out_buf); + free(filt->workers); + free(filt); +} + +/* ----------------------------------------------------------------------- + * The actual work callback — called from RetroArch's worker thread + * ----------------------------------------------------------------------- */ +static void ntsc_crt_work_cb(void *data, void *thread_data) +{ + struct filter_data *filt = (struct filter_data*)data; + struct softfilter_thread_data *thr = (struct softfilter_thread_data*)thread_data; + + unsigned width = thr->width; + unsigned height = thr->height; + size_t in_pitch = thr->in_pitch; + size_t out_pitch = thr->out_pitch; + + /* ---- Convert input to RGB byte-array for crt_modulate ---- */ + unsigned char *rgb_in = (unsigned char*)malloc(width * height * 3); + if (!rgb_in) return; + + if (thr->colfmt == SOFTFILTER_FMT_XRGB8888) { + unsigned y; + for (y = 0; y < height; y++) { + const uint32_t *row = (const uint32_t*) + ((const uint8_t*)thr->in_data + y * in_pitch); + xrgb8888_to_rgb(row, rgb_in + y * width * 3, width); + } + } else { + unsigned y; + for (y = 0; y < height; y++) { + const uint16_t *row = (const uint16_t*) + ((const uint8_t*)thr->in_data + y * in_pitch); + rgb565_to_rgb(row, rgb_in + y * width * 3, width); + } + } + + /* ---- Resize CRT output buffer if needed ---- */ + if (filt->out_buf_w != width || filt->out_buf_h != height) { + unsigned char *new_buf = (unsigned char*)realloc(filt->out_buf, + width * height * 3); + if (!new_buf) { free(rgb_in); return; } + filt->out_buf = new_buf; + filt->out_buf_w = width; + filt->out_buf_h = height; + crt_resize(&filt->crt, width, height, CRT_PIX_FORMAT_RGB, filt->out_buf); + } + filt->crt.out = filt->out_buf; + + /* ---- Set up NTSC_SETTINGS for this frame ---- */ + filt->ntsc.data = rgb_in; + filt->ntsc.format = CRT_PIX_FORMAT_RGB; + filt->ntsc.w = (int)width; + filt->ntsc.h = (int)height; + filt->ntsc.raw = 0; /* scale to fit */ + filt->ntsc.field = filt->field; + filt->ntsc.frame = filt->frame & 1; + filt->ntsc.hue = filt->hue; + + /* ---- Encode → signal → decode ---- */ + crt_modulate(&filt->crt, &filt->ntsc); + crt_demodulate(&filt->crt, filt->noise); + + /* ---- Advance field/frame counters ---- */ + filt->field ^= 1; + if (filt->field == 0) filt->frame++; + + /* ---- Copy RGB output → XRGB8888 destination ---- */ + { + unsigned y; + for (y = 0; y < height; y++) { + uint32_t *dst_row = (uint32_t*) + ((uint8_t*)thr->out_data + y * out_pitch); + const unsigned char *src_row = filt->out_buf + y * width * 3; + rgb_to_xrgb8888(src_row, dst_row, width); + } + } + + free(rgb_in); +} + +/* ----------------------------------------------------------------------- + * Packet submission (same structure as scanline2x) + * ----------------------------------------------------------------------- */ +static void ntsc_crt_packets(void *data, + struct softfilter_work_packet *packets, + void *output, size_t output_stride, + const void *input, unsigned width, unsigned height, + size_t input_stride) +{ + struct filter_data *filt = (struct filter_data*)data; + struct softfilter_thread_data *thr = &filt->workers[0]; + + thr->out_data = (uint8_t*)output; + thr->in_data = (const uint8_t*)input; + thr->out_pitch = output_stride; + thr->in_pitch = input_stride; + thr->width = width; + thr->height = height; + thr->colfmt = filt->in_fmt; + + packets[0].work = ntsc_crt_work_cb; + packets[0].thread_data = thr; +} + +/* ----------------------------------------------------------------------- + * Implementation descriptor — matches struct softfilter_implementation + * ----------------------------------------------------------------------- */ +static const struct softfilter_implementation ntsc_crt_impl = { + ntsc_crt_input_fmts, + ntsc_crt_output_fmts, + + ntsc_crt_create, + ntsc_crt_destroy, + + ntsc_crt_threads, + ntsc_crt_output_size, + ntsc_crt_packets, + + SOFTFILTER_API_VERSION, + "NTSC/CRT", + "ntsc_crt", +}; + +const struct softfilter_implementation *softfilter_get_implementation( + softfilter_simd_mask_t simd) +{ + (void)simd; + return &ntsc_crt_impl; +} + +#ifdef RARCH_INTERNAL +#undef softfilter_get_implementation +#undef softfilter_thread_data +#undef filter_data +#endif diff --git a/gfx/video_filters/ntsc_crt_filter/ntsc_crt.filt b/gfx/video_filters/ntsc_crt_filter/ntsc_crt.filt new file mode 100644 index 00000000000..fdb71df1fb0 --- /dev/null +++ b/gfx/video_filters/ntsc_crt_filter/ntsc_crt.filt @@ -0,0 +1,25 @@ +filter = ntsc_crt + +# --- Signal --- +# Amount of noise added to the analog signal (0 = clean, 10 = very noisy) +ntsc_crt_noise = 2 + +# Hue rotation in degrees (0-359) +ntsc_crt_hue = 0 + +# 0 = monochrome / 1 = full color +ntsc_crt_as_color = 1 + +# --- Monitor --- +ntsc_crt_brightness = 0 +ntsc_crt_contrast = 180 +ntsc_crt_saturation = 10 +ntsc_crt_black_point = 0 +ntsc_crt_white_point = 100 + +# --- Display --- +# 1 = dark gap between scanlines (classic CRT look) +ntsc_crt_scanlines = 0 + +# 1 = blend even/odd fields together (reduces flicker, softer look) +ntsc_crt_blend = 1 From e225095fe19ce082f8b74aaeaa86ce702ecac047 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 07:29:24 +0200 Subject: [PATCH 04/22] Add two new filters --- Makefile.common | 4 +++- gfx/video_filters/Makefile | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile.common b/Makefile.common index 291741557a1..ce3be55b827 100644 --- a/Makefile.common +++ b/Makefile.common @@ -2693,7 +2693,9 @@ ifeq ($(HAVE_STATIC_VIDEO_FILTERS), 1) gfx/video_filters/upscale_240x160_320x240.o \ gfx/video_filters/upscale_mix_240x160_320x240.o \ gfx/video_filters/dedither.o \ - gfx/video_filters/pixel_art_aa.o + gfx/video_filters/pixel_art_aa.o \ + gfx/video_filters/crop_borders.o \ + gfx/video_filters/ntsc.o endif ifeq ($(WANT_IOSUHAX), 1) diff --git a/gfx/video_filters/Makefile b/gfx/video_filters/Makefile index 48dd802eb5f..3139a8ad5dc 100644 --- a/gfx/video_filters/Makefile +++ b/gfx/video_filters/Makefile @@ -132,7 +132,9 @@ objects += blargg_ntsc_snes.$(DYLIB) \ upscale_256x_320x240.$(DYLIB) \ picoscale_256x_320x240.$(DYLIB) \ upscale_240x160_320x240.$(DYLIB) \ - upscale_mix_240x160_320x240.$(DYLIB) + upscale_mix_240x160_320x240.$(DYLIB) \ + crop_borders.$(DYLIB) \ + ntsc.$(DYLIB) all: build; From 24135f05c4764aeef8a893e9e49da83b7ed2eacc Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 07:35:17 +0200 Subject: [PATCH 05/22] Should never include stdbool.h for MSVC backwards compat --- libretro-common/include/retro_spsc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libretro-common/include/retro_spsc.h b/libretro-common/include/retro_spsc.h index 9191d1f0e65..edb86d4a300 100644 --- a/libretro-common/include/retro_spsc.h +++ b/libretro-common/include/retro_spsc.h @@ -97,7 +97,7 @@ #include #include -#include +#include #include #include From 4375682df331011cc57aced78647754d319ad746 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 07:41:36 +0200 Subject: [PATCH 06/22] Buildfixes --- network/netplay/netplay.h | 6 ++++++ ui/drivers/ui_qt_widgets.cpp | 32 +++++++++++++++++++------------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 604576241f3..e5864731ccc 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -22,6 +22,7 @@ #include #include +#include #ifdef HAVE_CONFIG_H #include "../../config.h" @@ -37,6 +38,8 @@ #include "../natt.h" +RETRO_BEGIN_DECLS + typedef struct netplay netplay_t; typedef struct netplay_client_info @@ -213,4 +216,7 @@ bool netplay_discovery_driver_ctl(enum rarch_netplay_discovery_ctl_state state, #endif extern const mitm_server_t netplay_mitm_server_list[NETPLAY_MITM_SERVERS]; + +RETRO_END_DECLS + #endif diff --git a/ui/drivers/ui_qt_widgets.cpp b/ui/drivers/ui_qt_widgets.cpp index 7c4513ef883..fb26805e0c2 100644 --- a/ui/drivers/ui_qt_widgets.cpp +++ b/ui/drivers/ui_qt_widgets.cpp @@ -32,19 +32,12 @@ #include "ui_qt_widgets.h" #include "ui_qt.h" -#ifndef CXX_BUILD -extern "C" { -#endif - -#include - -#include -#include -#include -#include -#include -#include - +/* RetroArch-internal headers are included here, OUTSIDE the extern "C" + * block below. They self-guard their C declarations with + * RETRO_BEGIN_DECLS / RETRO_END_DECLS, and several of them transitively + * include libretro-common/include/retro_atomic.h, which in C++ mode + * pulls in the C++ header. Templates inside extern "C" are a + * hard error, so these must live outside the extern "C" block. */ #include "../../config.def.h" #include "../../command.h" #include "../../core_info.h" @@ -70,6 +63,19 @@ extern "C" { #endif #endif +#ifndef CXX_BUILD +extern "C" { +#endif + +#include + +#include +#include +#include +#include +#include +#include + #ifndef CXX_BUILD } #endif From 6f8afa7a887cea351b0b9f0043866ad02629be8e Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 07:53:20 +0200 Subject: [PATCH 07/22] Folding - we want one file per filter --- Makefile.common | 3 +- gfx/video_filters/Makefile | 3 +- gfx/video_filters/ntsc_crt_filter/Makefile | 37 - gfx/video_filters/ntsc_crt_filter/crt_core.c | 666 ------------------ gfx/video_filters/ntsc_crt_filter/crt_core.h | 145 ---- gfx/video_filters/ntsc_crt_filter/crt_ntsc.c | 331 --------- gfx/video_filters/ntsc_crt_filter/crt_ntsc.h | 130 ---- gfx/video_filters/ntsc_crt_filter/ntsc_crt.c | 388 ---------- .../ntsc_crt_filter/ntsc_crt.filt | 25 - griffin/griffin.c | 1 + 10 files changed, 5 insertions(+), 1724 deletions(-) delete mode 100644 gfx/video_filters/ntsc_crt_filter/Makefile delete mode 100644 gfx/video_filters/ntsc_crt_filter/crt_core.c delete mode 100644 gfx/video_filters/ntsc_crt_filter/crt_core.h delete mode 100644 gfx/video_filters/ntsc_crt_filter/crt_ntsc.c delete mode 100644 gfx/video_filters/ntsc_crt_filter/crt_ntsc.h delete mode 100644 gfx/video_filters/ntsc_crt_filter/ntsc_crt.c delete mode 100644 gfx/video_filters/ntsc_crt_filter/ntsc_crt.filt diff --git a/Makefile.common b/Makefile.common index ce3be55b827..a3b34d36900 100644 --- a/Makefile.common +++ b/Makefile.common @@ -2695,7 +2695,8 @@ ifeq ($(HAVE_STATIC_VIDEO_FILTERS), 1) gfx/video_filters/dedither.o \ gfx/video_filters/pixel_art_aa.o \ gfx/video_filters/crop_borders.o \ - gfx/video_filters/ntsc.o + gfx/video_filters/ntsc.o \ + gfx/video_filters/ntsc_crt.o endif ifeq ($(WANT_IOSUHAX), 1) diff --git a/gfx/video_filters/Makefile b/gfx/video_filters/Makefile index 3139a8ad5dc..f8cc455c315 100644 --- a/gfx/video_filters/Makefile +++ b/gfx/video_filters/Makefile @@ -134,7 +134,8 @@ objects += blargg_ntsc_snes.$(DYLIB) \ upscale_240x160_320x240.$(DYLIB) \ upscale_mix_240x160_320x240.$(DYLIB) \ crop_borders.$(DYLIB) \ - ntsc.$(DYLIB) + ntsc.$(DYLIB) \ + ntsc_crt.$(DYLIB) all: build; diff --git a/gfx/video_filters/ntsc_crt_filter/Makefile b/gfx/video_filters/ntsc_crt_filter/Makefile deleted file mode 100644 index 483351d0dd0..00000000000 --- a/gfx/video_filters/ntsc_crt_filter/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -# Makefile for ntsc_crt RetroArch softfilter -# -# Usage: -# make -> builds ntsc_crt.so (Linux) -# make WINDOWS=1 -> cross-compiles ntsc_crt.dll (requires mingw-w64) -# make install -> copies .so + .filt to FILTERDIR - -CC = gcc -CFLAGS = -O2 -std=c99 -Wall -fPIC -DCRT_SYSTEM=0 -I.. -LDFLAGS = -shared -lm - -# Adjust this to your RetroArch installation path -FILTERDIR = /usr/share/retroarch/filters/video - -SRC = ntsc_crt.c crt_core.c crt_ntsc.c -TARGET = ntsc_crt.so - -ifdef WINDOWS - CC = x86_64-w64-mingw32-gcc - TARGET = ntsc_crt.dll - CFLAGS += -D_WIN32 -endif - -all: $(TARGET) - -$(TARGET): $(SRC) - $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ - -install: $(TARGET) - install -d $(FILTERDIR) - install -m 644 $(TARGET) $(FILTERDIR)/ - install -m 644 ntsc_crt.filt $(FILTERDIR)/ - -clean: - rm -f $(TARGET) - -.PHONY: all install clean diff --git a/gfx/video_filters/ntsc_crt_filter/crt_core.c b/gfx/video_filters/ntsc_crt_filter/crt_core.c deleted file mode 100644 index 3dfed0cec2f..00000000000 --- a/gfx/video_filters/ntsc_crt_filter/crt_core.c +++ /dev/null @@ -1,666 +0,0 @@ -/*****************************************************************************/ -/* - * NTSC/CRT - integer-only NTSC video signal encoding / decoding emulation - * - * by EMMIR 2018-2023 - * - * YouTube: https://www.youtube.com/@EMMIR_KC/videos - * Discord: https://discord.com/invite/hdYctSmyQJ - */ -/*****************************************************************************/ -#include "crt_core.h" - -#include -#include - -/* ensure negative values for x get properly modulo'd */ -#define POSMOD(x, n) (((x) % (n) + (n)) % (n)) - -static int sigpsin15[18] = { /* significant points on sine wave (15-bit) */ - 0x0000, - 0x0c88,0x18f8,0x2528,0x30f8,0x3c50,0x4718,0x5130,0x5a80, - 0x62f0,0x6a68,0x70e0,0x7640,0x7a78,0x7d88,0x7f60,0x8000, - 0x7f60 -}; - -static int -sintabil8(int n) -{ - int f, i, a, b; - - /* looks scary but if you don't change T14_2PI - * it won't cause out of bounds memory reads - */ - f = n >> 0 & 0xff; - i = n >> 8 & 0xff; - a = sigpsin15[i]; - b = sigpsin15[i + 1]; - return (a + ((b - a) * f >> 8)); -} - -/* 14-bit interpolated sine/cosine */ -extern void -crt_sincos14(int *s, int *c, int n) -{ - int h; - - n &= T14_MASK; - h = n & ((T14_2PI >> 1) - 1); - - if (h > ((T14_2PI >> 2) - 1)) { - *c = -sintabil8(h - (T14_2PI >> 2)); - *s = sintabil8((T14_2PI >> 1) - h); - } else { - *c = sintabil8((T14_2PI >> 2) - h); - *s = sintabil8(h); - } - if (n > ((T14_2PI >> 1) - 1)) { - *c = -*c; - *s = -*s; - } -} - -extern int -crt_bpp4fmt(int format) -{ - switch (format) { - case CRT_PIX_FORMAT_RGB: - case CRT_PIX_FORMAT_BGR: - return 3; - case CRT_PIX_FORMAT_ARGB: - case CRT_PIX_FORMAT_RGBA: - case CRT_PIX_FORMAT_ABGR: - case CRT_PIX_FORMAT_BGRA: - return 4; - default: - return 0; - } -} - -/*****************************************************************************/ -/********************************* FILTERS ***********************************/ -/*****************************************************************************/ - -/* convolution is much faster but the EQ looks softer, more authentic, and more analog */ -#define USE_CONVOLUTION 0 -#define USE_7_SAMPLE_KERNEL 1 -#define USE_6_SAMPLE_KERNEL 0 -#define USE_5_SAMPLE_KERNEL 0 - -#if (CRT_CC_SAMPLES != 4) -/* the current convolutions do not filter properly at > 4 samples */ -#undef USE_CONVOLUTION -#define USE_CONVOLUTION 0 -#endif - -#if USE_CONVOLUTION - -/* NOT 3 band equalizer, faster convolution instead. - * eq function names preserved to keep code clean - */ -static struct EQF { - int h[7]; -} eqY, eqI, eqQ; - -/* params unused to keep the function the same */ -static void -init_eq(struct EQF *f, - int f_lo, int f_hi, int rate, - int g_lo, int g_mid, int g_hi) -{ - memset(f, 0, sizeof(struct EQF)); -} - -static void -reset_eq(struct EQF *f) -{ - memset(f->h, 0, sizeof(f->h)); -} - -static int -eqf(struct EQF *f, int s) -{ - int i; - int *h = f->h; - - for (i = 6; i > 0; i--) { - h[i] = h[i - 1]; - } - h[0] = s; -#if USE_7_SAMPLE_KERNEL - /* index : 0 1 2 3 4 5 6 */ - /* weight: 1 4 7 8 7 4 1 */ - return (s + h[6] + ((h[1] + h[5]) * 4) + ((h[2] + h[4]) * 7) + (h[3] * 8)) >> 5; -#elif USE_6_SAMPLE_KERNEL - /* index : 0 1 2 3 4 5 */ - /* weight: 1 3 4 4 3 1 */ - return (s + h[5] + 3 * (h[1] + h[4]) + 4 * (h[2] + h[3])) >> 4; -#elif USE_5_SAMPLE_KERNEL - /* index : 0 1 2 3 4 */ - /* weight: 1 2 2 2 1 */ - return (s + h[4] + ((h[1] + h[2] + h[3]) << 1)) >> 3; -#else - /* index : 0 1 2 3 */ - /* weight: 1 1 1 1*/ - return (s + h[3] + h[1] + h[2]) >> 2; -#endif -} - -#else - -#define HISTLEN 3 -#define HISTOLD (HISTLEN - 1) /* oldest entry */ -#define HISTNEW 0 /* newest entry */ - -#define EQ_P 16 /* if changed, the gains will need to be adjusted */ -#define EQ_R (1 << (EQ_P - 1)) /* rounding */ -/* three band equalizer */ -static struct EQF { - int lf, hf; /* fractions */ - int g[3]; /* gains */ - int fL[4]; - int fH[4]; - int h[HISTLEN]; /* history */ -} eqY, eqI, eqQ; - -/* f_lo - low cutoff frequency - * f_hi - high cutoff frequency - * rate - sampling rate - * g_lo, g_mid, g_hi - gains - */ -static void -init_eq(struct EQF *f, - int f_lo, int f_hi, int rate, - int g_lo, int g_mid, int g_hi) -{ - int sn, cs; - - memset(f, 0, sizeof(struct EQF)); - - f->g[0] = g_lo; - f->g[1] = g_mid; - f->g[2] = g_hi; - - crt_sincos14(&sn, &cs, T14_PI * f_lo / rate); -#if (EQ_P >= 15) - f->lf = 2 * (sn << (EQ_P - 15)); -#else - f->lf = 2 * (sn >> (15 - EQ_P)); -#endif - crt_sincos14(&sn, &cs, T14_PI * f_hi / rate); -#if (EQ_P >= 15) - f->hf = 2 * (sn << (EQ_P - 15)); -#else - f->hf = 2 * (sn >> (15 - EQ_P)); -#endif -} - -static void -reset_eq(struct EQF *f) -{ - memset(f->fL, 0, sizeof(f->fL)); - memset(f->fH, 0, sizeof(f->fH)); - memset(f->h, 0, sizeof(f->h)); -} - -static int -eqf(struct EQF *f, int s) -{ - int i, r[3]; - - f->fL[0] += (f->lf * (s - f->fL[0]) + EQ_R) >> EQ_P; - f->fH[0] += (f->hf * (s - f->fH[0]) + EQ_R) >> EQ_P; - - for (i = 1; i < 4; i++) { - f->fL[i] += (f->lf * (f->fL[i - 1] - f->fL[i]) + EQ_R) >> EQ_P; - f->fH[i] += (f->hf * (f->fH[i - 1] - f->fH[i]) + EQ_R) >> EQ_P; - } - - r[0] = f->fL[3]; - r[1] = f->fH[3] - f->fL[3]; - r[2] = f->h[HISTOLD] - f->fH[3]; - - for (i = 0; i < 3; i++) { - r[i] = (r[i] * f->g[i]) >> EQ_P; - } - - for (i = HISTOLD; i > 0; i--) { - f->h[i] = f->h[i - 1]; - } - f->h[HISTNEW] = s; - - return (r[0] + r[1] + r[2]); -} - -#endif - -/*****************************************************************************/ -/***************************** PUBLIC FUNCTIONS ******************************/ -/*****************************************************************************/ - -extern void -crt_resize(struct CRT *v, int w, int h, int f, unsigned char *out) -{ - v->outw = w; - v->outh = h; - v->out_format = f; - v->out = out; -} - -extern void -crt_reset(struct CRT *v) -{ - v->hue = 0; - v->saturation = 10; - v->brightness = 0; - v->contrast = 180; - v->black_point = 0; - v->white_point = 100; - v->hsync = 0; - v->vsync = 0; -} - -extern void -crt_init(struct CRT *v, int w, int h, int f, unsigned char *out) -{ - memset(v, 0, sizeof(struct CRT)); - crt_resize(v, w, h, f, out); - crt_reset(v); - v->rn = 194; - - /* kilohertz to line sample conversion */ -#define kHz2L(kHz) (CRT_HRES * (kHz * 100) / L_FREQ) - - /* band gains are pre-scaled as 16-bit fixed point - * if you change the EQ_P define, you'll need to update these gains too - */ -#if (CRT_CC_SAMPLES == 4) - init_eq(&eqY, kHz2L(1500), kHz2L(3000), CRT_HRES, 65536, 8192, 9175); - init_eq(&eqI, kHz2L(80), kHz2L(1150), CRT_HRES, 65536, 65536, 1311); - init_eq(&eqQ, kHz2L(80), kHz2L(1000), CRT_HRES, 65536, 65536, 0); -#elif (CRT_CC_SAMPLES == 5) - init_eq(&eqY, kHz2L(1500), kHz2L(3000), CRT_HRES, 65536, 12192, 7775); - init_eq(&eqI, kHz2L(80), kHz2L(1150), CRT_HRES, 65536, 65536, 1311); - init_eq(&eqQ, kHz2L(80), kHz2L(1000), CRT_HRES, 65536, 65536, 0); -#else -#error "NTSC-CRT currently only supports 4 or 5 samples per chroma period." -#endif - -} - -extern void -crt_demodulate(struct CRT *v, int noise) -{ - /* made static so all this data does not go on the stack */ - static struct { - int y, i, q; - } out[AV_LEN + 1], *yiqA, *yiqB; - int i, j, line, rn; - signed char *sig; - int s = 0; - int field, ratio; - int *ccr; /* color carrier signal */ - int huesn, huecs; - int xnudge = -3, ynudge = 3; - int bright = v->brightness - (BLACK_LEVEL + v->black_point); - int bpp, pitch; -#if CRT_DO_BLOOM - int prev_e; /* filtered beam energy per scan line */ - int max_e; /* approx maximum energy in a scan line */ -#endif - - bpp = crt_bpp4fmt(v->out_format); - if (bpp == 0) { - return; - } - pitch = v->outw * bpp; - - crt_sincos14(&huesn, &huecs, ((v->hue % 360) + 33) * 8192 / 180); - huesn >>= 11; /* make 4-bit */ - huecs >>= 11; - - rn = v->rn; -#if !CRT_DO_VSYNC - /* determine field before we add noise, - * otherwise it's not reliably recoverable - */ - for (i = -CRT_VSYNC_WINDOW; i < CRT_VSYNC_WINDOW; i++) { - line = POSMOD(v->vsync + i, CRT_VRES); - sig = v->analog + line * CRT_HRES; - s = 0; - for (j = 0; j < CRT_HRES; j++) { - s += sig[j]; - if (s <= (CRT_VSYNC_THRESH * SYNC_LEVEL)) { - goto found_field; - } - } - } -found_field: - /* if vsync signal was in second half of line, odd field */ - field = (j > (CRT_HRES / 2)); - v->vsync = -3; -#endif -#if ((CRT_SYSTEM == CRT_SYSTEM_NTSCVHS) && CRT_VHS_NOISE) - line = ((rand() % 8) - 4) + 14; -#endif - for (i = 0; i < CRT_INPUT_SIZE; i++) { - int nn = noise; -#if ((CRT_SYSTEM == CRT_SYSTEM_NTSCVHS) && CRT_VHS_NOISE) - rn = rand(); - if (i > (CRT_INPUT_SIZE - CRT_HRES * (16 + ((rand() % 20) - 10))) && - i < (CRT_INPUT_SIZE - CRT_HRES * (5 + ((rand() % 8) - 4)))) { - int ln, sn, cs; - - ln = (i * line) / CRT_HRES; - crt_sincos14(&sn, &cs, ln * 8192 / 180); - nn = cs >> 8; - } -#else - rn = (214019 * rn + 140327895); -#endif - /* signal + noise */ - s = v->analog[i] + (((((rn >> 16) & 0xff) - 0x7f) * nn) >> 8); - if (s > 127) { s = 127; } - if (s < -127) { s = -127; } - v->inp[i] = s; - } - v->rn = rn; - -#if CRT_DO_VSYNC - /* Look for vertical sync. - * - * This is done by integrating the signal and - * seeing if it exceeds a threshold. The threshold of - * the vertical sync pulse is much higher because the - * vsync pulse is a lot longer than the hsync pulse. - * The signal needs to be integrated to lessen - * the noise in the signal. - */ - for (i = -CRT_VSYNC_WINDOW; i < CRT_VSYNC_WINDOW; i++) { - line = POSMOD(v->vsync + i, CRT_VRES); - sig = v->inp + line * CRT_HRES; - s = 0; - for (j = 0; j < CRT_HRES; j++) { - s += sig[j]; - /* increase the multiplier to make the vsync - * more stable when there is a lot of noise - */ - if (s <= (CRT_VSYNC_THRESH * SYNC_LEVEL)) { - goto vsync_found; - } - } - } -vsync_found: - v->vsync = line; /* vsync found (or gave up) at this line */ - /* if vsync signal was in second half of line, odd field */ - field = (j > (CRT_HRES / 2)); -#endif - -#if CRT_DO_BLOOM - max_e = (128 + (noise / 2)) * AV_LEN; - prev_e = (16384 / 8); -#endif - /* ratio of output height to active video lines in the signal */ - ratio = (v->outh << 16) / CRT_LINES; - ratio = (ratio + 32768) >> 16; - - field = (field * (ratio / 2)); - - for (line = CRT_TOP; line < CRT_BOT; line++) { - unsigned pos, ln, scanR; - int scanL, dx; - int L, R; - unsigned char *cL, *cR; -#if (CRT_CC_SAMPLES == 4) - int wave[CRT_CC_SAMPLES]; -#else - int waveI[CRT_CC_SAMPLES]; - int waveQ[CRT_CC_SAMPLES]; -#endif - int dci, dcq; /* decoded I, Q */ - int xpos, ypos; - int beg, end; - int phasealign; -#if CRT_DO_BLOOM - int line_w; -#endif - - beg = (line - CRT_TOP + 0) * (v->outh + v->v_fac) / CRT_LINES + field; - end = (line - CRT_TOP + 1) * (v->outh + v->v_fac) / CRT_LINES + field; - - if (beg >= v->outh) { continue; } - if (end > v->outh) { end = v->outh; } - - /* Look for horizontal sync. - * See comment above regarding vertical sync. - */ - ln = (POSMOD(line + v->vsync, CRT_VRES)) * CRT_HRES; - sig = v->inp + ln + v->hsync; - s = 0; - for (i = -CRT_HSYNC_WINDOW; i < CRT_HSYNC_WINDOW; i++) { - s += sig[SYNC_BEG + i]; - if (s <= (CRT_HSYNC_THRESH * SYNC_LEVEL)) { - break; - } - } -#if CRT_DO_HSYNC - v->hsync = POSMOD(i + v->hsync, CRT_HRES); -#else - v->hsync = 0; -#endif - - xpos = POSMOD(AV_BEG + v->hsync + xnudge, CRT_HRES); - ypos = POSMOD(line + v->vsync + ynudge, CRT_VRES); - pos = xpos + ypos * CRT_HRES; - - ccr = v->ccf[ypos % CRT_CC_VPER]; -#if (CRT_CC_SAMPLES == 4) - sig = v->inp + ln + (v->hsync & ~3); /* faster */ -#else - sig = v->inp + ln + (v->hsync - (v->hsync % CRT_CC_SAMPLES)); -#endif - for (i = CB_BEG; i < CB_BEG + (CB_CYCLES * CRT_CB_FREQ); i++) { - int p, n; - p = ccr[i % CRT_CC_SAMPLES] * 127 / 128; /* fraction of the previous */ - n = sig[i]; /* mixed with the new sample */ - ccr[i % CRT_CC_SAMPLES] = p + n; - } - - phasealign = POSMOD(v->hsync, CRT_CC_SAMPLES); - -#if (CRT_CC_SAMPLES == 4) - /* amplitude of carrier = saturation, phase difference = hue */ - dci = ccr[(phasealign + 1) & 3] - ccr[(phasealign + 3) & 3]; - dcq = ccr[(phasealign + 2) & 3] - ccr[(phasealign + 0) & 3]; - - wave[0] = ((dci * huecs - dcq * huesn) >> 4) * v->saturation; - wave[1] = ((dcq * huecs + dci * huesn) >> 4) * v->saturation; - wave[2] = -wave[0]; - wave[3] = -wave[1]; -#elif (CRT_CC_SAMPLES == 5) - { - int dciA, dciB; - int dcqA, dcqB; - int ang = (v->hue % 360); - int off180 = CRT_CC_SAMPLES / 2; - int off90 = CRT_CC_SAMPLES / 4; - int peakA = phasealign + off90; - int peakB = phasealign + 0; - dciA = dciB = dcqA = dcqB = 0; - /* amplitude of carrier = saturation, phase difference = hue */ - dciA = ccr[(peakA) % CRT_CC_SAMPLES]; - /* average */ - dciB = (ccr[(peakA + off180) % CRT_CC_SAMPLES] - + ccr[(peakA + off180 + 1) % CRT_CC_SAMPLES]) / 2; - dcqA = ccr[(peakB + off180) % CRT_CC_SAMPLES]; - dcqB = ccr[(peakB) % CRT_CC_SAMPLES]; - dci = dciA - dciB; - dcq = dcqA - dcqB; - /* create wave tables and rotate them by the hue adjustment angle */ - for (i = 0; i < CRT_CC_SAMPLES; i++) { - int sn, cs; - crt_sincos14(&sn, &cs, ang * 8192 / 180); - waveI[i] = ((dci * cs + dcq * sn) >> 15) * v->saturation; - /* Q is offset by 90 */ - crt_sincos14(&sn, &cs, (ang + 90) * 8192 / 180); - waveQ[i] = ((dci * cs + dcq * sn) >> 15) * v->saturation; - ang += (360 / CRT_CC_SAMPLES); - } - } -#endif - sig = v->inp + pos; -#if CRT_DO_BLOOM - s = 0; - for (i = 0; i < AV_LEN; i++) { - s += sig[i]; /* sum up the scan line */ - } - /* bloom emulation */ - prev_e = (prev_e * 123 / 128) + ((((max_e >> 1) - s) << 10) / max_e); - line_w = (AV_LEN * 112 / 128) + (prev_e >> 9); - - dx = (line_w << 12) / v->outw; - scanL = ((AV_LEN / 2) - (line_w >> 1) + 8) << 12; - scanR = (AV_LEN - 1) << 12; - - L = (scanL >> 12); - R = (scanR >> 12); -#else - dx = ((AV_LEN - 1) << 12) / v->outw; - scanL = 0; - scanR = (AV_LEN - 1) << 12; - L = 0; - R = AV_LEN; -#endif - reset_eq(&eqY); - reset_eq(&eqI); - reset_eq(&eqQ); - -#if (CRT_CC_SAMPLES == 4) - for (i = L; i < R; i++) { - out[i].y = eqf(&eqY, sig[i] + bright) << 4; - out[i].i = eqf(&eqI, sig[i] * wave[(i + 0) & 3] >> 9) >> 3; - out[i].q = eqf(&eqQ, sig[i] * wave[(i + 3) & 3] >> 9) >> 3; - } -#else - for (i = L; i < R; i++) { - out[i].y = eqf(&eqY, sig[i] + bright) << 4; - out[i].i = eqf(&eqI, sig[i] * waveI[i % CRT_CC_SAMPLES] >> 9) >> 3; - out[i].q = eqf(&eqQ, sig[i] * waveQ[i % CRT_CC_SAMPLES] >> 9) >> 3; - } -#endif - - cL = v->out + (beg * pitch); - cR = cL + pitch; - - for (pos = scanL; pos < scanR && cL < cR; pos += dx) { - int y, i, q; - int r, g, b; - int aa, bb; - - R = pos & 0xfff; - L = 0xfff - R; - s = pos >> 12; - - yiqA = out + s; - yiqB = out + s + 1; - - /* interpolate between samples if needed */ - y = ((yiqA->y * L) >> 2) + ((yiqB->y * R) >> 2); - i = ((yiqA->i * L) >> 14) + ((yiqB->i * R) >> 14); - q = ((yiqA->q * L) >> 14) + ((yiqB->q * R) >> 14); - - /* YIQ to RGB */ - r = (((y + 3879 * i + 2556 * q) >> 12) * v->contrast) >> 8; - g = (((y - 1126 * i - 2605 * q) >> 12) * v->contrast) >> 8; - b = (((y - 4530 * i + 7021 * q) >> 12) * v->contrast) >> 8; - - if (r < 0) r = 0; - if (g < 0) g = 0; - if (b < 0) b = 0; - if (r > 255) r = 255; - if (g > 255) g = 255; - if (b > 255) b = 255; - - if (v->blend) { - aa = (r << 16 | g << 8 | b); - - switch (v->out_format) { - case CRT_PIX_FORMAT_RGB: - case CRT_PIX_FORMAT_RGBA: - bb = cL[0] << 16 | cL[1] << 8 | cL[2]; - break; - case CRT_PIX_FORMAT_BGR: - case CRT_PIX_FORMAT_BGRA: - bb = cL[2] << 16 | cL[1] << 8 | cL[0]; - break; - case CRT_PIX_FORMAT_ARGB: - bb = cL[1] << 16 | cL[2] << 8 | cL[3]; - break; - case CRT_PIX_FORMAT_ABGR: - bb = cL[3] << 16 | cL[2] << 8 | cL[1]; - break; - default: - bb = 0; - break; - } - - /* blend with previous color there */ - bb = (((aa & 0xfefeff) >> 1) + ((bb & 0xfefeff) >> 1)); - } else { - bb = (r << 16 | g << 8 | b); - } - - switch (v->out_format) { - case CRT_PIX_FORMAT_RGB: - cL[0] = bb >> 16 & 0xff; - cL[1] = bb >> 8 & 0xff; - cL[2] = bb >> 0 & 0xff; - break; - - case CRT_PIX_FORMAT_RGBA: - cL[0] = bb >> 16 & 0xff; - cL[1] = bb >> 8 & 0xff; - cL[2] = bb >> 0 & 0xff; - cL[3] = 0xff; - break; - - case CRT_PIX_FORMAT_BGR: - cL[0] = bb >> 0 & 0xff; - cL[1] = bb >> 8 & 0xff; - cL[2] = bb >> 16 & 0xff; - break; - - case CRT_PIX_FORMAT_BGRA: - cL[0] = bb >> 0 & 0xff; - cL[1] = bb >> 8 & 0xff; - cL[2] = bb >> 16 & 0xff; - cL[3] = 0xff; - break; - - case CRT_PIX_FORMAT_ARGB: - cL[0] = 0xff; - cL[1] = bb >> 16 & 0xff; - cL[2] = bb >> 8 & 0xff; - cL[3] = bb >> 0 & 0xff; - break; - - case CRT_PIX_FORMAT_ABGR: - cL[0] = 0xff; - cL[1] = bb >> 0 & 0xff; - cL[2] = bb >> 8 & 0xff; - cL[3] = bb >> 16 & 0xff; - break; - - default: - break; - } - - cL += bpp; - } - - /* duplicate extra lines */ - for (s = beg + 1; s < (end - v->scanlines); s++) { - memcpy(v->out + s * pitch, v->out + (s - 1) * pitch, pitch); - } - } -} diff --git a/gfx/video_filters/ntsc_crt_filter/crt_core.h b/gfx/video_filters/ntsc_crt_filter/crt_core.h deleted file mode 100644 index 456191bb2a6..00000000000 --- a/gfx/video_filters/ntsc_crt_filter/crt_core.h +++ /dev/null @@ -1,145 +0,0 @@ -/*****************************************************************************/ -/* - * NTSC/CRT - integer-only NTSC video signal encoding / decoding emulation - * - * by EMMIR 2018-2023 - * - * YouTube: https://www.youtube.com/@EMMIR_KC/videos - * Discord: https://discord.com/invite/hdYctSmyQJ - */ -/*****************************************************************************/ -#ifndef _CRT_CORE_H_ -#define _CRT_CORE_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/* crt_core.h - * - * The demodulator. This is also where you can define which system to emulate. - * - */ - -/* library version */ -#define CRT_MAJOR 2 -#define CRT_MINOR 3 -#define CRT_PATCH 2 - - -#define CRT_SYSTEM_NTSC 0 /* standard NTSC */ -#define CRT_SYSTEM_NES 1 /* decode 6 or 9-bit NES pixels */ -#define CRT_SYSTEM_PV1K 2 /* Casio PV-1000 */ -#define CRT_SYSTEM_SNES 3 /* SNES - uses RGB */ -#define CRT_SYSTEM_TEMP 4 /* template implementation */ -#define CRT_SYSTEM_NTSCVHS 5 /* standard NTSC VHS */ -#define CRT_SYSTEM_NESRGB 6 /* encode RGB image with NES artifacts */ - -/* the system to be compiled */ -#ifndef CRT_SYSTEM -#define CRT_SYSTEM CRT_SYSTEM_NTSC -#endif - -#if (CRT_SYSTEM == CRT_SYSTEM_NES) -#include "crt_nes.h" -#elif (CRT_SYSTEM == CRT_SYSTEM_SNES) -#include "crt_snes.h" -#elif (CRT_SYSTEM == CRT_SYSTEM_NTSC) -#include "crt_ntsc.h" -#elif (CRT_SYSTEM == CRT_SYSTEM_PV1K) -#include "crt_pv1k.h" -#elif (CRT_SYSTEM == CRT_SYSTEM_TEMP) -#include "crt_template.h" -#elif (CRT_SYSTEM == CRT_SYSTEM_NTSCVHS) -#include "crt_ntscvhs.h" -#elif (CRT_SYSTEM == CRT_SYSTEM_NESRGB) -#include "crt_nesrgb.h" -#else -#error No system defined -#endif - -/* NOTE: this library does not use the alpha channel at all */ -#define CRT_PIX_FORMAT_RGB 0 /* 3 bytes per pixel [R,G,B,R,G,B,R,G,B...] */ -#define CRT_PIX_FORMAT_BGR 1 /* 3 bytes per pixel [B,G,R,B,G,R,B,G,R...] */ -#define CRT_PIX_FORMAT_ARGB 2 /* 4 bytes per pixel [A,R,G,B,A,R,G,B...] */ -#define CRT_PIX_FORMAT_RGBA 3 /* 4 bytes per pixel [R,G,B,A,R,G,B,A...] */ -#define CRT_PIX_FORMAT_ABGR 4 /* 4 bytes per pixel [A,B,G,R,A,B,G,R...] */ -#define CRT_PIX_FORMAT_BGRA 5 /* 4 bytes per pixel [B,G,R,A,B,G,R,A...] */ - -/* do bloom emulation (side effect: makes screen have black borders) */ -#define CRT_DO_BLOOM 0 /* does not work for NES */ -#define CRT_DO_VSYNC 1 /* look for VSYNC */ -#define CRT_DO_HSYNC 1 /* look for HSYNC */ - -struct CRT { - signed char analog[CRT_INPUT_SIZE]; - signed char inp[CRT_INPUT_SIZE]; /* CRT input, can be noisy */ - - int outw, outh; /* output width/height */ - int out_format; /* output pixel format (one of the CRT_PIX_FORMATs) */ - unsigned char *out; /* output image */ - - int hue, brightness, contrast, saturation; /* common monitor settings */ - int black_point, white_point; /* user-adjustable */ - int scanlines; /* leave gaps between lines if necessary */ - int blend; /* blend new field onto previous image */ - unsigned v_fac; /* factor to stretch img vertically onto the output img */ - - /* internal data */ - int ccf[CRT_CC_VPER][CRT_CC_SAMPLES]; /* faster color carrier convergence */ - int hsync, vsync; /* keep track of sync over frames */ - int rn; /* seed for the 'random' noise */ -}; - -/* Initializes the library. Sets up filters. - * w - width of the output image - * h - height of the output image - * f - format of the output image - * out - pointer to output image data - */ -extern void crt_init(struct CRT *v, int w, int h, int f, unsigned char *out); - -/* Updates the output image parameters - * w - width of the output image - * h - height of the output image - * f - format of the output image - * out - pointer to output image data - */ -extern void crt_resize(struct CRT *v, int w, int h, int f, unsigned char *out); - -/* Resets the CRT settings back to their defaults */ -extern void crt_reset(struct CRT *v); - -/* Modulates RGB image into an analog NTSC signal - * s - struct containing settings to apply to this field - */ -extern void crt_modulate(struct CRT *v, struct NTSC_SETTINGS *s); - -/* Demodulates the NTSC signal generated by crt_modulate() - * noise - the amount of noise added to the signal (0 - inf) - */ -extern void crt_demodulate(struct CRT *v, int noise); - -/* Get the bytes per pixel for a certain CRT_PIX_FORMAT_ - * - * format - the format to get the bytes per pixel for - * - * returns 0 if the specified format does not exist - */ -extern int crt_bpp4fmt(int format); - -/*****************************************************************************/ -/*************************** FIXED POINT SIN/COS *****************************/ -/*****************************************************************************/ - -#define T14_2PI 16384 -#define T14_MASK (T14_2PI - 1) -#define T14_PI (T14_2PI / 2) - -extern void crt_sincos14(int *s, int *c, int n); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/gfx/video_filters/ntsc_crt_filter/crt_ntsc.c b/gfx/video_filters/ntsc_crt_filter/crt_ntsc.c deleted file mode 100644 index 107a2eace93..00000000000 --- a/gfx/video_filters/ntsc_crt_filter/crt_ntsc.c +++ /dev/null @@ -1,331 +0,0 @@ -/*****************************************************************************/ -/* - * NTSC/CRT - integer-only NTSC video signal encoding / decoding emulation - * - * by EMMIR 2018-2023 - * - * YouTube: https://www.youtube.com/@EMMIR_KC/videos - * Discord: https://discord.com/invite/hdYctSmyQJ - */ -/*****************************************************************************/ - -#include "crt_core.h" - -#if (CRT_SYSTEM == CRT_SYSTEM_NTSC) -#include -#include - -#if (CRT_CHROMA_PATTERN == 1) -/* 227.5 subcarrier cycles per line means every other line has reversed phase */ -#define CC_PHASE(ln) (((ln) & 1) ? -1 : 1) -#else -#define CC_PHASE(ln) (1) -#endif - -#define EXP_P 11 -#define EXP_ONE (1 << EXP_P) -#define EXP_MASK (EXP_ONE - 1) -#define EXP_PI 6434 -#define EXP_MUL(x, y) (((x) * (y)) >> EXP_P) -#define EXP_DIV(x, y) (((x) << EXP_P) / (y)) - -static int e11[] = { - EXP_ONE, - 5567, /* e */ - 15133, /* e^2 */ - 41135, /* e^3 */ - 111817 /* e^4 */ -}; - -/* fixed point e^x */ -static int -expx(int n) -{ - int neg, idx, res; - int nxt, acc, del; - int i; - - if (n == 0) { - return EXP_ONE; - } - neg = n < 0; - if (neg) { - n = -n; - } - idx = n >> EXP_P; - res = EXP_ONE; - for (i = 0; i < idx / 4; i++) { - res = EXP_MUL(res, e11[4]); - } - idx &= 3; - if (idx > 0) { - res = EXP_MUL(res, e11[idx]); - } - - n &= EXP_MASK; - nxt = EXP_ONE; - acc = 0; - del = 1; - for (i = 1; i < 17; i++) { - acc += nxt / del; - nxt = EXP_MUL(nxt, n); - del *= i; - if (del > nxt || nxt <= 0 || del <= 0) { - break; - } - } - res = EXP_MUL(res, acc); - - if (neg) { - res = EXP_DIV(EXP_ONE, res); - } - return res; -} - -/*****************************************************************************/ -/********************************* FILTERS ***********************************/ -/*****************************************************************************/ - -/* infinite impulse response low pass filter for bandlimiting YIQ */ -static struct IIRLP { - int c; - int h; /* history */ -} iirY, iirI, iirQ; - -/* freq - total bandwidth - * limit - max frequency - */ -static void -init_iir(struct IIRLP *f, int freq, int limit) -{ - int rate; /* cycles/pixel rate */ - - memset(f, 0, sizeof(struct IIRLP)); - rate = (freq << 9) / limit; - f->c = EXP_ONE - expx(-((EXP_PI << 9) / rate)); -} - -static void -reset_iir(struct IIRLP *f) -{ - f->h = 0; -} - -/* hi-pass for debugging */ -#define HIPASS 0 - -static int -iirf(struct IIRLP *f, int s) -{ - f->h += EXP_MUL(s - f->h, f->c); -#if HIPASS - return s - f->h; -#else - return f->h; -#endif -} - -extern void -crt_modulate(struct CRT *v, struct NTSC_SETTINGS *s) -{ - int x, y, xo, yo; - int destw = AV_LEN; - int desth = ((CRT_LINES * 64500) >> 16); - int iccf[CRT_CC_SAMPLES]; - int ccmodI[CRT_CC_SAMPLES]; /* color phase for mod */ - int ccmodQ[CRT_CC_SAMPLES]; /* color phase for mod */ - int ccburst[CRT_CC_SAMPLES]; /* color phase for burst */ - int sn, cs, n, ph; - int inv_phase = 0; - int bpp; - - if (!s->iirs_initialized) { - init_iir(&iirY, L_FREQ, Y_FREQ); - init_iir(&iirI, L_FREQ, I_FREQ); - init_iir(&iirQ, L_FREQ, Q_FREQ); - s->iirs_initialized = 1; - } -#if CRT_DO_BLOOM - if (s->raw) { - destw = s->w; - desth = s->h; - if (destw > ((AV_LEN * 55500) >> 16)) { - destw = ((AV_LEN * 55500) >> 16); - } - if (desth > ((CRT_LINES * 63500) >> 16)) { - desth = ((CRT_LINES * 63500) >> 16); - } - } else { - destw = (AV_LEN * 55500) >> 16; - desth = (CRT_LINES * 63500) >> 16; - } -#else - if (s->raw) { - destw = s->w; - desth = s->h; - if (destw > AV_LEN) { - destw = AV_LEN; - } - if (desth > ((CRT_LINES * 64500) >> 16)) { - desth = ((CRT_LINES * 64500) >> 16); - } - } -#endif - if (s->as_color) { - for (x = 0; x < CRT_CC_SAMPLES; x++) { - n = s->hue + x * (360 / CRT_CC_SAMPLES); - crt_sincos14(&sn, &cs, (n + 33) * 8192 / 180); - ccburst[x] = sn >> 10; - crt_sincos14(&sn, &cs, n * 8192 / 180); - ccmodI[x] = sn >> 10; - crt_sincos14(&sn, &cs, (n - 90) * 8192 / 180); - ccmodQ[x] = sn >> 10; - } - } else { - memset(ccburst, 0, sizeof(ccburst)); - memset(ccmodI, 0, sizeof(ccmodI)); - memset(ccmodQ, 0, sizeof(ccmodQ)); - } - - bpp = crt_bpp4fmt(s->format); - if (bpp == 0) { - return; /* just to be safe */ - } - xo = AV_BEG + s->xoffset + (AV_LEN - destw) / 2; - yo = CRT_TOP + s->yoffset + (CRT_LINES - desth) / 2; - - s->field &= 1; - s->frame &= 1; - inv_phase = (s->field == s->frame); - ph = CC_PHASE(inv_phase); - - /* align signal */ - xo = (xo & ~3); - - for (n = 0; n < CRT_VRES; n++) { - int t; /* time */ - signed char *line = &v->analog[n * CRT_HRES]; - - t = LINE_BEG; - - if (n <= 3 || (n >= 7 && n <= 9)) { - /* equalizing pulses - small blips of sync, mostly blank */ - while (t < (4 * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; - while (t < (50 * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; - while (t < (54 * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; - while (t < (100 * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; - } else if (n >= 4 && n <= 6) { - int even[4] = { 46, 50, 96, 100 }; - int odd[4] = { 4, 50, 96, 100 }; - int *offs = even; - if (s->field == 1) { - offs = odd; - } - /* vertical sync pulse - small blips of blank, mostly sync */ - while (t < (offs[0] * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; - while (t < (offs[1] * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; - while (t < (offs[2] * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; - while (t < (offs[3] * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; - } else { - int cb; - - /* video line */ - while (t < SYNC_BEG) line[t++] = BLANK_LEVEL; /* FP */ - while (t < BW_BEG) line[t++] = SYNC_LEVEL; /* SYNC */ - while (t < AV_BEG) line[t++] = BLANK_LEVEL; /* BW + CB + BP */ - if (n < CRT_TOP) { - while (t < CRT_HRES) line[t++] = BLANK_LEVEL; - } - - /* CB_CYCLES of color burst at 3.579545 Mhz */ - for (t = CB_BEG; t < CB_BEG + (CB_CYCLES * CRT_CB_FREQ); t++) { -#if (CRT_CHROMA_PATTERN == 1) - int off180 = CRT_CC_SAMPLES / 2; - cb = ccburst[(t + inv_phase * off180) % CRT_CC_SAMPLES]; -#else - cb = ccburst[t % CRT_CC_SAMPLES]; -#endif - line[t] = (BLANK_LEVEL + (cb * BURST_LEVEL)) >> 5; - iccf[t % CRT_CC_SAMPLES] = line[t]; - } - } - } - - for (y = 0; y < desth; y++) { - int field_offset; - int sy; - - field_offset = (s->field * s->h + desth) / desth / 2; - sy = (y * s->h) / desth; - - sy += field_offset; - - if (sy >= s->h) sy = s->h; - - sy *= s->w; - - reset_iir(&iirY); - reset_iir(&iirI); - reset_iir(&iirQ); - - for (x = 0; x < destw; x++) { - int fy, fi, fq; - int rA, gA, bA; - const unsigned char *pix; - int ire; /* composite signal */ - int xoff; - - pix = s->data + ((((x * s->w) / destw) + sy) * bpp); - switch (s->format) { - case CRT_PIX_FORMAT_RGB: - case CRT_PIX_FORMAT_RGBA: - rA = pix[0]; - gA = pix[1]; - bA = pix[2]; - break; - case CRT_PIX_FORMAT_BGR: - case CRT_PIX_FORMAT_BGRA: - rA = pix[2]; - gA = pix[1]; - bA = pix[0]; - break; - case CRT_PIX_FORMAT_ARGB: - rA = pix[1]; - gA = pix[2]; - bA = pix[3]; - break; - case CRT_PIX_FORMAT_ABGR: - rA = pix[3]; - gA = pix[2]; - bA = pix[1]; - break; - default: - rA = gA = bA = 0; - break; - } - - /* RGB to YIQ */ - fy = (19595 * rA + 38470 * gA + 7471 * bA) >> 14; - fi = (39059 * rA - 18022 * gA - 21103 * bA) >> 14; - fq = (13894 * rA - 34275 * gA + 20382 * bA) >> 14; - ire = BLACK_LEVEL + v->black_point; - - xoff = (x + xo) % CRT_CC_SAMPLES; - /* bandlimit Y,I,Q */ - fy = iirf(&iirY, fy); - fi = iirf(&iirI, fi) * ph * ccmodI[xoff] >> 4; - fq = iirf(&iirQ, fq) * ph * ccmodQ[xoff] >> 4; - ire += (fy + fi + fq) * (WHITE_LEVEL * v->white_point / 100) >> 10; - if (ire < 0) ire = 0; - if (ire > 110) ire = 110; - - v->analog[(x + xo) + (y + yo) * CRT_HRES] = ire; - } - } - for (n = 0; n < CRT_CC_VPER; n++) { - for (x = 0; x < CRT_CC_SAMPLES; x++) { - v->ccf[n][x] = iccf[x] << 7; - } - } -} -#endif diff --git a/gfx/video_filters/ntsc_crt_filter/crt_ntsc.h b/gfx/video_filters/ntsc_crt_filter/crt_ntsc.h deleted file mode 100644 index 2a6f0e94770..00000000000 --- a/gfx/video_filters/ntsc_crt_filter/crt_ntsc.h +++ /dev/null @@ -1,130 +0,0 @@ -/*****************************************************************************/ -/* - * NTSC/CRT - integer-only NTSC video signal encoding / decoding emulation - * - * by EMMIR 2018-2023 - * - * YouTube: https://www.youtube.com/@EMMIR_KC/videos - * Discord: https://discord.com/invite/hdYctSmyQJ - */ -/*****************************************************************************/ -#ifndef _CRT_NTSC_H_ -#define _CRT_NTSC_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -/* crt_ntsc.h - * - * An interface to convert a digital image to an analog NTSC signal. - * - */ -/* 0 = vertical chroma (228 chroma clocks per line) */ -/* 1 = checkered chroma (227.5 chroma clocks per line) */ -#define CRT_CHROMA_PATTERN 1 - -/* chroma clocks (subcarrier cycles) per line */ -#if (CRT_CHROMA_PATTERN == 1) -#define CRT_CC_LINE 2275 -#else -/* this will give the 'rainbow' effect in the famous waterfall scene */ -#define CRT_CC_LINE 2280 -#endif - -/* NOTE, in general, increasing CRT_CB_FREQ reduces blur and bleed */ -#define CRT_CB_FREQ 4 /* carrier frequency relative to sample rate */ -#define CRT_HRES (CRT_CC_LINE * CRT_CB_FREQ / 10) /* horizontal res */ -#define CRT_VRES 262 /* vertical resolution */ -#define CRT_INPUT_SIZE (CRT_HRES * CRT_VRES) - -#define CRT_TOP 21 /* first line with active video */ -#define CRT_BOT 261 /* final line with active video */ -#define CRT_LINES (CRT_BOT - CRT_TOP) /* number of active video lines */ - -#define CRT_CC_SAMPLES 4 /* samples per chroma period (samples per 360 deg) */ -#define CRT_CC_VPER 1 /* vertical period in which the artifacts repeat */ - -/* search windows, in samples */ -#define CRT_HSYNC_WINDOW 8 -#define CRT_VSYNC_WINDOW 8 - -/* accumulated signal threshold required for sync detection. - * Larger = more stable, until it's so large that it is never reached in which - * case the CRT won't be able to sync - */ -#define CRT_HSYNC_THRESH 4 -#define CRT_VSYNC_THRESH 94 - -/* - * FULL HORIZONTAL LINE SIGNAL (~63500 ns) - * |---------------------------------------------------------------------------| - * HBLANK (~10900 ns) ACTIVE VIDEO (~52600 ns) - * |-------------------||------------------------------------------------------| - * - * - * WITHIN HBLANK PERIOD: - * - * FP (~1500 ns) SYNC (~4700 ns) BW (~600 ns) CB (~2500 ns) BP (~1600 ns) - * |--------------||---------------||------------||-------------||-------------| - * BLANK SYNC BLANK BLANK BLANK - * - */ -#define LINE_BEG 0 -#define FP_ns 1500 /* front porch */ -#define SYNC_ns 4700 /* sync tip */ -#define BW_ns 600 /* breezeway */ -#define CB_ns 2500 /* color burst */ -#define BP_ns 1600 /* back porch */ -#define AV_ns 52600 /* active video */ -#define HB_ns (FP_ns + SYNC_ns + BW_ns + CB_ns + BP_ns) /* h blank */ -/* line duration should be ~63500 ns */ -#define LINE_ns (FP_ns + SYNC_ns + BW_ns + CB_ns + BP_ns + AV_ns) - -/* convert nanosecond offset to its corresponding point on the sampled line */ -#define ns2pos(ns) ((ns) * CRT_HRES / LINE_ns) -/* starting points for all the different pulses */ -#define FP_BEG ns2pos(0) -#define SYNC_BEG ns2pos(FP_ns) -#define BW_BEG ns2pos(FP_ns + SYNC_ns) -#define CB_BEG ns2pos(FP_ns + SYNC_ns + BW_ns) -#define BP_BEG ns2pos(FP_ns + SYNC_ns + BW_ns + CB_ns) -#define AV_BEG ns2pos(HB_ns) -#define AV_LEN ns2pos(AV_ns) - -/* somewhere between 7 and 12 cycles */ -#define CB_CYCLES 10 - -/* frequencies for bandlimiting */ -#define L_FREQ 1431818 /* full line */ -#define Y_FREQ 420000 /* Luma (Y) 4.2 MHz of the 14.31818 MHz */ -#define I_FREQ 150000 /* Chroma (I) 1.5 MHz of the 14.31818 MHz */ -#define Q_FREQ 55000 /* Chroma (Q) 0.55 MHz of the 14.31818 MHz */ - -/* IRE units (100 = 1.0V, -40 = 0.0V) */ -#define WHITE_LEVEL 100 -#define BURST_LEVEL 20 -#define BLACK_LEVEL 7 -#define BLANK_LEVEL 0 -#define SYNC_LEVEL -40 - -struct NTSC_SETTINGS { - const unsigned char *data; /* image data */ - int format; /* pix format (one of the CRT_PIX_FORMATs in crt_core.h) */ - int w, h; /* width and height of image */ - int raw; /* 0 = scale image to fit monitor, 1 = don't scale */ - int as_color; /* 0 = monochrome, 1 = full color */ - int field; /* 0 = even, 1 = odd */ - int frame; /* 0 = even, 1 = odd */ - int hue; /* 0-359 */ - int xoffset; /* x offset in sample space. 0 is minimum value */ - int yoffset; /* y offset in # of lines. 0 is minimum value */ - /* make sure your NTSC_SETTINGS struct is zeroed out before you do anything */ - int iirs_initialized; /* internal state */ -}; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/gfx/video_filters/ntsc_crt_filter/ntsc_crt.c b/gfx/video_filters/ntsc_crt_filter/ntsc_crt.c deleted file mode 100644 index 2c70d8bec76..00000000000 --- a/gfx/video_filters/ntsc_crt_filter/ntsc_crt.c +++ /dev/null @@ -1,388 +0,0 @@ -/* RetroArch CPU Filter - NTSC/CRT - * - * Original NTSC/CRT library by EMMIR 2018-2023 - * https://www.youtube.com/@EMMIR_KC/videos - * - * RetroArch softfilter wrapper 2024 - * - * Compile (Linux): - * gcc -O2 -shared -fPIC -std=c99 \ - * -o ntsc_crt.so \ - * ntsc_crt.c crt_core.c crt_ntsc.c \ - * -lm - * - * Compile (Windows cross): - * x86_64-w64-mingw32-gcc -O2 -shared -std=c99 \ - * -o ntsc_crt.dll \ - * ntsc_crt.c crt_core.c crt_ntsc.c \ - * -lm - * - * Install: - * Copy ntsc_crt.so (or .dll) + ntsc_crt.filt to: - * /filters/video/ - */ - -#include "softfilter.h" -#include "crt_core.h" - -#include -#include -#include - -#ifdef RARCH_INTERNAL -#define softfilter_get_implementation ntsc_crt_get_implementation -#define softfilter_thread_data ntsc_crt_softfilter_thread_data -#define filter_data ntsc_crt_filter_data -#endif - -/* ----------------------------------------------------------------------- - * Default tuning — these become the starting values shown in the .filt - * ----------------------------------------------------------------------- */ -#define DEFAULT_NOISE 2 /* 0 = clean signal, ~10 = noisy */ -#define DEFAULT_HUE 0 /* 0-359 */ -#define DEFAULT_BRIGHTNESS 0 /* monitor brightness offset */ -#define DEFAULT_CONTRAST 180 /* monitor contrast */ -#define DEFAULT_SATURATION 10 /* color saturation */ -#define DEFAULT_SCANLINES 0 /* 1 = dark gap between scanlines */ -#define DEFAULT_BLEND 1 /* 1 = blend fields (less flicker) */ -#define DEFAULT_AS_COLOR 1 /* 0 = monochrome, 1 = color */ -#define DEFAULT_BLACK_PT 0 /* black point adjustment */ -#define DEFAULT_WHITE_PT 100 /* white point adjustment */ - -/* ----------------------------------------------------------------------- - * Per-thread data (same pattern as scanline2x) - * ----------------------------------------------------------------------- */ -struct softfilter_thread_data -{ - void *out_data; - const void *in_data; - size_t out_pitch; - size_t in_pitch; - unsigned colfmt; - unsigned width; - unsigned height; - int first; - int last; -}; - -/* ----------------------------------------------------------------------- - * Main filter state - * ----------------------------------------------------------------------- */ -struct filter_data -{ - unsigned threads; - struct softfilter_thread_data *workers; - unsigned in_fmt; - - /* CRT library state */ - struct CRT crt; - struct NTSC_SETTINGS ntsc; - - /* output buffer (XRGB8888) */ - unsigned char *out_buf; - unsigned out_buf_w; - unsigned out_buf_h; - - /* user-settable params */ - int noise; - int hue; - int scanlines; - int blend; - int as_color; - - /* frame/field counter */ - int frame; - int field; -}; - -/* ----------------------------------------------------------------------- - * Helpers: XRGB8888 ↔ RGB packing - * ----------------------------------------------------------------------- */ - -/* Convert XRGB8888 source row to a packed RGB byte array for crt_modulate */ -static void xrgb8888_to_rgb(const uint32_t *src, unsigned char *dst, - unsigned w) -{ - unsigned x; - for (x = 0; x < w; x++) { - uint32_t c = src[x]; - dst[x * 3 + 0] = (c >> 16) & 0xff; /* R */ - dst[x * 3 + 1] = (c >> 8) & 0xff; /* G */ - dst[x * 3 + 2] = (c >> 0) & 0xff; /* B */ - } -} - -static void rgb565_to_rgb(const uint16_t *src, unsigned char *dst, - unsigned w) -{ - unsigned x; - for (x = 0; x < w; x++) { - uint16_t c = src[x]; - /* expand to 8-bit */ - dst[x * 3 + 0] = ((c >> 11) & 0x1f) << 3; - dst[x * 3 + 1] = ((c >> 5) & 0x3f) << 2; - dst[x * 3 + 2] = ((c >> 0) & 0x1f) << 3; - } -} - -/* Convert CRT XRGB output back to RetroArch XRGB8888 scanline */ -static void rgb_to_xrgb8888(const unsigned char *src, uint32_t *dst, - unsigned w) -{ - unsigned x; - for (x = 0; x < w; x++) { - dst[x] = 0xff000000u - | ((uint32_t)src[x * 3 + 0] << 16) - | ((uint32_t)src[x * 3 + 1] << 8) - | ((uint32_t)src[x * 3 + 2] << 0); - } -} - -/* ----------------------------------------------------------------------- - * softfilter API - * ----------------------------------------------------------------------- */ - -static unsigned ntsc_crt_input_fmts(void) -{ - return SOFTFILTER_FMT_XRGB8888 | SOFTFILTER_FMT_RGB565; -} - -static unsigned ntsc_crt_output_fmts(unsigned input_fmts) -{ - /* We always output XRGB8888 (CRT lib works in RGB) */ - (void)input_fmts; - return SOFTFILTER_FMT_XRGB8888; -} - -static unsigned ntsc_crt_threads(void *data) -{ - struct filter_data *filt = (struct filter_data*)data; - return filt->threads; -} - -/* Output is the same size as input — the CRT effect is spatial, not scaling */ -static void ntsc_crt_output_size(void *data, - unsigned *out_width, unsigned *out_height, - unsigned width, unsigned height) -{ - (void)data; - *out_width = width; - *out_height = height; -} - -static void *ntsc_crt_create(const struct softfilter_config *config, - unsigned in_fmt, unsigned out_fmt, - unsigned max_width, unsigned max_height, - unsigned threads, softfilter_simd_mask_t simd, void *userdata) -{ - struct filter_data *filt; - unsigned char *out_buf; - (void)out_fmt; - (void)simd; - (void)userdata; - - filt = (struct filter_data*)calloc(1, sizeof(*filt)); - if (!filt) return NULL; - - filt->workers = (struct softfilter_thread_data*) - calloc(1, sizeof(struct softfilter_thread_data)); - if (!filt->workers) { free(filt); return NULL; } - - /* single-threaded — CRT state is not thread-safe */ - filt->threads = 1; - filt->in_fmt = in_fmt; - - /* Allocate CRT output buffer: RGB (3 bpp) */ - out_buf = (unsigned char*)malloc(max_width * max_height * 3); - if (!out_buf) { free(filt->workers); free(filt); return NULL; } - - filt->out_buf = out_buf; - filt->out_buf_w = max_width; - filt->out_buf_h = max_height; - - /* Init CRT library */ - crt_init(&filt->crt, max_width, max_height, - CRT_PIX_FORMAT_RGB, out_buf); - - /* Read tunable params from .filt config (falls back to defaults) */ - if (config) { - config->get_int(userdata, "noise", &filt->noise, DEFAULT_NOISE); - config->get_int(userdata, "hue", &filt->hue, DEFAULT_HUE); - config->get_int(userdata, "as_color", &filt->as_color, DEFAULT_AS_COLOR); - config->get_int(userdata, "scanlines", &filt->scanlines, DEFAULT_SCANLINES); - config->get_int(userdata, "blend", &filt->blend, DEFAULT_BLEND); - config->get_int(userdata, "brightness", &filt->crt.brightness, DEFAULT_BRIGHTNESS); - config->get_int(userdata, "contrast", &filt->crt.contrast, DEFAULT_CONTRAST); - config->get_int(userdata, "saturation", &filt->crt.saturation, DEFAULT_SATURATION); - config->get_int(userdata, "black_point",&filt->crt.black_point, DEFAULT_BLACK_PT); - config->get_int(userdata, "white_point",&filt->crt.white_point, DEFAULT_WHITE_PT); - } else { - filt->noise = DEFAULT_NOISE; - filt->hue = DEFAULT_HUE; - filt->as_color = DEFAULT_AS_COLOR; - filt->scanlines = DEFAULT_SCANLINES; - filt->blend = DEFAULT_BLEND; - filt->crt.brightness = DEFAULT_BRIGHTNESS; - filt->crt.contrast = DEFAULT_CONTRAST; - filt->crt.saturation = DEFAULT_SATURATION; - filt->crt.black_point = DEFAULT_BLACK_PT; - filt->crt.white_point = DEFAULT_WHITE_PT; - } - - filt->crt.scanlines = filt->scanlines; - filt->crt.blend = filt->blend; - filt->crt.hue = filt->hue; - - /* NTSC_SETTINGS — zeroed by calloc, just set fields */ - memset(&filt->ntsc, 0, sizeof(filt->ntsc)); - filt->ntsc.as_color = filt->as_color; - filt->ntsc.hue = filt->hue; - filt->ntsc.format = CRT_PIX_FORMAT_RGB; - - filt->frame = 0; - filt->field = 0; - - return filt; -} - -static void ntsc_crt_destroy(void *data) -{ - struct filter_data *filt = (struct filter_data*)data; - if (!filt) return; - if (filt->out_buf) free(filt->out_buf); - free(filt->workers); - free(filt); -} - -/* ----------------------------------------------------------------------- - * The actual work callback — called from RetroArch's worker thread - * ----------------------------------------------------------------------- */ -static void ntsc_crt_work_cb(void *data, void *thread_data) -{ - struct filter_data *filt = (struct filter_data*)data; - struct softfilter_thread_data *thr = (struct softfilter_thread_data*)thread_data; - - unsigned width = thr->width; - unsigned height = thr->height; - size_t in_pitch = thr->in_pitch; - size_t out_pitch = thr->out_pitch; - - /* ---- Convert input to RGB byte-array for crt_modulate ---- */ - unsigned char *rgb_in = (unsigned char*)malloc(width * height * 3); - if (!rgb_in) return; - - if (thr->colfmt == SOFTFILTER_FMT_XRGB8888) { - unsigned y; - for (y = 0; y < height; y++) { - const uint32_t *row = (const uint32_t*) - ((const uint8_t*)thr->in_data + y * in_pitch); - xrgb8888_to_rgb(row, rgb_in + y * width * 3, width); - } - } else { - unsigned y; - for (y = 0; y < height; y++) { - const uint16_t *row = (const uint16_t*) - ((const uint8_t*)thr->in_data + y * in_pitch); - rgb565_to_rgb(row, rgb_in + y * width * 3, width); - } - } - - /* ---- Resize CRT output buffer if needed ---- */ - if (filt->out_buf_w != width || filt->out_buf_h != height) { - unsigned char *new_buf = (unsigned char*)realloc(filt->out_buf, - width * height * 3); - if (!new_buf) { free(rgb_in); return; } - filt->out_buf = new_buf; - filt->out_buf_w = width; - filt->out_buf_h = height; - crt_resize(&filt->crt, width, height, CRT_PIX_FORMAT_RGB, filt->out_buf); - } - filt->crt.out = filt->out_buf; - - /* ---- Set up NTSC_SETTINGS for this frame ---- */ - filt->ntsc.data = rgb_in; - filt->ntsc.format = CRT_PIX_FORMAT_RGB; - filt->ntsc.w = (int)width; - filt->ntsc.h = (int)height; - filt->ntsc.raw = 0; /* scale to fit */ - filt->ntsc.field = filt->field; - filt->ntsc.frame = filt->frame & 1; - filt->ntsc.hue = filt->hue; - - /* ---- Encode → signal → decode ---- */ - crt_modulate(&filt->crt, &filt->ntsc); - crt_demodulate(&filt->crt, filt->noise); - - /* ---- Advance field/frame counters ---- */ - filt->field ^= 1; - if (filt->field == 0) filt->frame++; - - /* ---- Copy RGB output → XRGB8888 destination ---- */ - { - unsigned y; - for (y = 0; y < height; y++) { - uint32_t *dst_row = (uint32_t*) - ((uint8_t*)thr->out_data + y * out_pitch); - const unsigned char *src_row = filt->out_buf + y * width * 3; - rgb_to_xrgb8888(src_row, dst_row, width); - } - } - - free(rgb_in); -} - -/* ----------------------------------------------------------------------- - * Packet submission (same structure as scanline2x) - * ----------------------------------------------------------------------- */ -static void ntsc_crt_packets(void *data, - struct softfilter_work_packet *packets, - void *output, size_t output_stride, - const void *input, unsigned width, unsigned height, - size_t input_stride) -{ - struct filter_data *filt = (struct filter_data*)data; - struct softfilter_thread_data *thr = &filt->workers[0]; - - thr->out_data = (uint8_t*)output; - thr->in_data = (const uint8_t*)input; - thr->out_pitch = output_stride; - thr->in_pitch = input_stride; - thr->width = width; - thr->height = height; - thr->colfmt = filt->in_fmt; - - packets[0].work = ntsc_crt_work_cb; - packets[0].thread_data = thr; -} - -/* ----------------------------------------------------------------------- - * Implementation descriptor — matches struct softfilter_implementation - * ----------------------------------------------------------------------- */ -static const struct softfilter_implementation ntsc_crt_impl = { - ntsc_crt_input_fmts, - ntsc_crt_output_fmts, - - ntsc_crt_create, - ntsc_crt_destroy, - - ntsc_crt_threads, - ntsc_crt_output_size, - ntsc_crt_packets, - - SOFTFILTER_API_VERSION, - "NTSC/CRT", - "ntsc_crt", -}; - -const struct softfilter_implementation *softfilter_get_implementation( - softfilter_simd_mask_t simd) -{ - (void)simd; - return &ntsc_crt_impl; -} - -#ifdef RARCH_INTERNAL -#undef softfilter_get_implementation -#undef softfilter_thread_data -#undef filter_data -#endif diff --git a/gfx/video_filters/ntsc_crt_filter/ntsc_crt.filt b/gfx/video_filters/ntsc_crt_filter/ntsc_crt.filt deleted file mode 100644 index fdb71df1fb0..00000000000 --- a/gfx/video_filters/ntsc_crt_filter/ntsc_crt.filt +++ /dev/null @@ -1,25 +0,0 @@ -filter = ntsc_crt - -# --- Signal --- -# Amount of noise added to the analog signal (0 = clean, 10 = very noisy) -ntsc_crt_noise = 2 - -# Hue rotation in degrees (0-359) -ntsc_crt_hue = 0 - -# 0 = monochrome / 1 = full color -ntsc_crt_as_color = 1 - -# --- Monitor --- -ntsc_crt_brightness = 0 -ntsc_crt_contrast = 180 -ntsc_crt_saturation = 10 -ntsc_crt_black_point = 0 -ntsc_crt_white_point = 100 - -# --- Display --- -# 1 = dark gap between scanlines (classic CRT look) -ntsc_crt_scanlines = 0 - -# 1 = blend even/odd fields together (reduces flicker, softer look) -ntsc_crt_blend = 1 diff --git a/griffin/griffin.c b/griffin/griffin.c index 18d8dccc3d7..11badc6b934 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1034,6 +1034,7 @@ FILTERS #include "../gfx/video_filters/picoscale_256x_320x240.c" #include "../gfx/video_filters/upscale_240x160_320x240.c" #include "../gfx/video_filters/upscale_mix_240x160_320x240.c" +#include "../gfx/video_filters/ntsc_crt.c" #endif #ifdef HAVE_DSP_FILTER From e5a74e7cf3c95a065072546c8d9461747d234654 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 07:56:39 +0200 Subject: [PATCH 08/22] Add missing files --- gfx/video_filters/ntsc_crt.c | 1512 +++++++++++++++++++++++++++++++ gfx/video_filters/ntsc_crt.filt | 25 + 2 files changed, 1537 insertions(+) create mode 100644 gfx/video_filters/ntsc_crt.c create mode 100644 gfx/video_filters/ntsc_crt.filt diff --git a/gfx/video_filters/ntsc_crt.c b/gfx/video_filters/ntsc_crt.c new file mode 100644 index 00000000000..26b01fccf1e --- /dev/null +++ b/gfx/video_filters/ntsc_crt.c @@ -0,0 +1,1512 @@ +/* RetroArch CPU Filter - NTSC/CRT + * + * Original NTSC/CRT library by EMMIR 2018-2023 + * https://www.youtube.com/@EMMIR_KC/videos + * https://discord.com/invite/hdYctSmyQJ + * + * RetroArch softfilter wrapper 2024 + * + * This is a consolidated single-file build of: + * crt_core.h crt_core.c + * crt_ntsc.h crt_ntsc.c + * ntsc_crt.c + */ + +#include "softfilter.h" + +#include +#include +#include + +#ifdef RARCH_INTERNAL +#define softfilter_get_implementation ntsc_crt_get_implementation +#define softfilter_thread_data ntsc_crt_softfilter_thread_data +#define filter_data ntsc_crt_filter_data +#endif + +/*****************************************************************************/ +/*****************************************************************************/ +/**** ****/ +/**** NTSC/CRT integer-only NTSC video signal ****/ +/**** encoding / decoding emulation (EMMIR 2018-2023) ****/ +/**** ****/ +/*****************************************************************************/ +/*****************************************************************************/ + +/* library version */ +#define CRT_MAJOR 2 +#define CRT_MINOR 3 +#define CRT_PATCH 2 + +/* NOTE: this library does not use the alpha channel at all */ +#define CRT_PIX_FORMAT_RGB 0 /* 3 bytes per pixel [R,G,B,R,G,B,R,G,B...] */ +#define CRT_PIX_FORMAT_BGR 1 /* 3 bytes per pixel [B,G,R,B,G,R,B,G,R...] */ +#define CRT_PIX_FORMAT_ARGB 2 /* 4 bytes per pixel [A,R,G,B,A,R,G,B...] */ +#define CRT_PIX_FORMAT_RGBA 3 /* 4 bytes per pixel [R,G,B,A,R,G,B,A...] */ +#define CRT_PIX_FORMAT_ABGR 4 /* 4 bytes per pixel [A,B,G,R,A,B,G,R...] */ +#define CRT_PIX_FORMAT_BGRA 5 /* 4 bytes per pixel [B,G,R,A,B,G,R,A...] */ + +/* do bloom emulation (side effect: makes screen have black borders) */ +#define CRT_DO_BLOOM 0 /* does not work for NES */ +#define CRT_DO_VSYNC 1 /* look for VSYNC */ +#define CRT_DO_HSYNC 1 /* look for HSYNC */ + +/*****************************************************************************/ +/******************** NTSC system parameters (from crt_ntsc.h) ***************/ +/*****************************************************************************/ + +/* 0 = vertical chroma (228 chroma clocks per line) */ +/* 1 = checkered chroma (227.5 chroma clocks per line) */ +#define CRT_CHROMA_PATTERN 1 + +/* chroma clocks (subcarrier cycles) per line */ +#if (CRT_CHROMA_PATTERN == 1) +#define CRT_CC_LINE 2275 +#else +/* this will give the 'rainbow' effect in the famous waterfall scene */ +#define CRT_CC_LINE 2280 +#endif + +/* NOTE, in general, increasing CRT_CB_FREQ reduces blur and bleed */ +#define CRT_CB_FREQ 4 /* carrier frequency relative to sample rate */ +#define CRT_HRES (CRT_CC_LINE * CRT_CB_FREQ / 10) /* horizontal res */ +#define CRT_VRES 262 /* vertical resolution */ +#define CRT_INPUT_SIZE (CRT_HRES * CRT_VRES) + +#define CRT_TOP 21 /* first line with active video */ +#define CRT_BOT 261 /* final line with active video */ +#define CRT_LINES (CRT_BOT - CRT_TOP) /* number of active video lines */ + +#define CRT_CC_SAMPLES 4 /* samples per chroma period (samples per 360 deg) */ +#define CRT_CC_VPER 1 /* vertical period in which the artifacts repeat */ + +/* search windows, in samples */ +#define CRT_HSYNC_WINDOW 8 +#define CRT_VSYNC_WINDOW 8 + +/* accumulated signal threshold required for sync detection. + * Larger = more stable, until it's so large that it is never reached in which + * case the CRT won't be able to sync + */ +#define CRT_HSYNC_THRESH 4 +#define CRT_VSYNC_THRESH 94 + +/* + * FULL HORIZONTAL LINE SIGNAL (~63500 ns) + * |---------------------------------------------------------------------------| + * HBLANK (~10900 ns) ACTIVE VIDEO (~52600 ns) + * |-------------------||------------------------------------------------------| + * + * + * WITHIN HBLANK PERIOD: + * + * FP (~1500 ns) SYNC (~4700 ns) BW (~600 ns) CB (~2500 ns) BP (~1600 ns) + * |--------------||---------------||------------||-------------||-------------| + * BLANK SYNC BLANK BLANK BLANK + * + */ +#define LINE_BEG 0 +#define FP_ns 1500 /* front porch */ +#define SYNC_ns 4700 /* sync tip */ +#define BW_ns 600 /* breezeway */ +#define CB_ns 2500 /* color burst */ +#define BP_ns 1600 /* back porch */ +#define AV_ns 52600 /* active video */ +#define HB_ns (FP_ns + SYNC_ns + BW_ns + CB_ns + BP_ns) /* h blank */ +/* line duration should be ~63500 ns */ +#define LINE_ns (FP_ns + SYNC_ns + BW_ns + CB_ns + BP_ns + AV_ns) + +/* convert nanosecond offset to its corresponding point on the sampled line */ +#define ns2pos(ns) ((ns) * CRT_HRES / LINE_ns) +/* starting points for all the different pulses */ +#define FP_BEG ns2pos(0) +#define SYNC_BEG ns2pos(FP_ns) +#define BW_BEG ns2pos(FP_ns + SYNC_ns) +#define CB_BEG ns2pos(FP_ns + SYNC_ns + BW_ns) +#define BP_BEG ns2pos(FP_ns + SYNC_ns + BW_ns + CB_ns) +#define AV_BEG ns2pos(HB_ns) +#define AV_LEN ns2pos(AV_ns) + +/* somewhere between 7 and 12 cycles */ +#define CB_CYCLES 10 + +/* frequencies for bandlimiting */ +#define L_FREQ 1431818 /* full line */ +#define Y_FREQ 420000 /* Luma (Y) 4.2 MHz of the 14.31818 MHz */ +#define I_FREQ 150000 /* Chroma (I) 1.5 MHz of the 14.31818 MHz */ +#define Q_FREQ 55000 /* Chroma (Q) 0.55 MHz of the 14.31818 MHz */ + +/* IRE units (100 = 1.0V, -40 = 0.0V) */ +#define WHITE_LEVEL 100 +#define BURST_LEVEL 20 +#define BLACK_LEVEL 7 +#define BLANK_LEVEL 0 +#define SYNC_LEVEL -40 + +struct NTSC_SETTINGS { + const unsigned char *data; /* image data */ + int format; /* pix format (one of the CRT_PIX_FORMATs) */ + int w, h; /* width and height of image */ + int raw; /* 0 = scale image to fit monitor, 1 = don't scale */ + int as_color; /* 0 = monochrome, 1 = full color */ + int field; /* 0 = even, 1 = odd */ + int frame; /* 0 = even, 1 = odd */ + int hue; /* 0-359 */ + int xoffset; /* x offset in sample space. 0 is minimum value */ + int yoffset; /* y offset in # of lines. 0 is minimum value */ + /* make sure your NTSC_SETTINGS struct is zeroed out before you do anything */ + int iirs_initialized; /* internal state */ +}; + +/*****************************************************************************/ +/********************** CRT struct (from crt_core.h) *************************/ +/*****************************************************************************/ + +struct CRT { + signed char analog[CRT_INPUT_SIZE]; + signed char inp[CRT_INPUT_SIZE]; /* CRT input, can be noisy */ + + int outw, outh; /* output width/height */ + int out_format; /* output pixel format (one of the CRT_PIX_FORMATs) */ + unsigned char *out; /* output image */ + + int hue, brightness, contrast, saturation; /* common monitor settings */ + int black_point, white_point; /* user-adjustable */ + int scanlines; /* leave gaps between lines if necessary */ + int blend; /* blend new field onto previous image */ + unsigned v_fac; /* factor to stretch img vertically onto the output img */ + + /* internal data */ + int ccf[CRT_CC_VPER][CRT_CC_SAMPLES]; /* faster color carrier convergence */ + int hsync, vsync; /* keep track of sync over frames */ + int rn; /* seed for the 'random' noise */ +}; + +/*****************************************************************************/ +/*************************** FIXED POINT SIN/COS *****************************/ +/*****************************************************************************/ + +#define T14_2PI 16384 +#define T14_MASK (T14_2PI - 1) +#define T14_PI (T14_2PI / 2) + +/*****************************************************************************/ +/*****************************************************************************/ +/**** crt_core.c - the demodulator implementation ****/ +/*****************************************************************************/ +/*****************************************************************************/ + +/* ensure negative values for x get properly modulo'd */ +#define POSMOD(x, n) (((x) % (n) + (n)) % (n)) + +static int sigpsin15[18] = { /* significant points on sine wave (15-bit) */ + 0x0000, + 0x0c88,0x18f8,0x2528,0x30f8,0x3c50,0x4718,0x5130,0x5a80, + 0x62f0,0x6a68,0x70e0,0x7640,0x7a78,0x7d88,0x7f60,0x8000, + 0x7f60 +}; + +static int +sintabil8(int n) +{ + int f, i, a, b; + + /* looks scary but if you don't change T14_2PI + * it won't cause out of bounds memory reads + */ + f = n >> 0 & 0xff; + i = n >> 8 & 0xff; + a = sigpsin15[i]; + b = sigpsin15[i + 1]; + return (a + ((b - a) * f >> 8)); +} + +/* 14-bit interpolated sine/cosine */ +static void +crt_sincos14(int *s, int *c, int n) +{ + int h; + + n &= T14_MASK; + h = n & ((T14_2PI >> 1) - 1); + + if (h > ((T14_2PI >> 2) - 1)) { + *c = -sintabil8(h - (T14_2PI >> 2)); + *s = sintabil8((T14_2PI >> 1) - h); + } else { + *c = sintabil8((T14_2PI >> 2) - h); + *s = sintabil8(h); + } + if (n > ((T14_2PI >> 1) - 1)) { + *c = -*c; + *s = -*s; + } +} + +static int +crt_bpp4fmt(int format) +{ + switch (format) { + case CRT_PIX_FORMAT_RGB: + case CRT_PIX_FORMAT_BGR: + return 3; + case CRT_PIX_FORMAT_ARGB: + case CRT_PIX_FORMAT_RGBA: + case CRT_PIX_FORMAT_ABGR: + case CRT_PIX_FORMAT_BGRA: + return 4; + default: + return 0; + } +} + +/*****************************************************************************/ +/********************************* FILTERS ***********************************/ +/*****************************************************************************/ + +/* convolution is much faster but the EQ looks softer, more authentic, and more analog */ +#define USE_CONVOLUTION 0 +#define USE_7_SAMPLE_KERNEL 1 +#define USE_6_SAMPLE_KERNEL 0 +#define USE_5_SAMPLE_KERNEL 0 + +#if (CRT_CC_SAMPLES != 4) +/* the current convolutions do not filter properly at > 4 samples */ +#undef USE_CONVOLUTION +#define USE_CONVOLUTION 0 +#endif + +#if USE_CONVOLUTION + +/* NOT 3 band equalizer, faster convolution instead. + * eq function names preserved to keep code clean + */ +static struct EQF { + int h[7]; +} eqY, eqI, eqQ; + +/* params unused to keep the function the same */ +static void +init_eq(struct EQF *f, + int f_lo, int f_hi, int rate, + int g_lo, int g_mid, int g_hi) +{ + memset(f, 0, sizeof(struct EQF)); +} + +static void +reset_eq(struct EQF *f) +{ + memset(f->h, 0, sizeof(f->h)); +} + +static int +eqf(struct EQF *f, int s) +{ + int i; + int *h = f->h; + + for (i = 6; i > 0; i--) { + h[i] = h[i - 1]; + } + h[0] = s; +#if USE_7_SAMPLE_KERNEL + /* index : 0 1 2 3 4 5 6 */ + /* weight: 1 4 7 8 7 4 1 */ + return (s + h[6] + ((h[1] + h[5]) * 4) + ((h[2] + h[4]) * 7) + (h[3] * 8)) >> 5; +#elif USE_6_SAMPLE_KERNEL + /* index : 0 1 2 3 4 5 */ + /* weight: 1 3 4 4 3 1 */ + return (s + h[5] + 3 * (h[1] + h[4]) + 4 * (h[2] + h[3])) >> 4; +#elif USE_5_SAMPLE_KERNEL + /* index : 0 1 2 3 4 */ + /* weight: 1 2 2 2 1 */ + return (s + h[4] + ((h[1] + h[2] + h[3]) << 1)) >> 3; +#else + /* index : 0 1 2 3 */ + /* weight: 1 1 1 1*/ + return (s + h[3] + h[1] + h[2]) >> 2; +#endif +} + +#else + +#define HISTLEN 3 +#define HISTOLD (HISTLEN - 1) /* oldest entry */ +#define HISTNEW 0 /* newest entry */ + +#define EQ_P 16 /* if changed, the gains will need to be adjusted */ +#define EQ_R (1 << (EQ_P - 1)) /* rounding */ +/* three band equalizer */ +static struct EQF { + int lf, hf; /* fractions */ + int g[3]; /* gains */ + int fL[4]; + int fH[4]; + int h[HISTLEN]; /* history */ +} eqY, eqI, eqQ; + +/* f_lo - low cutoff frequency + * f_hi - high cutoff frequency + * rate - sampling rate + * g_lo, g_mid, g_hi - gains + */ +static void +init_eq(struct EQF *f, + int f_lo, int f_hi, int rate, + int g_lo, int g_mid, int g_hi) +{ + int sn, cs; + + memset(f, 0, sizeof(struct EQF)); + + f->g[0] = g_lo; + f->g[1] = g_mid; + f->g[2] = g_hi; + + crt_sincos14(&sn, &cs, T14_PI * f_lo / rate); +#if (EQ_P >= 15) + f->lf = 2 * (sn << (EQ_P - 15)); +#else + f->lf = 2 * (sn >> (15 - EQ_P)); +#endif + crt_sincos14(&sn, &cs, T14_PI * f_hi / rate); +#if (EQ_P >= 15) + f->hf = 2 * (sn << (EQ_P - 15)); +#else + f->hf = 2 * (sn >> (15 - EQ_P)); +#endif +} + +static void +reset_eq(struct EQF *f) +{ + memset(f->fL, 0, sizeof(f->fL)); + memset(f->fH, 0, sizeof(f->fH)); + memset(f->h, 0, sizeof(f->h)); +} + +static int +eqf(struct EQF *f, int s) +{ + int i, r[3]; + + f->fL[0] += (f->lf * (s - f->fL[0]) + EQ_R) >> EQ_P; + f->fH[0] += (f->hf * (s - f->fH[0]) + EQ_R) >> EQ_P; + + for (i = 1; i < 4; i++) { + f->fL[i] += (f->lf * (f->fL[i - 1] - f->fL[i]) + EQ_R) >> EQ_P; + f->fH[i] += (f->hf * (f->fH[i - 1] - f->fH[i]) + EQ_R) >> EQ_P; + } + + r[0] = f->fL[3]; + r[1] = f->fH[3] - f->fL[3]; + r[2] = f->h[HISTOLD] - f->fH[3]; + + for (i = 0; i < 3; i++) { + r[i] = (r[i] * f->g[i]) >> EQ_P; + } + + for (i = HISTOLD; i > 0; i--) { + f->h[i] = f->h[i - 1]; + } + f->h[HISTNEW] = s; + + return (r[0] + r[1] + r[2]); +} + +#endif + +/*****************************************************************************/ +/***************************** PUBLIC FUNCTIONS ******************************/ +/*****************************************************************************/ + +static void +crt_resize(struct CRT *v, int w, int h, int f, unsigned char *out) +{ + v->outw = w; + v->outh = h; + v->out_format = f; + v->out = out; +} + +static void +crt_reset(struct CRT *v) +{ + v->hue = 0; + v->saturation = 10; + v->brightness = 0; + v->contrast = 180; + v->black_point = 0; + v->white_point = 100; + v->hsync = 0; + v->vsync = 0; +} + +static void +crt_init(struct CRT *v, int w, int h, int f, unsigned char *out) +{ + memset(v, 0, sizeof(struct CRT)); + crt_resize(v, w, h, f, out); + crt_reset(v); + v->rn = 194; + + /* kilohertz to line sample conversion */ +#define kHz2L(kHz) (CRT_HRES * (kHz * 100) / L_FREQ) + + /* band gains are pre-scaled as 16-bit fixed point + * if you change the EQ_P define, you'll need to update these gains too + */ +#if (CRT_CC_SAMPLES == 4) + init_eq(&eqY, kHz2L(1500), kHz2L(3000), CRT_HRES, 65536, 8192, 9175); + init_eq(&eqI, kHz2L(80), kHz2L(1150), CRT_HRES, 65536, 65536, 1311); + init_eq(&eqQ, kHz2L(80), kHz2L(1000), CRT_HRES, 65536, 65536, 0); +#elif (CRT_CC_SAMPLES == 5) + init_eq(&eqY, kHz2L(1500), kHz2L(3000), CRT_HRES, 65536, 12192, 7775); + init_eq(&eqI, kHz2L(80), kHz2L(1150), CRT_HRES, 65536, 65536, 1311); + init_eq(&eqQ, kHz2L(80), kHz2L(1000), CRT_HRES, 65536, 65536, 0); +#else +#error "NTSC-CRT currently only supports 4 or 5 samples per chroma period." +#endif + +} + +static void +crt_demodulate(struct CRT *v, int noise) +{ + /* made static so all this data does not go on the stack */ + static struct { + int y, i, q; + } out[AV_LEN + 1], *yiqA, *yiqB; + int i, j, line, rn; + signed char *sig; + int s = 0; + int field, ratio; + int *ccr; /* color carrier signal */ + int huesn, huecs; + int xnudge = -3, ynudge = 3; + int bright = v->brightness - (BLACK_LEVEL + v->black_point); + int bpp, pitch; +#if CRT_DO_BLOOM + int prev_e; /* filtered beam energy per scan line */ + int max_e; /* approx maximum energy in a scan line */ +#endif + + bpp = crt_bpp4fmt(v->out_format); + if (bpp == 0) { + return; + } + pitch = v->outw * bpp; + + crt_sincos14(&huesn, &huecs, ((v->hue % 360) + 33) * 8192 / 180); + huesn >>= 11; /* make 4-bit */ + huecs >>= 11; + + rn = v->rn; +#if !CRT_DO_VSYNC + /* determine field before we add noise, + * otherwise it's not reliably recoverable + */ + for (i = -CRT_VSYNC_WINDOW; i < CRT_VSYNC_WINDOW; i++) { + line = POSMOD(v->vsync + i, CRT_VRES); + sig = v->analog + line * CRT_HRES; + s = 0; + for (j = 0; j < CRT_HRES; j++) { + s += sig[j]; + if (s <= (CRT_VSYNC_THRESH * SYNC_LEVEL)) { + goto found_field; + } + } + } +found_field: + /* if vsync signal was in second half of line, odd field */ + field = (j > (CRT_HRES / 2)); + v->vsync = -3; +#endif + for (i = 0; i < CRT_INPUT_SIZE; i++) { + int nn = noise; + rn = (214019 * rn + 140327895); + /* signal + noise */ + s = v->analog[i] + (((((rn >> 16) & 0xff) - 0x7f) * nn) >> 8); + if (s > 127) { s = 127; } + if (s < -127) { s = -127; } + v->inp[i] = s; + } + v->rn = rn; + +#if CRT_DO_VSYNC + /* Look for vertical sync. + * + * This is done by integrating the signal and + * seeing if it exceeds a threshold. The threshold of + * the vertical sync pulse is much higher because the + * vsync pulse is a lot longer than the hsync pulse. + * The signal needs to be integrated to lessen + * the noise in the signal. + */ + for (i = -CRT_VSYNC_WINDOW; i < CRT_VSYNC_WINDOW; i++) { + line = POSMOD(v->vsync + i, CRT_VRES); + sig = v->inp + line * CRT_HRES; + s = 0; + for (j = 0; j < CRT_HRES; j++) { + s += sig[j]; + /* increase the multiplier to make the vsync + * more stable when there is a lot of noise + */ + if (s <= (CRT_VSYNC_THRESH * SYNC_LEVEL)) { + goto vsync_found; + } + } + } +vsync_found: + v->vsync = line; /* vsync found (or gave up) at this line */ + /* if vsync signal was in second half of line, odd field */ + field = (j > (CRT_HRES / 2)); +#endif + +#if CRT_DO_BLOOM + max_e = (128 + (noise / 2)) * AV_LEN; + prev_e = (16384 / 8); +#endif + /* ratio of output height to active video lines in the signal */ + ratio = (v->outh << 16) / CRT_LINES; + ratio = (ratio + 32768) >> 16; + + field = (field * (ratio / 2)); + + for (line = CRT_TOP; line < CRT_BOT; line++) { + unsigned pos, ln, scanR; + int scanL, dx; + int L, R; + unsigned char *cL, *cR; +#if (CRT_CC_SAMPLES == 4) + int wave[CRT_CC_SAMPLES]; +#else + int waveI[CRT_CC_SAMPLES]; + int waveQ[CRT_CC_SAMPLES]; +#endif + int dci, dcq; /* decoded I, Q */ + int xpos, ypos; + int beg, end; + int phasealign; +#if CRT_DO_BLOOM + int line_w; +#endif + + beg = (line - CRT_TOP + 0) * (v->outh + v->v_fac) / CRT_LINES + field; + end = (line - CRT_TOP + 1) * (v->outh + v->v_fac) / CRT_LINES + field; + + if (beg >= v->outh) { continue; } + if (end > v->outh) { end = v->outh; } + + /* Look for horizontal sync. + * See comment above regarding vertical sync. + */ + ln = (POSMOD(line + v->vsync, CRT_VRES)) * CRT_HRES; + sig = v->inp + ln + v->hsync; + s = 0; + for (i = -CRT_HSYNC_WINDOW; i < CRT_HSYNC_WINDOW; i++) { + s += sig[SYNC_BEG + i]; + if (s <= (CRT_HSYNC_THRESH * SYNC_LEVEL)) { + break; + } + } +#if CRT_DO_HSYNC + v->hsync = POSMOD(i + v->hsync, CRT_HRES); +#else + v->hsync = 0; +#endif + + xpos = POSMOD(AV_BEG + v->hsync + xnudge, CRT_HRES); + ypos = POSMOD(line + v->vsync + ynudge, CRT_VRES); + pos = xpos + ypos * CRT_HRES; + + ccr = v->ccf[ypos % CRT_CC_VPER]; +#if (CRT_CC_SAMPLES == 4) + sig = v->inp + ln + (v->hsync & ~3); /* faster */ +#else + sig = v->inp + ln + (v->hsync - (v->hsync % CRT_CC_SAMPLES)); +#endif + for (i = CB_BEG; i < CB_BEG + (CB_CYCLES * CRT_CB_FREQ); i++) { + int p, n; + p = ccr[i % CRT_CC_SAMPLES] * 127 / 128; /* fraction of the previous */ + n = sig[i]; /* mixed with the new sample */ + ccr[i % CRT_CC_SAMPLES] = p + n; + } + + phasealign = POSMOD(v->hsync, CRT_CC_SAMPLES); + +#if (CRT_CC_SAMPLES == 4) + /* amplitude of carrier = saturation, phase difference = hue */ + dci = ccr[(phasealign + 1) & 3] - ccr[(phasealign + 3) & 3]; + dcq = ccr[(phasealign + 2) & 3] - ccr[(phasealign + 0) & 3]; + + wave[0] = ((dci * huecs - dcq * huesn) >> 4) * v->saturation; + wave[1] = ((dcq * huecs + dci * huesn) >> 4) * v->saturation; + wave[2] = -wave[0]; + wave[3] = -wave[1]; +#elif (CRT_CC_SAMPLES == 5) + { + int dciA, dciB; + int dcqA, dcqB; + int ang = (v->hue % 360); + int off180 = CRT_CC_SAMPLES / 2; + int off90 = CRT_CC_SAMPLES / 4; + int peakA = phasealign + off90; + int peakB = phasealign + 0; + dciA = dciB = dcqA = dcqB = 0; + /* amplitude of carrier = saturation, phase difference = hue */ + dciA = ccr[(peakA) % CRT_CC_SAMPLES]; + /* average */ + dciB = (ccr[(peakA + off180) % CRT_CC_SAMPLES] + + ccr[(peakA + off180 + 1) % CRT_CC_SAMPLES]) / 2; + dcqA = ccr[(peakB + off180) % CRT_CC_SAMPLES]; + dcqB = ccr[(peakB) % CRT_CC_SAMPLES]; + dci = dciA - dciB; + dcq = dcqA - dcqB; + /* create wave tables and rotate them by the hue adjustment angle */ + for (i = 0; i < CRT_CC_SAMPLES; i++) { + int sn, cs; + crt_sincos14(&sn, &cs, ang * 8192 / 180); + waveI[i] = ((dci * cs + dcq * sn) >> 15) * v->saturation; + /* Q is offset by 90 */ + crt_sincos14(&sn, &cs, (ang + 90) * 8192 / 180); + waveQ[i] = ((dci * cs + dcq * sn) >> 15) * v->saturation; + ang += (360 / CRT_CC_SAMPLES); + } + } +#endif + sig = v->inp + pos; +#if CRT_DO_BLOOM + s = 0; + for (i = 0; i < AV_LEN; i++) { + s += sig[i]; /* sum up the scan line */ + } + /* bloom emulation */ + prev_e = (prev_e * 123 / 128) + ((((max_e >> 1) - s) << 10) / max_e); + line_w = (AV_LEN * 112 / 128) + (prev_e >> 9); + + dx = (line_w << 12) / v->outw; + scanL = ((AV_LEN / 2) - (line_w >> 1) + 8) << 12; + scanR = (AV_LEN - 1) << 12; + + L = (scanL >> 12); + R = (scanR >> 12); +#else + dx = ((AV_LEN - 1) << 12) / v->outw; + scanL = 0; + scanR = (AV_LEN - 1) << 12; + L = 0; + R = AV_LEN; +#endif + reset_eq(&eqY); + reset_eq(&eqI); + reset_eq(&eqQ); + +#if (CRT_CC_SAMPLES == 4) + for (i = L; i < R; i++) { + out[i].y = eqf(&eqY, sig[i] + bright) << 4; + out[i].i = eqf(&eqI, sig[i] * wave[(i + 0) & 3] >> 9) >> 3; + out[i].q = eqf(&eqQ, sig[i] * wave[(i + 3) & 3] >> 9) >> 3; + } +#else + for (i = L; i < R; i++) { + out[i].y = eqf(&eqY, sig[i] + bright) << 4; + out[i].i = eqf(&eqI, sig[i] * waveI[i % CRT_CC_SAMPLES] >> 9) >> 3; + out[i].q = eqf(&eqQ, sig[i] * waveQ[i % CRT_CC_SAMPLES] >> 9) >> 3; + } +#endif + + cL = v->out + (beg * pitch); + cR = cL + pitch; + + for (pos = scanL; pos < scanR && cL < cR; pos += dx) { + int y, i, q; + int r, g, b; + int aa, bb; + + R = pos & 0xfff; + L = 0xfff - R; + s = pos >> 12; + + yiqA = out + s; + yiqB = out + s + 1; + + /* interpolate between samples if needed */ + y = ((yiqA->y * L) >> 2) + ((yiqB->y * R) >> 2); + i = ((yiqA->i * L) >> 14) + ((yiqB->i * R) >> 14); + q = ((yiqA->q * L) >> 14) + ((yiqB->q * R) >> 14); + + /* YIQ to RGB */ + r = (((y + 3879 * i + 2556 * q) >> 12) * v->contrast) >> 8; + g = (((y - 1126 * i - 2605 * q) >> 12) * v->contrast) >> 8; + b = (((y - 4530 * i + 7021 * q) >> 12) * v->contrast) >> 8; + + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + + if (v->blend) { + aa = (r << 16 | g << 8 | b); + + switch (v->out_format) { + case CRT_PIX_FORMAT_RGB: + case CRT_PIX_FORMAT_RGBA: + bb = cL[0] << 16 | cL[1] << 8 | cL[2]; + break; + case CRT_PIX_FORMAT_BGR: + case CRT_PIX_FORMAT_BGRA: + bb = cL[2] << 16 | cL[1] << 8 | cL[0]; + break; + case CRT_PIX_FORMAT_ARGB: + bb = cL[1] << 16 | cL[2] << 8 | cL[3]; + break; + case CRT_PIX_FORMAT_ABGR: + bb = cL[3] << 16 | cL[2] << 8 | cL[1]; + break; + default: + bb = 0; + break; + } + + /* blend with previous color there */ + bb = (((aa & 0xfefeff) >> 1) + ((bb & 0xfefeff) >> 1)); + } else { + bb = (r << 16 | g << 8 | b); + } + + switch (v->out_format) { + case CRT_PIX_FORMAT_RGB: + cL[0] = bb >> 16 & 0xff; + cL[1] = bb >> 8 & 0xff; + cL[2] = bb >> 0 & 0xff; + break; + + case CRT_PIX_FORMAT_RGBA: + cL[0] = bb >> 16 & 0xff; + cL[1] = bb >> 8 & 0xff; + cL[2] = bb >> 0 & 0xff; + cL[3] = 0xff; + break; + + case CRT_PIX_FORMAT_BGR: + cL[0] = bb >> 0 & 0xff; + cL[1] = bb >> 8 & 0xff; + cL[2] = bb >> 16 & 0xff; + break; + + case CRT_PIX_FORMAT_BGRA: + cL[0] = bb >> 0 & 0xff; + cL[1] = bb >> 8 & 0xff; + cL[2] = bb >> 16 & 0xff; + cL[3] = 0xff; + break; + + case CRT_PIX_FORMAT_ARGB: + cL[0] = 0xff; + cL[1] = bb >> 16 & 0xff; + cL[2] = bb >> 8 & 0xff; + cL[3] = bb >> 0 & 0xff; + break; + + case CRT_PIX_FORMAT_ABGR: + cL[0] = 0xff; + cL[1] = bb >> 0 & 0xff; + cL[2] = bb >> 8 & 0xff; + cL[3] = bb >> 16 & 0xff; + break; + + default: + break; + } + + cL += bpp; + } + + /* duplicate extra lines */ + for (s = beg + 1; s < (end - v->scanlines); s++) { + memcpy(v->out + s * pitch, v->out + (s - 1) * pitch, pitch); + } + } +} + +/*****************************************************************************/ +/*****************************************************************************/ +/**** crt_ntsc.c - the modulator implementation ****/ +/*****************************************************************************/ +/*****************************************************************************/ + +#if (CRT_CHROMA_PATTERN == 1) +/* 227.5 subcarrier cycles per line means every other line has reversed phase */ +#define CC_PHASE(ln) (((ln) & 1) ? -1 : 1) +#else +#define CC_PHASE(ln) (1) +#endif + +#define EXP_P 11 +#define EXP_ONE (1 << EXP_P) +#define EXP_MASK (EXP_ONE - 1) +#define EXP_PI 6434 +#define EXP_MUL(x, y) (((x) * (y)) >> EXP_P) +#define EXP_DIV(x, y) (((x) << EXP_P) / (y)) + +static int e11[] = { + EXP_ONE, + 5567, /* e */ + 15133, /* e^2 */ + 41135, /* e^3 */ + 111817 /* e^4 */ +}; + +/* fixed point e^x */ +static int +expx(int n) +{ + int neg, idx, res; + int nxt, acc, del; + int i; + + if (n == 0) { + return EXP_ONE; + } + neg = n < 0; + if (neg) { + n = -n; + } + idx = n >> EXP_P; + res = EXP_ONE; + for (i = 0; i < idx / 4; i++) { + res = EXP_MUL(res, e11[4]); + } + idx &= 3; + if (idx > 0) { + res = EXP_MUL(res, e11[idx]); + } + + n &= EXP_MASK; + nxt = EXP_ONE; + acc = 0; + del = 1; + for (i = 1; i < 17; i++) { + acc += nxt / del; + nxt = EXP_MUL(nxt, n); + del *= i; + if (del > nxt || nxt <= 0 || del <= 0) { + break; + } + } + res = EXP_MUL(res, acc); + + if (neg) { + res = EXP_DIV(EXP_ONE, res); + } + return res; +} + +/*****************************************************************************/ +/********************************* FILTERS ***********************************/ +/*****************************************************************************/ + +/* infinite impulse response low pass filter for bandlimiting YIQ */ +static struct IIRLP { + int c; + int h; /* history */ +} iirY, iirI, iirQ; + +/* freq - total bandwidth + * limit - max frequency + */ +static void +init_iir(struct IIRLP *f, int freq, int limit) +{ + int rate; /* cycles/pixel rate */ + + memset(f, 0, sizeof(struct IIRLP)); + rate = (freq << 9) / limit; + f->c = EXP_ONE - expx(-((EXP_PI << 9) / rate)); +} + +static void +reset_iir(struct IIRLP *f) +{ + f->h = 0; +} + +/* hi-pass for debugging */ +#define HIPASS 0 + +static int +iirf(struct IIRLP *f, int s) +{ + f->h += EXP_MUL(s - f->h, f->c); +#if HIPASS + return s - f->h; +#else + return f->h; +#endif +} + +static void +crt_modulate(struct CRT *v, struct NTSC_SETTINGS *s) +{ + int x, y, xo, yo; + int destw = AV_LEN; + int desth = ((CRT_LINES * 64500) >> 16); + int iccf[CRT_CC_SAMPLES]; + int ccmodI[CRT_CC_SAMPLES]; /* color phase for mod */ + int ccmodQ[CRT_CC_SAMPLES]; /* color phase for mod */ + int ccburst[CRT_CC_SAMPLES]; /* color phase for burst */ + int sn, cs, n, ph; + int inv_phase = 0; + int bpp; + + if (!s->iirs_initialized) { + init_iir(&iirY, L_FREQ, Y_FREQ); + init_iir(&iirI, L_FREQ, I_FREQ); + init_iir(&iirQ, L_FREQ, Q_FREQ); + s->iirs_initialized = 1; + } +#if CRT_DO_BLOOM + if (s->raw) { + destw = s->w; + desth = s->h; + if (destw > ((AV_LEN * 55500) >> 16)) { + destw = ((AV_LEN * 55500) >> 16); + } + if (desth > ((CRT_LINES * 63500) >> 16)) { + desth = ((CRT_LINES * 63500) >> 16); + } + } else { + destw = (AV_LEN * 55500) >> 16; + desth = (CRT_LINES * 63500) >> 16; + } +#else + if (s->raw) { + destw = s->w; + desth = s->h; + if (destw > AV_LEN) { + destw = AV_LEN; + } + if (desth > ((CRT_LINES * 64500) >> 16)) { + desth = ((CRT_LINES * 64500) >> 16); + } + } +#endif + if (s->as_color) { + for (x = 0; x < CRT_CC_SAMPLES; x++) { + n = s->hue + x * (360 / CRT_CC_SAMPLES); + crt_sincos14(&sn, &cs, (n + 33) * 8192 / 180); + ccburst[x] = sn >> 10; + crt_sincos14(&sn, &cs, n * 8192 / 180); + ccmodI[x] = sn >> 10; + crt_sincos14(&sn, &cs, (n - 90) * 8192 / 180); + ccmodQ[x] = sn >> 10; + } + } else { + memset(ccburst, 0, sizeof(ccburst)); + memset(ccmodI, 0, sizeof(ccmodI)); + memset(ccmodQ, 0, sizeof(ccmodQ)); + } + + bpp = crt_bpp4fmt(s->format); + if (bpp == 0) { + return; /* just to be safe */ + } + xo = AV_BEG + s->xoffset + (AV_LEN - destw) / 2; + yo = CRT_TOP + s->yoffset + (CRT_LINES - desth) / 2; + + s->field &= 1; + s->frame &= 1; + inv_phase = (s->field == s->frame); + ph = CC_PHASE(inv_phase); + + /* align signal */ + xo = (xo & ~3); + + for (n = 0; n < CRT_VRES; n++) { + int t; /* time */ + signed char *line = &v->analog[n * CRT_HRES]; + + t = LINE_BEG; + + if (n <= 3 || (n >= 7 && n <= 9)) { + /* equalizing pulses - small blips of sync, mostly blank */ + while (t < (4 * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; + while (t < (50 * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; + while (t < (54 * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; + while (t < (100 * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; + } else if (n >= 4 && n <= 6) { + int even[4] = { 46, 50, 96, 100 }; + int odd[4] = { 4, 50, 96, 100 }; + int *offs = even; + if (s->field == 1) { + offs = odd; + } + /* vertical sync pulse - small blips of blank, mostly sync */ + while (t < (offs[0] * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; + while (t < (offs[1] * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; + while (t < (offs[2] * CRT_HRES / 100)) line[t++] = SYNC_LEVEL; + while (t < (offs[3] * CRT_HRES / 100)) line[t++] = BLANK_LEVEL; + } else { + int cb; + + /* video line */ + while (t < SYNC_BEG) line[t++] = BLANK_LEVEL; /* FP */ + while (t < BW_BEG) line[t++] = SYNC_LEVEL; /* SYNC */ + while (t < AV_BEG) line[t++] = BLANK_LEVEL; /* BW + CB + BP */ + if (n < CRT_TOP) { + while (t < CRT_HRES) line[t++] = BLANK_LEVEL; + } + + /* CB_CYCLES of color burst at 3.579545 Mhz */ + for (t = CB_BEG; t < CB_BEG + (CB_CYCLES * CRT_CB_FREQ); t++) { +#if (CRT_CHROMA_PATTERN == 1) + int off180 = CRT_CC_SAMPLES / 2; + cb = ccburst[(t + inv_phase * off180) % CRT_CC_SAMPLES]; +#else + cb = ccburst[t % CRT_CC_SAMPLES]; +#endif + line[t] = (BLANK_LEVEL + (cb * BURST_LEVEL)) >> 5; + iccf[t % CRT_CC_SAMPLES] = line[t]; + } + } + } + + for (y = 0; y < desth; y++) { + int field_offset; + int sy; + + field_offset = (s->field * s->h + desth) / desth / 2; + sy = (y * s->h) / desth; + + sy += field_offset; + + if (sy >= s->h) sy = s->h; + + sy *= s->w; + + reset_iir(&iirY); + reset_iir(&iirI); + reset_iir(&iirQ); + + for (x = 0; x < destw; x++) { + int fy, fi, fq; + int rA, gA, bA; + const unsigned char *pix; + int ire; /* composite signal */ + int xoff; + + pix = s->data + ((((x * s->w) / destw) + sy) * bpp); + switch (s->format) { + case CRT_PIX_FORMAT_RGB: + case CRT_PIX_FORMAT_RGBA: + rA = pix[0]; + gA = pix[1]; + bA = pix[2]; + break; + case CRT_PIX_FORMAT_BGR: + case CRT_PIX_FORMAT_BGRA: + rA = pix[2]; + gA = pix[1]; + bA = pix[0]; + break; + case CRT_PIX_FORMAT_ARGB: + rA = pix[1]; + gA = pix[2]; + bA = pix[3]; + break; + case CRT_PIX_FORMAT_ABGR: + rA = pix[3]; + gA = pix[2]; + bA = pix[1]; + break; + default: + rA = gA = bA = 0; + break; + } + + /* RGB to YIQ */ + fy = (19595 * rA + 38470 * gA + 7471 * bA) >> 14; + fi = (39059 * rA - 18022 * gA - 21103 * bA) >> 14; + fq = (13894 * rA - 34275 * gA + 20382 * bA) >> 14; + ire = BLACK_LEVEL + v->black_point; + + xoff = (x + xo) % CRT_CC_SAMPLES; + /* bandlimit Y,I,Q */ + fy = iirf(&iirY, fy); + fi = iirf(&iirI, fi) * ph * ccmodI[xoff] >> 4; + fq = iirf(&iirQ, fq) * ph * ccmodQ[xoff] >> 4; + ire += (fy + fi + fq) * (WHITE_LEVEL * v->white_point / 100) >> 10; + if (ire < 0) ire = 0; + if (ire > 110) ire = 110; + + v->analog[(x + xo) + (y + yo) * CRT_HRES] = ire; + } + } + for (n = 0; n < CRT_CC_VPER; n++) { + for (x = 0; x < CRT_CC_SAMPLES; x++) { + v->ccf[n][x] = iccf[x] << 7; + } + } +} + +/*****************************************************************************/ +/*****************************************************************************/ +/**** ntsc_crt.c - RetroArch softfilter wrapper ****/ +/*****************************************************************************/ +/*****************************************************************************/ + +/* ----------------------------------------------------------------------- + * Default tuning — these become the starting values shown in the .filt + * ----------------------------------------------------------------------- */ +#define DEFAULT_NOISE 2 /* 0 = clean signal, ~10 = noisy */ +#define DEFAULT_HUE 0 /* 0-359 */ +#define DEFAULT_BRIGHTNESS 0 /* monitor brightness offset */ +#define DEFAULT_CONTRAST 180 /* monitor contrast */ +#define DEFAULT_SATURATION 10 /* color saturation */ +#define DEFAULT_SCANLINES 0 /* 1 = dark gap between scanlines */ +#define DEFAULT_BLEND 1 /* 1 = blend fields (less flicker) */ +#define DEFAULT_AS_COLOR 1 /* 0 = monochrome, 1 = color */ +#define DEFAULT_BLACK_PT 0 /* black point adjustment */ +#define DEFAULT_WHITE_PT 100 /* white point adjustment */ + +/* ----------------------------------------------------------------------- + * Per-thread data (same pattern as scanline2x) + * ----------------------------------------------------------------------- */ +struct softfilter_thread_data +{ + void *out_data; + const void *in_data; + size_t out_pitch; + size_t in_pitch; + unsigned colfmt; + unsigned width; + unsigned height; + int first; + int last; +}; + +/* ----------------------------------------------------------------------- + * Main filter state + * ----------------------------------------------------------------------- */ +struct filter_data +{ + unsigned threads; + struct softfilter_thread_data *workers; + unsigned in_fmt; + + /* CRT library state */ + struct CRT crt; + struct NTSC_SETTINGS ntsc; + + /* output buffer (XRGB8888) */ + unsigned char *out_buf; + unsigned out_buf_w; + unsigned out_buf_h; + + /* user-settable params */ + int noise; + int hue; + int scanlines; + int blend; + int as_color; + + /* frame/field counter */ + int frame; + int field; +}; + +/* ----------------------------------------------------------------------- + * Helpers: XRGB8888 ↔ RGB packing + * ----------------------------------------------------------------------- */ + +/* Convert XRGB8888 source row to a packed RGB byte array for crt_modulate */ +static void xrgb8888_to_rgb(const uint32_t *src, unsigned char *dst, + unsigned w) +{ + unsigned x; + for (x = 0; x < w; x++) { + uint32_t c = src[x]; + dst[x * 3 + 0] = (c >> 16) & 0xff; /* R */ + dst[x * 3 + 1] = (c >> 8) & 0xff; /* G */ + dst[x * 3 + 2] = (c >> 0) & 0xff; /* B */ + } +} + +static void rgb565_to_rgb(const uint16_t *src, unsigned char *dst, + unsigned w) +{ + unsigned x; + for (x = 0; x < w; x++) { + uint16_t c = src[x]; + /* expand to 8-bit */ + dst[x * 3 + 0] = ((c >> 11) & 0x1f) << 3; + dst[x * 3 + 1] = ((c >> 5) & 0x3f) << 2; + dst[x * 3 + 2] = ((c >> 0) & 0x1f) << 3; + } +} + +/* Convert CRT XRGB output back to RetroArch XRGB8888 scanline */ +static void rgb_to_xrgb8888(const unsigned char *src, uint32_t *dst, + unsigned w) +{ + unsigned x; + for (x = 0; x < w; x++) { + dst[x] = 0xff000000u + | ((uint32_t)src[x * 3 + 0] << 16) + | ((uint32_t)src[x * 3 + 1] << 8) + | ((uint32_t)src[x * 3 + 2] << 0); + } +} + +/* ----------------------------------------------------------------------- + * softfilter API + * ----------------------------------------------------------------------- */ + +static unsigned ntsc_crt_input_fmts(void) +{ + return SOFTFILTER_FMT_XRGB8888 | SOFTFILTER_FMT_RGB565; +} + +static unsigned ntsc_crt_output_fmts(unsigned input_fmts) +{ + /* We always output XRGB8888 (CRT lib works in RGB) */ + (void)input_fmts; + return SOFTFILTER_FMT_XRGB8888; +} + +static unsigned ntsc_crt_threads(void *data) +{ + struct filter_data *filt = (struct filter_data*)data; + return filt->threads; +} + +/* Output is the same size as input — the CRT effect is spatial, not scaling */ +static void ntsc_crt_output_size(void *data, + unsigned *out_width, unsigned *out_height, + unsigned width, unsigned height) +{ + (void)data; + *out_width = width; + *out_height = height; +} + +static void *ntsc_crt_create(const struct softfilter_config *config, + unsigned in_fmt, unsigned out_fmt, + unsigned max_width, unsigned max_height, + unsigned threads, softfilter_simd_mask_t simd, void *userdata) +{ + struct filter_data *filt; + unsigned char *out_buf; + (void)out_fmt; + (void)simd; + (void)userdata; + + filt = (struct filter_data*)calloc(1, sizeof(*filt)); + if (!filt) return NULL; + + filt->workers = (struct softfilter_thread_data*) + calloc(1, sizeof(struct softfilter_thread_data)); + if (!filt->workers) { free(filt); return NULL; } + + /* single-threaded — CRT state is not thread-safe */ + filt->threads = 1; + filt->in_fmt = in_fmt; + + /* Allocate CRT output buffer: RGB (3 bpp) */ + out_buf = (unsigned char*)malloc(max_width * max_height * 3); + if (!out_buf) { free(filt->workers); free(filt); return NULL; } + + filt->out_buf = out_buf; + filt->out_buf_w = max_width; + filt->out_buf_h = max_height; + + /* Init CRT library */ + crt_init(&filt->crt, max_width, max_height, + CRT_PIX_FORMAT_RGB, out_buf); + + /* Read tunable params from .filt config (falls back to defaults) */ + if (config) { + config->get_int(userdata, "noise", &filt->noise, DEFAULT_NOISE); + config->get_int(userdata, "hue", &filt->hue, DEFAULT_HUE); + config->get_int(userdata, "as_color", &filt->as_color, DEFAULT_AS_COLOR); + config->get_int(userdata, "scanlines", &filt->scanlines, DEFAULT_SCANLINES); + config->get_int(userdata, "blend", &filt->blend, DEFAULT_BLEND); + config->get_int(userdata, "brightness", &filt->crt.brightness, DEFAULT_BRIGHTNESS); + config->get_int(userdata, "contrast", &filt->crt.contrast, DEFAULT_CONTRAST); + config->get_int(userdata, "saturation", &filt->crt.saturation, DEFAULT_SATURATION); + config->get_int(userdata, "black_point",&filt->crt.black_point, DEFAULT_BLACK_PT); + config->get_int(userdata, "white_point",&filt->crt.white_point, DEFAULT_WHITE_PT); + } else { + filt->noise = DEFAULT_NOISE; + filt->hue = DEFAULT_HUE; + filt->as_color = DEFAULT_AS_COLOR; + filt->scanlines = DEFAULT_SCANLINES; + filt->blend = DEFAULT_BLEND; + filt->crt.brightness = DEFAULT_BRIGHTNESS; + filt->crt.contrast = DEFAULT_CONTRAST; + filt->crt.saturation = DEFAULT_SATURATION; + filt->crt.black_point = DEFAULT_BLACK_PT; + filt->crt.white_point = DEFAULT_WHITE_PT; + } + + filt->crt.scanlines = filt->scanlines; + filt->crt.blend = filt->blend; + filt->crt.hue = filt->hue; + + /* NTSC_SETTINGS — zeroed by calloc, just set fields */ + memset(&filt->ntsc, 0, sizeof(filt->ntsc)); + filt->ntsc.as_color = filt->as_color; + filt->ntsc.hue = filt->hue; + filt->ntsc.format = CRT_PIX_FORMAT_RGB; + + filt->frame = 0; + filt->field = 0; + + return filt; +} + +static void ntsc_crt_destroy(void *data) +{ + struct filter_data *filt = (struct filter_data*)data; + if (!filt) return; + if (filt->out_buf) free(filt->out_buf); + free(filt->workers); + free(filt); +} + +/* ----------------------------------------------------------------------- + * The actual work callback — called from RetroArch's worker thread + * ----------------------------------------------------------------------- */ +static void ntsc_crt_work_cb(void *data, void *thread_data) +{ + struct filter_data *filt = (struct filter_data*)data; + struct softfilter_thread_data *thr = (struct softfilter_thread_data*)thread_data; + + unsigned width = thr->width; + unsigned height = thr->height; + size_t in_pitch = thr->in_pitch; + size_t out_pitch = thr->out_pitch; + + /* ---- Convert input to RGB byte-array for crt_modulate ---- */ + unsigned char *rgb_in = (unsigned char*)malloc(width * height * 3); + if (!rgb_in) return; + + if (thr->colfmt == SOFTFILTER_FMT_XRGB8888) { + unsigned y; + for (y = 0; y < height; y++) { + const uint32_t *row = (const uint32_t*) + ((const uint8_t*)thr->in_data + y * in_pitch); + xrgb8888_to_rgb(row, rgb_in + y * width * 3, width); + } + } else { + unsigned y; + for (y = 0; y < height; y++) { + const uint16_t *row = (const uint16_t*) + ((const uint8_t*)thr->in_data + y * in_pitch); + rgb565_to_rgb(row, rgb_in + y * width * 3, width); + } + } + + /* ---- Resize CRT output buffer if needed ---- */ + if (filt->out_buf_w != width || filt->out_buf_h != height) { + unsigned char *new_buf = (unsigned char*)realloc(filt->out_buf, + width * height * 3); + if (!new_buf) { free(rgb_in); return; } + filt->out_buf = new_buf; + filt->out_buf_w = width; + filt->out_buf_h = height; + crt_resize(&filt->crt, width, height, CRT_PIX_FORMAT_RGB, filt->out_buf); + } + filt->crt.out = filt->out_buf; + + /* ---- Set up NTSC_SETTINGS for this frame ---- */ + filt->ntsc.data = rgb_in; + filt->ntsc.format = CRT_PIX_FORMAT_RGB; + filt->ntsc.w = (int)width; + filt->ntsc.h = (int)height; + filt->ntsc.raw = 0; /* scale to fit */ + filt->ntsc.field = filt->field; + filt->ntsc.frame = filt->frame & 1; + filt->ntsc.hue = filt->hue; + + /* ---- Encode → signal → decode ---- */ + crt_modulate(&filt->crt, &filt->ntsc); + crt_demodulate(&filt->crt, filt->noise); + + /* ---- Advance field/frame counters ---- */ + filt->field ^= 1; + if (filt->field == 0) filt->frame++; + + /* ---- Copy RGB output → XRGB8888 destination ---- */ + { + unsigned y; + for (y = 0; y < height; y++) { + uint32_t *dst_row = (uint32_t*) + ((uint8_t*)thr->out_data + y * out_pitch); + const unsigned char *src_row = filt->out_buf + y * width * 3; + rgb_to_xrgb8888(src_row, dst_row, width); + } + } + + free(rgb_in); +} + +/* ----------------------------------------------------------------------- + * Packet submission (same structure as scanline2x) + * ----------------------------------------------------------------------- */ +static void ntsc_crt_packets(void *data, + struct softfilter_work_packet *packets, + void *output, size_t output_stride, + const void *input, unsigned width, unsigned height, + size_t input_stride) +{ + struct filter_data *filt = (struct filter_data*)data; + struct softfilter_thread_data *thr = &filt->workers[0]; + + thr->out_data = (uint8_t*)output; + thr->in_data = (const uint8_t*)input; + thr->out_pitch = output_stride; + thr->in_pitch = input_stride; + thr->width = width; + thr->height = height; + thr->colfmt = filt->in_fmt; + + packets[0].work = ntsc_crt_work_cb; + packets[0].thread_data = thr; +} + +/* ----------------------------------------------------------------------- + * Implementation descriptor — matches struct softfilter_implementation + * ----------------------------------------------------------------------- */ +static const struct softfilter_implementation ntsc_crt_impl = { + ntsc_crt_input_fmts, + ntsc_crt_output_fmts, + + ntsc_crt_create, + ntsc_crt_destroy, + + ntsc_crt_threads, + ntsc_crt_output_size, + ntsc_crt_packets, + + SOFTFILTER_API_VERSION, + "NTSC/CRT", + "ntsc_crt", +}; + +const struct softfilter_implementation *softfilter_get_implementation( + softfilter_simd_mask_t simd) +{ + (void)simd; + return &ntsc_crt_impl; +} + +#ifdef RARCH_INTERNAL +#undef softfilter_get_implementation +#undef softfilter_thread_data +#undef filter_data +#endif diff --git a/gfx/video_filters/ntsc_crt.filt b/gfx/video_filters/ntsc_crt.filt new file mode 100644 index 00000000000..fdb71df1fb0 --- /dev/null +++ b/gfx/video_filters/ntsc_crt.filt @@ -0,0 +1,25 @@ +filter = ntsc_crt + +# --- Signal --- +# Amount of noise added to the analog signal (0 = clean, 10 = very noisy) +ntsc_crt_noise = 2 + +# Hue rotation in degrees (0-359) +ntsc_crt_hue = 0 + +# 0 = monochrome / 1 = full color +ntsc_crt_as_color = 1 + +# --- Monitor --- +ntsc_crt_brightness = 0 +ntsc_crt_contrast = 180 +ntsc_crt_saturation = 10 +ntsc_crt_black_point = 0 +ntsc_crt_white_point = 100 + +# --- Display --- +# 1 = dark gap between scanlines (classic CRT look) +ntsc_crt_scanlines = 0 + +# 1 = blend even/odd fields together (reduces flicker, softer look) +ntsc_crt_blend = 1 From 7a8fd93d6745adfc70a0539511c5dca038d0591f Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 08:52:15 +0200 Subject: [PATCH 09/22] Fix various warnings --- deps/rcheevos/src/rcheevos/condition.c | 2 +- libretro-common/audio/dsp_filters/reverb.c | 36 +++--- menu/menu_explore.c | 22 ++-- retroarch.c | 133 +++++++++------------ 4 files changed, 90 insertions(+), 103 deletions(-) diff --git a/deps/rcheevos/src/rcheevos/condition.c b/deps/rcheevos/src/rcheevos/condition.c index ff6da02bf83..fcb7788a817 100644 --- a/deps/rcheevos/src/rcheevos/condition.c +++ b/deps/rcheevos/src/rcheevos/condition.c @@ -166,7 +166,7 @@ static int rc_parse_operator(const char** memaddr) { void rc_condition_convert_to_operand(const rc_condition_t* condition, rc_operand_t* operand, rc_parse_state_t* parse) { if (condition->oper == RC_OPERATOR_NONE) { if (operand != &condition->operand1) - memcpy(operand, &condition->operand1, sizeof(*operand)); + *operand = condition->operand1; } else { uint8_t new_size = RC_MEMSIZE_32_BITS; diff --git a/libretro-common/audio/dsp_filters/reverb.c b/libretro-common/audio/dsp_filters/reverb.c index 62bdab2f7ed..0981047b4ab 100644 --- a/libretro-common/audio/dsp_filters/reverb.c +++ b/libretro-common/audio/dsp_filters/reverb.c @@ -109,7 +109,7 @@ struct revmodel static float revmodel_process(struct revmodel *rev, float in) { - int i; + unsigned i; float mono_out = 0.0f; float mono_in = in; float input = mono_in * rev->gain; @@ -125,7 +125,7 @@ static float revmodel_process(struct revmodel *rev, float in) static void revmodel_update(struct revmodel *rev) { - int i; + unsigned i; rev->wet1 = rev->wet * (rev->width / 2.0f + 0.5f); if (rev->mode >= freezemode) @@ -187,27 +187,27 @@ static void revmodel_setmode(struct revmodel *rev, float value) static void revmodel_init(struct revmodel *rev,int srate) { - unsigned c; - static const int comb_lengths[8] = { 1116,1188,1277,1356,1422,1491,1557,1617 }; - static const int allpass_lengths[4] = { 225,341,441,556 }; - double r = srate * (1 / 44100.0); + unsigned c; + static const int comb_lengths[8] = { 1116,1188,1277,1356,1422,1491,1557,1617 }; + static const int allpass_lengths[4] = { 225,341,441,556 }; + double r = srate * (1 / 44100.0); for (c = 0; c < numcombs; ++c) { - rev->bufcomb[c] = (float*)malloc(r * comb_lengths[c] * sizeof(float)); - rev->combL[c].buffer = rev->bufcomb[c]; - memset(rev->combL[c].buffer, 0, r * comb_lengths[c] * sizeof(float)); - rev->combL[c].bufsize=r*comb_lengths[c]; - } + unsigned bufsize = (unsigned)(r * comb_lengths[c]); + rev->bufcomb[c] = (float*)calloc(bufsize, sizeof(float)); + rev->combL[c].buffer = rev->bufcomb[c]; + rev->combL[c].bufsize = bufsize; + } for (c = 0; c < numallpasses; ++c) { - rev->bufallpass[c] = (float*)malloc(r * allpass_lengths[c] * sizeof(float)); - rev->allpassL[c].buffer = rev->bufallpass[c]; - memset(rev->allpassL[c].buffer, 0, r * allpass_lengths[c] * sizeof(float)); - rev->allpassL[c].bufsize=r*allpass_lengths[c]; + unsigned bufsize = (unsigned)(r * allpass_lengths[c]); + rev->bufallpass[c] = (float*)calloc(bufsize, sizeof(float)); + rev->allpassL[c].buffer = rev->bufallpass[c]; + rev->allpassL[c].bufsize = bufsize; rev->allpassL[c].feedback = 0.5f; - } + } revmodel_setwet(rev, initialwet); revmodel_setroomsize(rev, initialroom); @@ -224,7 +224,7 @@ struct reverb_data static void reverb_free(void *data) { - int i; + unsigned i; struct reverb_data *rev = (struct reverb_data*)data; for (i = 0; i < numcombs; i++) @@ -244,7 +244,7 @@ static void reverb_free(void *data) static void reverb_process(void *data, struct dspfilter_output *output, const struct dspfilter_input *input) { - int i; + unsigned i; float *out; struct reverb_data *rev = (struct reverb_data*)data; diff --git a/menu/menu_explore.c b/menu/menu_explore.c index 178598a163d..57518aa9279 100644 --- a/menu/menu_explore.c +++ b/menu/menu_explore.c @@ -1489,8 +1489,9 @@ unsigned menu_displaylist_explore(file_list_t *list, settings_t *settings) && !explore_by_info[cat].is_boolean && RBUF_LEN(state->by[cat]) > 1)) { - size_t _len = strlcpy(tmp, - msg_hash_to_str(explore_by_info[cat].by_enum), sizeof(tmp)); + size_t _len = 0; + strlcpy_append(tmp, sizeof(tmp), &_len, + msg_hash_to_str(explore_by_info[cat].by_enum)); if (is_top) { @@ -1502,20 +1503,21 @@ unsigned menu_displaylist_explore(file_list_t *list, settings_t *settings) entries[RBUF_LEN(entries) - 1]->str); else if (!explore_by_info[cat].is_boolean) { - _len += strlcpy (tmp + _len, " (", sizeof(tmp) - _len); - _len += snprintf(tmp + _len, sizeof(tmp) - _len, + strlcpy_append(tmp, sizeof(tmp), &_len, " ("); + _len += snprintf(tmp + _len, sizeof(tmp) - _len, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_EXPLORE_ITEMS_COUNT), (unsigned)RBUF_LEN(entries)); - strlcpy(tmp + _len, ")", sizeof(tmp) - _len); + if (_len >= sizeof(tmp)) + _len = sizeof(tmp) - 1; + strlcpy_append(tmp, sizeof(tmp), &_len, ")"); } } else if (i != state->view_levels) { - _len += strlcpy(tmp + _len, " (", sizeof(tmp) - _len); - _len += strlcpy(tmp + _len, - msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_RANGE_FILTER), - sizeof(tmp) - _len); - strlcpy(tmp + _len, ")", sizeof(tmp) - _len); + strlcpy_append(tmp, sizeof(tmp), &_len, " ("); + strlcpy_append(tmp, sizeof(tmp), &_len, + msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_RANGE_FILTER)); + strlcpy_append(tmp, sizeof(tmp), &_len, ")"); } explore_menu_entry(list, state, diff --git a/retroarch.c b/retroarch.c index 6c029f0c4d9..3469afb7376 100644 --- a/retroarch.c +++ b/retroarch.c @@ -6749,7 +6749,7 @@ static void retroarch_print_help(const char *arg0) fprintf(stdout, "Usage: %s [OPTIONS]... [FILE]\n\n", arg0); - _len = strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " -h, --help " "Show this help message.\n" " -v, --verbose " @@ -6759,30 +6759,29 @@ static void retroarch_print_help(const char *arg0) " -V, --version " "Show version.\n" " --features " - "Print available features compiled into program.\n" - , sizeof(buf) - _len); + "Print available features compiled into program.\n"); #ifdef HAVE_MENU - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " --menu " "Do not require content or libretro core to be loaded,\n" " " " starts directly in menu. If no arguments are passed to\n" " " - " the program, it is equivalent to using --menu as only argument.\n" - , sizeof(buf) - _len); + " the program, it is equivalent to using --menu as only argument.\n"); #endif #ifdef HAVE_CONFIGFILE - _len += strlcpy(buf + _len, " -c, --config=FILE " - "Path for config file.\n", sizeof(buf) - _len); + strlcpy_append(buf, sizeof(buf), &_len, + " -c, --config=FILE " + "Path for config file.\n"); #ifdef _WIN32 - _len += strlcpy(buf + _len, " " + strlcpy_append(buf, sizeof(buf), &_len, + " " " Defaults to retroarch.cfg in same directory as retroarch.exe.\n" " " - " If a default config is not found, the program will attempt to create one.\n" - , sizeof(buf) - _len); + " If a default config is not found, the program will attempt to create one.\n"); #else - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " " " By default looks for config in\n" " " @@ -6794,33 +6793,31 @@ static void retroarch_print_help(const char *arg0) " " " If a default config is not found, the program will attempt to create one\n" " " - " based on the skeleton config (" GLOBAL_CONFIG_DIR "/retroarch.cfg).\n" - , sizeof(buf) - _len); + " based on the skeleton config (" GLOBAL_CONFIG_DIR "/retroarch.cfg).\n"); #endif - _len += strlcpy(buf + _len, " --appendconfig=FILE " + strlcpy_append(buf, sizeof(buf), &_len, + " --appendconfig=FILE " "Extra config files are loaded in, and take priority over\n" " " " config selected in -c (or default). Multiple configs are\n" " " - " delimited by '|'.\n" - , sizeof(buf) - _len); + " delimited by '|'.\n"); #endif fputs(buf, stdout); buf[0] = '\0'; _len = 0; - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " --subsystem=NAME " "Use a subsystem of the libretro core. Multiple content\n" " " " files are loaded as multiple arguments. If a content\n" " " - " file is skipped, use a blank (\"\") command line argument.\n" - , sizeof(buf) - _len); + " file is skipped, use a blank (\"\") command line argument.\n"); #ifdef HAVE_DYNAMIC - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " -L, --libretro=FILE " "Path to libretro implementation. Overrides any config setting.\n" " " @@ -6828,35 +6825,31 @@ static void retroarch_print_help(const char *arg0) " " " 1. The full path to a core shared object library: path/to/_libretro.\n" " " - " 2. A core shared object library 'file name' (*): _libretro.\n" - , sizeof(buf) - _len); - _len += strlcpy(buf + _len, + " 2. A core shared object library 'file name' (*): _libretro.\n"); + strlcpy_append(buf, sizeof(buf), &_len, " " " 3. A core 'short name' (*): _libretro OR \n" " " " (*) If 'file name' or 'short name' do not correspond to an existing full file path,\n" " " - " the configured frontend 'cores' directory will be searched for a match.\n" - , sizeof(buf) - _len); + " the configured frontend 'cores' directory will be searched for a match.\n"); #endif - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " " " Content must be loaded in an order which depends on the\n" " " " particular subsystem used. See verbose log output to learn\n" " " - " how a particular subsystem wants content to be loaded.\n" - , sizeof(buf) - _len); + " how a particular subsystem wants content to be loaded.\n"); #ifdef HAVE_LIBRETRODB - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " --scan=PATH|FILE " - "Import content from path.\n" - , sizeof(buf) - _len); + "Import content from path.\n"); #endif - strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " -f, --fullscreen " "Start the program in fullscreen regardless of config setting.\n" " --set-shader=PATH " @@ -6864,8 +6857,7 @@ static void retroarch_print_help(const char *arg0) " " " Effectively overrides automatic shader presets.\n" " " - " An empty argument \"\" will disable automatic shader presets.\n" - , sizeof(buf) - _len); + " An empty argument \"\" will disable automatic shader presets.\n"); fputs(buf, stdout); buf[0] = '\0'; @@ -6873,12 +6865,18 @@ static void retroarch_print_help(const char *arg0) _len += snprintf(buf + _len, sizeof(buf) - _len," -N, --nodevice=PORT " "Disconnects controller device connected to PORT (1 to %d).\n", MAX_USERS); + if (_len >= sizeof(buf)) + _len = sizeof(buf) - 1; _len += snprintf(buf + _len, sizeof(buf) - _len," -A, --dualanalog=PORT " "Connect a DualAnalog controller to PORT (1 to %d).\n", MAX_USERS); + if (_len >= sizeof(buf)) + _len = sizeof(buf) - 1; _len += snprintf(buf + _len, sizeof(buf) - _len," -d, --device=PORT:ID " "Connect a generic device into PORT of the device (1 to %d).\n", MAX_USERS); + if (_len >= sizeof(buf)) + _len = sizeof(buf) - 1; - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " " " Format is PORT:ID, where ID is a number corresponding to the particular device.\n" " -M, --sram-mode=MODE " @@ -6886,11 +6884,10 @@ static void retroarch_print_help(const char *arg0) " " " 'noload-nosave', 'noload-save', 'load-nosave' or 'load-save'.\n" " " - " Note: 'noload-save' implies that save files *WILL BE OVERWRITTEN*.\n" - , sizeof(buf) - _len); + " Note: 'noload-save' implies that save files *WILL BE OVERWRITTEN*.\n"); #ifdef HAVE_NETWORKING - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " -H, --host " "Host netplay as user 1.\n" " -C, --connect=HOST " @@ -6902,87 +6899,77 @@ static void retroarch_print_help(const char *arg0) " --nick=NICK " "Picks a username (for use with netplay). Not mandatory.\n" " --check-frames=NUMBER " - "Check frames when using netplay.\n" - , sizeof(buf) - _len); + "Check frames when using netplay.\n"); #ifdef HAVE_NETWORK_CMD - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " --command " "Sends a command over UDP to an already running program process.\n" " " - " Available commands are listed if command is invalid.\n" - , sizeof(buf) - _len); + " Available commands are listed if command is invalid.\n"); #endif #endif #ifdef HAVE_BSV_MOVIE - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " -P, --play-replay=FILE " "Playback a replay file.\n" " -R, --record-replay=FILE " "Start recording a replay file from the beginning.\n" " --eof-exit " - "Exit upon reaching the end of the replay file.\n" - , sizeof(buf) - _len); + "Exit upon reaching the end of the replay file.\n"); #endif - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " -r, --record=FILE " "Path to record video file. Using mkv extension is recommended.\n" " --recordconfig " "Path to settings used during recording.\n" " --size=WIDTHxHEIGHT " - "Overrides output video size when recording.\n" - , sizeof(buf) - _len); + "Overrides output video size when recording.\n"); fputs(buf, stdout); buf[0] = '\0'; _len = 0; - _len = strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " -D, --detach " "Detach program from the running console. Not relevant for all platforms.\n" " --max-frames=NUMBER " - "Runs for the specified number of frames, then exits.\n" - , sizeof(buf) - _len); + "Runs for the specified number of frames, then exits.\n"); #ifdef HAVE_PATCH - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " -U, --ups=FILE " "Specifies path for UPS patch that will be applied to content.\n" " --bps=FILE " "Specifies path for BPS patch that will be applied to content.\n" " --ips=FILE " - "Specifies path for IPS patch that will be applied to content.\n" - , sizeof(buf) - _len); + "Specifies path for IPS patch that will be applied to content.\n"); #ifdef HAVE_XDELTA - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " --xdelta=FILE " - "Specifies path for Xdelta patch that will be applied to content.\n" - , sizeof(buf) - _len); + "Specifies path for Xdelta patch that will be applied to content.\n"); #endif /* HAVE_XDELTA */ - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " --no-patch " - "Disables all forms of content patching.\n" - , sizeof(buf) - _len); + "Disables all forms of content patching.\n"); #endif /* HAVE_PATCH */ #ifdef HAVE_SCREENSHOTS - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " --max-frames-ss " "Takes a screenshot at the end of max-frames.\n" " --max-frames-ss-path=FILE " - "Path to save the screenshot to at the end of max-frames.\n" - , sizeof(buf) - _len); + "Path to save the screenshot to at the end of max-frames.\n"); #endif #ifdef HAVE_ACCESSIBILITY - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " --accessibility " - "Enables accessibility for blind users using text-to-speech.\n" - , sizeof(buf) - _len); + "Enables accessibility for blind users using text-to-speech.\n"); #endif - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, " --load-menu-on-error " "Open menu instead of quitting if specified core or content fails to load.\n" " -e, --entryslot=NUMBER " @@ -6990,8 +6977,7 @@ static void retroarch_print_help(const char *arg0) " -s, --save=PATH " "Path for save files (*.srm). (DEPRECATED, use --appendconfig and savefile_directory)\n" " -S, --savestate=PATH " - "Path for the save state files (*.state). (DEPRECATED, use --appendconfig and savestate_directory)\n" - , sizeof(buf) - _len); + "Path for the save state files (*.state). (DEPRECATED, use --appendconfig and savestate_directory)\n"); /* Flush buffer here to avoid the error "error: string length ‘752’ * is greater than the length ‘509’ ISO C90 compilers are required @@ -7001,7 +6987,7 @@ static void retroarch_print_help(const char *arg0) #if defined(__linux__) || defined(__GNU__) || (defined(BSD) && !defined(__MACH__)) buf[0] = '\0'; _len = 0; - _len += strlcpy(buf + _len, + strlcpy_append(buf, sizeof(buf), &_len, "\nThe following environment variables are supported:\n\n" " LIBRETRO_ASSETS_DIRECTORY\n" " LIBRETRO_AUTOCONFIG_DIRECTORY\n" @@ -7011,8 +6997,7 @@ static void retroarch_print_help(const char *arg0) " LIBRETRO_SYSTEM_DIRECTORY\n" " LIBRETRO_VIDEO_FILTER_DIRECTORY\n" " LIBRETRO_VIDEO_SHADER_DIRECTORY\n\n" - "Refer to `man 6 retroarch' for a description of what they do.\n" - , sizeof(buf) - _len); + "Refer to `man 6 retroarch' for a description of what they do.\n"); fputs(buf, stdout); #endif } From dcf6cc7520c73c886d3dd79ac7dbe44082f9b838 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 09:21:31 +0200 Subject: [PATCH 10/22] Warning fixes #2 --- configuration.c | 6 +-- gfx/drivers_shader/slang_cache.cpp | 53 ++++++++++++-------- libretro-common/vfs/vfs_implementation_smb.c | 6 +-- libretro-common/vfs/vfs_implementation_smb.h | 2 +- menu/drivers/materialui.c | 1 - menu/drivers/ozone.c | 2 +- menu/drivers/rgui.c | 6 +-- menu/drivers/xmb.c | 2 +- retroarch.c | 8 +-- runahead.c | 2 +- runloop.c | 4 +- tasks/task_content.c | 2 +- tasks/task_movie.c | 4 +- 13 files changed, 55 insertions(+), 43 deletions(-) diff --git a/configuration.c b/configuration.c index 11128b1abaa..f8ff8127c29 100644 --- a/configuration.c +++ b/configuration.c @@ -5436,7 +5436,7 @@ void input_config_get_prefix(char *s, char len, char user, bool meta) */ static void input_config_save_keybinds_user(config_file_t *conf, unsigned user) { - size_t i = 0; + unsigned i; for (i = 0; input_config_bind_map_get_valid(i); i++) { char key[64]; @@ -5482,7 +5482,7 @@ static void input_config_save_keybinds_user_override(config_file_t *conf, unsigned user, unsigned bind_id, const struct retro_keybind *override_bind) { - size_t i = bind_id; + unsigned i = bind_id; if (input_config_bind_map_get_valid(i)) { @@ -5533,7 +5533,7 @@ static void input_config_save_keybinds_user_override(config_file_t *conf, static void input_config_save_keybinds_user_minimal(config_file_t *conf, unsigned user, const retro_keybind_set default_binds) { - size_t i = 0; + unsigned i; for (i = 0; input_config_bind_map_get_valid(i); i++) { char key[64]; diff --git a/gfx/drivers_shader/slang_cache.cpp b/gfx/drivers_shader/slang_cache.cpp index 79aa5c257f4..f6112f0ee32 100644 --- a/gfx/drivers_shader/slang_cache.cpp +++ b/gfx/drivers_shader/slang_cache.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -83,12 +84,15 @@ static bool spirv_cache_get_filename(const char *hash, */ static bool spirv_cache_write_string(RFILE *file, const std::string &str) { - uint32_t len = str.length(); + uint32_t _len; + if (str.length() > UINT32_MAX) + return false; + _len = (uint32_t)str.length(); - if (filestream_write(file, &len, sizeof(uint32_t)) != sizeof(uint32_t)) + if (filestream_write(file, &_len, sizeof(uint32_t)) != sizeof(uint32_t)) return false; - if (len > 0 && filestream_write(file, str.c_str(), len) != len) + if (_len > 0 && filestream_write(file, str.c_str(), _len) != _len) return false; return true; @@ -103,29 +107,30 @@ static bool spirv_cache_write_string(RFILE *file, const std::string &str) */ static bool spirv_cache_read_string(RFILE *file, std::string &str_out) { - uint32_t len; + uint32_t _len; + char *buf; - if (filestream_read(file, &len, sizeof(uint32_t)) != sizeof(uint32_t)) + if (filestream_read(file, &_len, sizeof(uint32_t)) != sizeof(uint32_t)) return false; - if (len == 0) + if (_len == 0) { str_out.clear(); return true; } /* Allocate and read string */ - char *buf = new char[len + 1]; + buf = new char[_len + 1]; if (!buf) return false; - if (filestream_read(file, buf, len) != len) + if (filestream_read(file, buf, _len) != _len) { delete[] buf; return false; } - buf[len] = '\0'; + buf[_len] = '\0'; str_out = buf; delete[] buf; @@ -134,18 +139,18 @@ static bool spirv_cache_read_string(RFILE *file, std::string &str_out) extern "C" { -bool spirv_cache_compute_hash(const char *vertex_source, const char *fragment_source, - char *hash_out) +bool spirv_cache_compute_hash(const char *vertex_source, const char *fragment_source, char *hash_out) { + uint8_t *combined; + size_t vertex_len, fragment_len, total_len; if (!vertex_source || !fragment_source || !hash_out) return false; /* Build combined hash input: vertex + "|" + fragment */ - size_t vertex_len = strlen(vertex_source); - size_t fragment_len = strlen(fragment_source); - size_t total_len = vertex_len + 1 + fragment_len; /* 1 for "|" separator */ - - uint8_t *combined = new uint8_t[total_len]; + vertex_len = strlen(vertex_source); + fragment_len = strlen(fragment_source); + total_len = vertex_len + 1 + fragment_len; /* 1 for "|" separator */ + combined = new uint8_t[total_len]; if (!combined) return false; @@ -163,8 +168,8 @@ bool spirv_cache_compute_hash(const char *vertex_source, const char *fragment_so bool spirv_cache_load(const char *hash, struct glslang_output *output) { RFILE *file; - char cache_file[PATH_MAX_LENGTH]; uint8_t version; + char cache_file[PATH_MAX_LENGTH]; uint32_t vertex_size, fragment_size, param_count, i; uint16_t rt_format; @@ -259,10 +264,10 @@ bool spirv_cache_load(const char *hash, struct glslang_output *output) bool spirv_cache_save(const char *hash, const struct glslang_output *output) { RFILE *file; + uint16_t rt_format; char cache_file[PATH_MAX_LENGTH]; uint8_t version = SPIRV_CACHE_VERSION; uint32_t vertex_size, fragment_size, param_count, i; - uint16_t rt_format; if (!hash || !output) return false; @@ -284,7 +289,9 @@ bool spirv_cache_save(const char *hash, const struct glslang_output *output) goto error; /* Write vertex SPIR-V */ - vertex_size = output->vertex.size(); + if (output->vertex.size() > UINT32_MAX) + goto error; + vertex_size = (uint32_t)output->vertex.size(); if (filestream_write(file, &vertex_size, sizeof(uint32_t)) != sizeof(uint32_t)) goto error; if (vertex_size > 0) @@ -294,7 +301,9 @@ bool spirv_cache_save(const char *hash, const struct glslang_output *output) } /* Write fragment SPIR-V */ - fragment_size = output->fragment.size(); + if (output->fragment.size() > UINT32_MAX) + goto error; + fragment_size = (uint32_t)output->fragment.size(); if (filestream_write(file, &fragment_size, sizeof(uint32_t)) != sizeof(uint32_t)) goto error; if (fragment_size > 0) @@ -304,7 +313,9 @@ bool spirv_cache_save(const char *hash, const struct glslang_output *output) } /* Write parameters */ - param_count = output->meta.parameters.size(); + if (output->meta.parameters.size() > UINT32_MAX) + goto error; + param_count = (uint32_t)output->meta.parameters.size(); if (filestream_write(file, ¶m_count, sizeof(uint32_t)) != sizeof(uint32_t)) goto error; diff --git a/libretro-common/vfs/vfs_implementation_smb.c b/libretro-common/vfs/vfs_implementation_smb.c index 65f4bc2a549..1dffffd79d7 100644 --- a/libretro-common/vfs/vfs_implementation_smb.c +++ b/libretro-common/vfs/vfs_implementation_smb.c @@ -36,7 +36,7 @@ static bool smb_initialized = false; static int max_context_configured = 0; static const struct smb_settings *smb_cfg = NULL; -static struct smb2_context *get_smb_context() +static struct smb2_context *get_smb_context(void) { int idx; @@ -81,7 +81,7 @@ bool smb_init_cfg(const struct smb_settings *new_cfg) } /* Initialize SMB context */ -static bool smb_init() +static bool smb_init(void) { char server[256]; char share[256]; @@ -220,7 +220,7 @@ void smb_close_context(int index) } /* Shutdown SMB context - called on exit */ -void smb_shutdown() +void smb_shutdown(void) { int i; diff --git a/libretro-common/vfs/vfs_implementation_smb.h b/libretro-common/vfs/vfs_implementation_smb.h index aeefc333ebe..15c2f2cbff6 100644 --- a/libretro-common/vfs/vfs_implementation_smb.h +++ b/libretro-common/vfs/vfs_implementation_smb.h @@ -66,7 +66,7 @@ int retro_vfs_stat_smb(const char *path, int64_t *size); int retro_vfs_file_error_smb(libretro_vfs_implementation_file *stream); /* Context management */ -void smb_shutdown(); +void smb_shutdown(void); #ifdef __cplusplus } diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 8cb133f3019..3621b497ead 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -10784,7 +10784,6 @@ static int materialui_pointer_down(void *userdata, int drag_margin_vert; gfx_display_t *p_disp = disp_get_ptr(); unsigned header_height = p_disp->header_height; - unsigned width = mui->last_width; unsigned height = mui->last_height; /* Check whether pointer down event is within diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c index 1e9c9ae1ae0..4bbf0f69291 100644 --- a/menu/drivers/ozone.c +++ b/menu/drivers/ozone.c @@ -7458,7 +7458,7 @@ static void ozone_draw_messagebox( unsigned slice_new_w = longest_width + (slice_margin * 2); unsigned slice_new_h = line_height * (line_count + 2) + (slice_margin / 2); unsigned slice_w = 256; - int slice_x = x - (longest_width / 2) - slice_margin; + int slice_x = (int)(x - (longest_width / 2) - slice_margin); int slice_y = y - line_height - (slice_margin / 4) + ((slice_new_h >= slice_w) ? (16.0f * scale_factor) diff --git a/menu/drivers/rgui.c b/menu/drivers/rgui.c index 9e784c271f1..d478109b6da 100644 --- a/menu/drivers/rgui.c +++ b/menu/drivers/rgui.c @@ -2972,7 +2972,7 @@ static void rgui_render_mini_thumbnail( else fb_y_offset = (rgui->term_layout.start_y + term_height) - thumbnail->max_height; - text_x = (thumbnail->max_width / 2) + fb_x_offset - (strlen(msg) * (rgui->font_width_stride / 2)); + text_x = (thumbnail->max_width / 2) + fb_x_offset - ((int)strlen(msg) * (rgui->font_width_stride / 2)); text_y = (thumbnail->max_height / 2) + fb_y_offset - (rgui->font_height_stride / 3); /* Draw background */ @@ -5543,9 +5543,9 @@ static void rgui_render(void *data, unsigned width, unsigned height, rgui_blit_line(rgui, fb_width, - term_end_x + (int)(term_end_x - (powerstate_len * rgui->font_width_stride) - - (timedate_len * rgui->font_width_stride), + - (timedate_len * rgui->font_width_stride)), title_y, timedate, rgui->colors.hover_color, diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index c39491a593a..aa007eb73f9 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2549,7 +2549,7 @@ static void xmb_set_title(xmb_handle_t *xmb) if (xmb->categories_selection_ptr > xmb->system_tab_end) { xmb_node_t *sidebar_node = NULL; - int i = xmb->categories_selection_ptr - xmb->system_tab_end - 1; + size_t i = xmb->categories_selection_ptr - xmb->system_tab_end - 1; /* Explore views */ if (string_ends_with_size(xmb->horizontal_list.list[i].label, ".lvw", diff --git a/retroarch.c b/retroarch.c index 3469afb7376..febf11167ab 100644 --- a/retroarch.c +++ b/retroarch.c @@ -1261,7 +1261,7 @@ static size_t find_driver_nonempty( int driver_find_index(const char *label, const char *drv) { - size_t i; + int i; char str[NAME_MAX_LENGTH]; str[0] = '\0'; @@ -1288,7 +1288,7 @@ int driver_find_index(const char *label, const char *drv) **/ static void driver_find_last(const char *label, char *s, size_t len) { - size_t i; + int i; for (i = 0; find_driver_nonempty(label, i, s, len) > 0; i++) { } if (i) @@ -1805,7 +1805,7 @@ void drivers_init( #endif #ifdef HAVE_MENU - srand(time(NULL)); + srand((unsigned)time(NULL)); #endif } @@ -5408,7 +5408,7 @@ bool command_event(enum event_command cmd, void *data) break; case CMD_EVENT_RUMBLE_STOP: { - size_t i; + unsigned i; for (i = 0; i < MAX_USERS; i++) { unsigned joy_idx = settings->uints.input_joypad_index[i]; diff --git a/runahead.c b/runahead.c index 3dbb6557914..68b87dfba05 100644 --- a/runahead.c +++ b/runahead.c @@ -1612,7 +1612,7 @@ static INLINE void preempt_input_poll(preempt_t *preempt, for (p = 0; p < max_users; p++) { /* Check full digital joypad */ - int16_t joypad_state = (int16_t)(state_cb(p, RETRO_DEVICE_JOYPAD, + int16_t joypad_state = (int16_t)(state_cb((unsigned)p, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK)); if (joypad_state != preempt->joypad_state[p]) { diff --git a/runloop.c b/runloop.c index c8078db282f..3f83d36a6dc 100644 --- a/runloop.c +++ b/runloop.c @@ -2752,7 +2752,7 @@ bool runloop_environment_cb(unsigned cmd, void *data) memcpy(sys_info->subsystem.data, info, i * sizeof(*sys_info->subsystem.data)); - sys_info->subsystem.size = i; + sys_info->subsystem.size = (unsigned)i; runloop_st->current_core.flags |= RETRO_CORE_FLAG_HAS_SET_SUBSYSTEMS; } @@ -2797,7 +2797,7 @@ bool runloop_environment_cb(unsigned cmd, void *data) sys_info->ports.data = info_ptr; memcpy(sys_info->ports.data, info, i * sizeof(*sys_info->ports.data)); - sys_info->ports.size = i; + sys_info->ports.size = (unsigned)i; } break; } diff --git a/tasks/task_content.c b/tasks/task_content.c index 66adf78c4c9..7f7130bf733 100644 --- a/tasks/task_content.c +++ b/tasks/task_content.c @@ -2614,7 +2614,7 @@ void content_set_subsystem(unsigned idx) /* Sets the subsystem by name */ bool content_set_subsystem_by_name(const char* subsystem_name) { - size_t i; + unsigned i; runloop_state_t *runloop_st = runloop_state_get_ptr(); rarch_system_info_t *sys_info = &runloop_st->system; /* Core not loaded completely, use the data we peeked on load core */ diff --git a/tasks/task_movie.c b/tasks/task_movie.c index 6e6861459ef..cad3ff00505 100644 --- a/tasks/task_movie.c +++ b/tasks/task_movie.c @@ -415,7 +415,9 @@ bool movie_stop_record(input_driver_state_t *input_st) uint32s_index_print_count_data(movie->blocks); #endif #endif - frame_count = swap_if_big32(movie->frame_counter); + if (movie->frame_counter > UINT32_MAX) + RARCH_ERR("[Replay] Frame counter too big to fit in 32 bits\n"); + frame_count = swap_if_big32((uint32_t)movie->frame_counter); intfstream_seek(movie->file, REPLAY_HEADER_FRAME_COUNT_INDEX*sizeof(uint32_t), SEEK_SET); intfstream_write(movie->file, &frame_count, sizeof(uint32_t)); bsv_movie_deinit_full(input_st); From 8ec8c328035e48012348432bb6bd97d66d020d07 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 09:29:51 +0200 Subject: [PATCH 11/22] Fix warnings for Android --- deps/mbedtls/net_sockets.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deps/mbedtls/net_sockets.c b/deps/mbedtls/net_sockets.c index a05b0c26dfd..2c9964e4288 100644 --- a/deps/mbedtls/net_sockets.c +++ b/deps/mbedtls/net_sockets.c @@ -306,7 +306,8 @@ int mbedtls_net_accept( mbedtls_net_context *bind_ctx, struct sockaddr_storage client_addr; #if defined(__socklen_t_defined) || defined(_SOCKLEN_T) || \ - defined(_SOCKLEN_T_DECLARED) || defined(__DEFINED_socklen_t) + defined(_SOCKLEN_T_DECLARED) || defined(__DEFINED_socklen_t) || \ + defined(socklen_t) || (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L) socklen_t n = (socklen_t) sizeof( client_addr ); socklen_t type_len = (socklen_t) sizeof( type ); #else From f3f48bd991ebfa66e87c7a9354156531cab69bf9 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 09:33:38 +0200 Subject: [PATCH 12/22] Silence warnings for stb_vorbis --- deps/stb/stb_vorbis.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deps/stb/stb_vorbis.h b/deps/stb/stb_vorbis.h index f53f1dd47b0..bce979efbb3 100644 --- a/deps/stb/stb_vorbis.h +++ b/deps/stb/stb_vorbis.h @@ -3702,12 +3702,12 @@ void stb_vorbis_seek_start(stb_vorbis *f) unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) { - unsigned int restore_offset, previous_safe; - unsigned int end, last_page_loc; + uint32_t restore_offset, previous_safe; + uint32_t end, last_page_loc; if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); if (!f->total_samples) { - unsigned int last; + uint32_t last; uint32_t lo,hi; char header[6]; From 75b55c2334e4b69608b4eb30961c411d44337664 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 09:36:42 +0200 Subject: [PATCH 13/22] Silence glslang warnings --- deps/glslang/glslang/glslang/MachineIndependent/SymbolTable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/glslang/glslang/glslang/MachineIndependent/SymbolTable.cpp b/deps/glslang/glslang/glslang/MachineIndependent/SymbolTable.cpp index db46e1075d7..23e2bd7d170 100644 --- a/deps/glslang/glslang/glslang/MachineIndependent/SymbolTable.cpp +++ b/deps/glslang/glslang/glslang/MachineIndependent/SymbolTable.cpp @@ -308,7 +308,7 @@ TVariable* TVariable::clone() const TFunction::TFunction(const TFunction& copyOf) : TSymbol(copyOf) { for (unsigned int i = 0; i < copyOf.parameters.size(); ++i) { - TParameter param; + TParameter param = { nullptr, nullptr, nullptr }; parameters.push_back(param); parameters.back().copyParam(copyOf.parameters[i]); } From f25536c9700597444cef32435d5dec3901b259c8 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 09:38:46 +0200 Subject: [PATCH 14/22] Fix spirv-cross warning fix --- deps/SPIRV-Cross/spirv_cross_parsed_ir.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deps/SPIRV-Cross/spirv_cross_parsed_ir.cpp b/deps/SPIRV-Cross/spirv_cross_parsed_ir.cpp index 8d1acf69f97..32059098a75 100644 --- a/deps/SPIRV-Cross/spirv_cross_parsed_ir.cpp +++ b/deps/SPIRV-Cross/spirv_cross_parsed_ir.cpp @@ -1005,8 +1005,9 @@ ParsedIR::LoopLock::LoopLock(uint32_t *lock_) } ParsedIR::LoopLock::LoopLock(LoopLock &&other) SPIRV_CROSS_NOEXCEPT + : lock(other.lock) { - *this = std::move(other); + other.lock = nullptr; } ParsedIR::LoopLock &ParsedIR::LoopLock::operator=(LoopLock &&other) SPIRV_CROSS_NOEXCEPT From c380ff57437a5bf315b45f713539e447c4d60654 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 09:45:38 +0200 Subject: [PATCH 15/22] Silence sdl2_gfx warning --- gfx/drivers/sdl2_gfx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gfx/drivers/sdl2_gfx.c b/gfx/drivers/sdl2_gfx.c index 52f89c89d7a..89521c8c3ed 100644 --- a/gfx/drivers/sdl2_gfx.c +++ b/gfx/drivers/sdl2_gfx.c @@ -984,7 +984,7 @@ static bool sdl2_gfx_set_shader(void *data, return false; } -#ifdef HAVE_GFX_WIDGETS +#if defined(HAVE_GFX_WIDGETS) && SDL_VERSION_ATLEAST(2, 0, 18) static bool sdl2_gfx_widgets_enabled(void *data) { (void)data; return true; } #endif From 2aeaa5fd25cceeb3454c2893fcd9a330f512754d Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 10:01:04 +0200 Subject: [PATCH 16/22] Silence more warnings --- audio/drivers/openal.c | 8 ++++---- cheevos/cheevos_rvz.c | 6 +++--- cores/libretro-ffmpeg/ffmpeg_core.c | 6 +++--- deps/rcheevos/src/rapi/rc_api_info.c | 2 +- deps/rcheevos/src/rapi/rc_api_user.c | 2 +- gfx/drivers/metal.m | 4 ++-- gfx/drivers/vulkan.c | 2 +- input/drivers_joypad/sdl_joypad.c | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/audio/drivers/openal.c b/audio/drivers/openal.c index 69f8993c763..ede8c40efc9 100644 --- a/audio/drivers/openal.c +++ b/audio/drivers/openal.c @@ -134,7 +134,7 @@ static void *al_init(const char *device, unsigned rate, unsigned latency, if (string_is_equal(device, list->elems[i].data)) { RARCH_DBG("[OpenAL] Found device #%d: \"%s\".\n", i, list->elems[i].data); - idx_found = i; + idx_found = (int32_t)i; dev_id = strdup(list->elems[i].data); break; } @@ -144,7 +144,7 @@ static void *al_init(const char *device, unsigned rate, unsigned latency, if (idx_found == -1 && isdigit(device[0])) { - idx_found = strtoul(device, NULL, 0); + idx_found = (int32_t)strtoul(device, NULL, 0); RARCH_LOG("[OpenAL] Fallback, device index is a single number index instead: %d.\n", idx_found); if (idx_found != -1) @@ -190,7 +190,7 @@ static void *al_init(const char *device, unsigned rate, unsigned latency, _latency = latency * rate * 2 * sizeof(int16_t); } - al->num_buffers = _latency / (1000 * OPENAL_BUFSIZE); + al->num_buffers = (ALsizei)(_latency / (1000 * OPENAL_BUFSIZE)); if (al->num_buffers < 2) al->num_buffers = 2; @@ -263,7 +263,7 @@ static ssize_t al_write(void *data, const void *s, size_t len) if (!al_get_buffer(al, &buffer)) break; - alBufferData(buffer, al->format, buf, rc, al->rate); + alBufferData(buffer, al->format, buf, (ALsizei)rc, al->rate); alSourceQueueBuffers(al->source, 1, &buffer); _len += rc; diff --git a/cheevos/cheevos_rvz.c b/cheevos/cheevos_rvz.c index 4da8a77a9f1..7b867a6b5d7 100644 --- a/cheevos/cheevos_rvz.c +++ b/cheevos/cheevos_rvz.c @@ -855,7 +855,7 @@ static uint32_t rvz_get_group_decompression_size(rcheevos_rvz_file_t* rvz, /* This is a partition data group */ /* Calculate blocks per group based on chunk_size * chunk_size is in ISO space (with headers), convert to decrypted space */ - uint32_t adjusted_chunk_size = ((uint64_t)chunk_size * RVZ_WII_SECTOR_DATA_SIZE) / RVZ_WII_SECTOR_SIZE; + uint32_t adjusted_chunk_size = (uint32_t)(((uint64_t)chunk_size * RVZ_WII_SECTOR_DATA_SIZE) / RVZ_WII_SECTOR_SIZE); uint32_t blocks_per_group = adjusted_chunk_size / RVZ_WII_SECTOR_DATA_SIZE; uint32_t group_offset_in_partition = group_index - pdata->group_index; uint32_t total_blocks = pdata->number_of_sectors; @@ -2262,7 +2262,7 @@ static int rvz_calculate_partition_offsets_stateless( offsets->file_offset = (offsets->block_index * RVZ_WII_SECTOR_DATA_SIZE) + offsets->offset_in_block; /* Calculate effective chunk size (decrypted group size) */ - effective_chunk_size = ((uint64_t)chunk_size * RVZ_WII_SECTOR_DATA_SIZE) / RVZ_WII_SECTOR_SIZE; + effective_chunk_size = (uint32_t)(((uint64_t)chunk_size * RVZ_WII_SECTOR_DATA_SIZE) / RVZ_WII_SECTOR_SIZE); /* Calculate group index */ file_block = offsets->file_offset / effective_chunk_size; @@ -2307,7 +2307,7 @@ static size_t rvz_read_partition_data_stateless( uint64_t bytes_left_in_block; /* Calculate effective chunk size (decrypted group size) */ - effective_chunk_size = ((uint64_t)chunk_size * RVZ_WII_SECTOR_DATA_SIZE) / RVZ_WII_SECTOR_SIZE; + effective_chunk_size = (uint32_t)(((uint64_t)chunk_size * RVZ_WII_SECTOR_DATA_SIZE) / RVZ_WII_SECTOR_SIZE); /* Get the decompressed chunk data */ chunk_data = rvz_get_chunk(handle, offsets->group_index, &chunk_data_size); diff --git a/cores/libretro-ffmpeg/ffmpeg_core.c b/cores/libretro-ffmpeg/ffmpeg_core.c index 25aa559143b..eb46f116355 100644 --- a/cores/libretro-ffmpeg/ffmpeg_core.c +++ b/cores/libretro-ffmpeg/ffmpeg_core.c @@ -1918,7 +1918,7 @@ static void check_variables(bool firststart) fft_ms_var.key = "ffmpeg_fft_multisample"; if (CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_GET_VARIABLE, &fft_ms_var) && fft_ms_var.value) - FFT_MULTISAMPLE_STR = strtoul(fft_ms_var.value, NULL, 0); + FFT_MULTISAMPLE_STR = (unsigned)strtoul(fft_ms_var.value, NULL, 0); #endif color_var.key = "ffmpeg_color_space"; @@ -2502,7 +2502,7 @@ void CORE_PREFIX(retro_run)(void) #if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES3) else if (FFT_STR) { - unsigned fft_frames = to_read_frames; + unsigned fft_frames = (unsigned)to_read_frames; const int16_t *buffer = audio_buffer; while (fft_frames) @@ -2518,7 +2518,7 @@ void CORE_PREFIX(retro_run)(void) buffer += to_read * 2; fft_frames -= to_read; } - hwfft_render(FFT_STR, HW_RENDER_STR.get_current_framebuffer(), FFT_WIDTH_STR, FFT_HEIGHT_STR); + hwfft_render(FFT_STR, (GLuint)HW_RENDER_STR.get_current_framebuffer(), FFT_WIDTH_STR, FFT_HEIGHT_STR); CORE_PREFIX(video_cb)(RETRO_HW_FRAME_BUFFER_VALID, FFT_WIDTH_STR, FFT_HEIGHT_STR, FFT_WIDTH_STR * sizeof(uint32_t)); } diff --git a/deps/rcheevos/src/rapi/rc_api_info.c b/deps/rcheevos/src/rapi/rc_api_info.c index 5ab9709287b..a1fa7e5d532 100644 --- a/deps/rcheevos/src/rapi/rc_api_info.c +++ b/deps/rcheevos/src/rapi/rc_api_info.c @@ -383,7 +383,7 @@ int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_resp entry = response->entries; while (rc_json_get_next_object_field(&iterator, &field)) { - entry->id = strtol(field.name, &end, 10); + entry->id = (uint32_t)strtol(field.name, &end, 10); field.name = ""; if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, "")) diff --git a/deps/rcheevos/src/rapi/rc_api_user.c b/deps/rcheevos/src/rapi/rc_api_user.c index 323756cfd15..6b61611773f 100644 --- a/deps/rcheevos/src/rapi/rc_api_user.c +++ b/deps/rcheevos/src/rapi/rc_api_user.c @@ -457,7 +457,7 @@ int rc_api_process_fetch_all_user_progress_server_response(rc_api_fetch_all_user entry = response->entries; while (rc_json_get_next_object_field(&iterator, &field)) { - entry->game_id = strtol(field.name, &end, 10); + entry->game_id = (uint32_t)strtol(field.name, &end, 10); field.name = ""; if (!rc_json_get_required_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), diff --git a/gfx/drivers/metal.m b/gfx/drivers/metal.m index e3e18b0e481..54ad36be5ea 100644 --- a/gfx/drivers/metal.m +++ b/gfx/drivers/metal.m @@ -1363,7 +1363,7 @@ - (bool)_initHDRPipelines ca.sourceAlphaBlendFactor = MTLBlendFactorSourceAlpha; ca.destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; } - id s = [_device newRenderPipelineStateWithDescriptor:psd error:&e]; + id s = [self->_device newRenderPipelineStateWithDescriptor:psd error:&e]; if (e) { RARCH_ERR("[Metal] HDR %s pipeline: %s.\n", @@ -4952,7 +4952,7 @@ - (BOOL)setShaderFromPath:(NSString *)path @try { - size_t i; + unsigned i; texture_t *source = NULL; if (!video_shader_load_preset_into_shader(path.UTF8String, shader)) return NO; diff --git a/gfx/drivers/vulkan.c b/gfx/drivers/vulkan.c index 03ecc6eea16..70bd211d6d4 100644 --- a/gfx/drivers/vulkan.c +++ b/gfx/drivers/vulkan.c @@ -6234,7 +6234,7 @@ static bool vulkan_frame(void *data, const void *frame, uint64_t frame_count, unsigned pitch, const char *msg, video_frame_info_t *video_info) { - int i, j, k; + int j, k; VkSubmitInfo submit_info; VkClearValue clear_color; VkRenderPassBeginInfo rp_info; diff --git a/input/drivers_joypad/sdl_joypad.c b/input/drivers_joypad/sdl_joypad.c index 9a5155d9742..6151a808c33 100644 --- a/input/drivers_joypad/sdl_joypad.c +++ b/input/drivers_joypad/sdl_joypad.c @@ -274,7 +274,7 @@ static void sdl_joypad_destroy(void) static void *sdl_joypad_init(void *data) { - size_t i; + unsigned i; unsigned num_sticks; #ifdef HAVE_SDL2 uint32_t subsystem = SDL_INIT_GAMECONTROLLER; From 6a3bcdc0837672d443ade57ec3176f956d4fea55 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 10:11:01 +0200 Subject: [PATCH 17/22] Silence warnings --- libretro-common/formats/libchdr/libchdr_chd.c | 2 +- libretro-common/include/libchdr/chd.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libretro-common/formats/libchdr/libchdr_chd.c b/libretro-common/formats/libchdr/libchdr_chd.c index dde9771aa8c..c1eb0bd91f6 100644 --- a/libretro-common/formats/libchdr/libchdr_chd.c +++ b/libretro-common/formats/libchdr/libchdr_chd.c @@ -1632,7 +1632,7 @@ static uint32_t header_guess_unitbytes(chd_file *chd) { /* look for hard disk metadata; if found, then the unit size == sector size */ char metadata[512]; - int i0, i1, i2, i3; + unsigned int i0, i1, i2, i3; if (chd_get_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL) == CHDERR_NONE && sscanf(metadata, HARD_DISK_METADATA_FORMAT, &i0, &i1, &i2, &i3) == 4) return i3; diff --git a/libretro-common/include/libchdr/chd.h b/libretro-common/include/libchdr/chd.h index c8fcc13a35a..e47036efede 100644 --- a/libretro-common/include/libchdr/chd.h +++ b/libretro-common/include/libchdr/chd.h @@ -227,7 +227,7 @@ extern "C" { /* standard hard disk metadata */ #define HARD_DISK_METADATA_TAG CHD_MAKE_TAG('G','D','D','D') -#define HARD_DISK_METADATA_FORMAT "CYLS:%d,HEADS:%d,SECS:%d,BPS:%d" +#define HARD_DISK_METADATA_FORMAT "CYLS:%u,HEADS:%u,SECS:%u,BPS:%u" /* hard disk identify information */ #define HARD_DISK_IDENT_METADATA_TAG CHD_MAKE_TAG('I','D','N','T') From bb5f72dce108466c3cb3e51af0c9aca833ce70d2 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 10:13:01 +0200 Subject: [PATCH 18/22] Silence warnings --- gfx/display_servers/dispserv_apple.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gfx/display_servers/dispserv_apple.m b/gfx/display_servers/dispserv_apple.m index 8a6511c8498..8d0f239708d 100644 --- a/gfx/display_servers/dispserv_apple.m +++ b/gfx/display_servers/dispserv_apple.m @@ -556,9 +556,9 @@ static enum rotation apple_display_server_get_screen_orientation(void *data) && settings->floats.video_refresh_rate >= 10.0f && settings->floats.video_refresh_rate <= 250.0f) { - float hz = settings->floats.video_refresh_rate; - CocoaView *view = [CocoaView get]; #if defined(IOS) + float hz = settings->floats.video_refresh_rate; + CocoaView *view = [CocoaView get]; if (view && view.displayLink) { RARCH_DBG("[Video] Setting initial refresh rate to %.3f Hz\n", hz); @@ -571,6 +571,8 @@ static enum rotation apple_display_server_get_screen_orientation(void *data) view.displayLink.preferredFramesPerSecond = hz; } #elif defined(OSX) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 140000 + float hz = settings->floats.video_refresh_rate; + CocoaView *view = [CocoaView get]; if (view) { if (@available(macOS 14, *)) From 323e8b5c5ff45e9f7a4a802c256e2141bf3c975e Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 10:24:02 +0200 Subject: [PATCH 19/22] Fix metal warnings --- gfx/drivers/metal.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gfx/drivers/metal.m b/gfx/drivers/metal.m index 54ad36be5ea..24f795960dc 100644 --- a/gfx/drivers/metal.m +++ b/gfx/drivers/metal.m @@ -437,7 +437,7 @@ - (void)setRotation:(unsigned)rotation; { \ NSObject * __y = y; \ if (x != nil) { \ - NSObject * __foo = (__bridge_transfer NSObject *)(__bridge void *)(x); \ + __attribute__((unused)) NSObject * __foo = (__bridge_transfer NSObject *)(__bridge void *)(x); \ __foo = nil; \ x = (__bridge __typeof__(x))nil; \ } \ @@ -5624,7 +5624,7 @@ static bool metal_set_shader(void *data, static void metal_free(void *data) { - MetalDriver *md = (__bridge_transfer MetalDriver *)data; + __attribute__((unused)) MetalDriver *md = (__bridge_transfer MetalDriver *)data; metal_ctx_data = NULL; md = nil; } @@ -5742,7 +5742,7 @@ static void metal_unload_texture(void *data, * buffer is still using it -- the Metal runtime refcounts * resources across CPU and GPU. No cross-thread * serialisation needed for unload. */ - Texture *t = (__bridge_transfer Texture *)(void *)handle; + __attribute__((unused)) Texture *t = (__bridge_transfer Texture *)(void *)handle; t = nil; } From 98f5b4a0a22aa8eed8575624049c07e39ea020a8 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 10:26:41 +0200 Subject: [PATCH 20/22] (D3D12) Remove unused variable --- gfx/drivers/d3d12.c | 1 - 1 file changed, 1 deletion(-) diff --git a/gfx/drivers/d3d12.c b/gfx/drivers/d3d12.c index b2c2d348446..2257535a2ea 100644 --- a/gfx/drivers/d3d12.c +++ b/gfx/drivers/d3d12.c @@ -6117,7 +6117,6 @@ static bool d3d12_gpu_hdr_readback_to_bgr24( d3d12_texture_t sdr_rt = { 0 }; D3D12Resource readback = NULL; D3D12Resource back_buffer; - D3D12_RESOURCE_DESC sdr_desc; D3D12_HEAP_PROPERTIES heap_props; D3D12_RESOURCE_DESC buf_desc; D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; From 0a7758076d9375bace1c75f70cca149ce2b4929a Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 11:11:28 +0200 Subject: [PATCH 21/22] Silence VS2019 warnings --- gfx/include/dxsdk/dxsdk_sal_compat.h | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/gfx/include/dxsdk/dxsdk_sal_compat.h b/gfx/include/dxsdk/dxsdk_sal_compat.h index d919a0cb6ef..72e01f80cd8 100644 --- a/gfx/include/dxsdk/dxsdk_sal_compat.h +++ b/gfx/include/dxsdk/dxsdk_sal_compat.h @@ -374,8 +374,24 @@ #endif /* --- SAL 2 misc ------------------------------------------------------ */ +/* _Inexpressible_(s) marks an annotation size expression that the + * static analyser cannot easily evaluate; on a real SAL implementation + * the SDK consumes `s` inside its annotation metadata. When the SDK's + * is present and active (modern MSVC), _Inexpressible_ is + * defined elsewhere only under _PREFAST_ -- so for normal compilation + * the SDK leaves it undefined and falls back to whatever the + * environment provides. If we stub it to empty, the argument is + * discarded, and any enclosing function-like macro receives an empty + * argument list -- triggering C4003 on MSVC for sites like + * _In_reads_opt_(_Inexpressible_(p->q != 0)) + * because the SDK's _In_reads_opt_(size) ultimately invokes + * _Pre_opt_count_(size), which does not accept zero arguments. + * + * Defining the stub as `(s)` (the parenthesised expression) keeps the + * token stream non-empty for any enclosing macro and parses as a valid + * size expression. */ #ifndef _Inexpressible_ -#define _Inexpressible_(s) +#define _Inexpressible_(s) (s) #endif #ifndef _Use_decl_annotations_ #define _Use_decl_annotations_ From cd386a39333ea689aba463fccb7171f1784451cd Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-SPFP6AQ\\twistedtechre" Date: Fri, 1 May 2026 11:35:46 +0200 Subject: [PATCH 22/22] [GameMode] Don't probe libgamemode on every config load, preserve user setting Two related fixes for the GameMode startup behavior on Linux: 1. configuration.c: Only call frontend_driver_set_gamemode() during config load when gamemode_enable is actually true. Previously the probe ran unconditionally with the current setting value, meaning every user on every config load triggered a dlopen of libgamemode.so even when the feature was off - producing log spam ("[GameMode] GameMode cannot be enabled on this system...") for the majority of users who don't have libgamemode installed. Also stop force-disabling gamemode_enable in the config when the probe fails. Silently flipping the user's preference to false and persisting it to retroarch.cfg meant a transient failure (sandbox path issue, library not yet installed, multi-arch mismatch, etc.) would require the user to manually re-enable the option on every subsequent launch. The user's intent should survive across sessions; if libgamemode shows up later, the next launch picks it up automatically. 2. frontend/drivers/platform_unix.c: Latch the unavailable state inside frontend_unix_set_gamemode() once gamemode_query_status() reports failure. The result will not change for the lifetime of the process, so subsequent calls (deinit cleanup, menu toggles) short-circuit without re-entering the Feral client code and without re-emitting the warning. The warning now fires exactly once per process when the user has gamemode enabled but libgamemode is missing. The redundant "[Config] GameMode unsupported - disabling..." follow-up warning is removed - it added no information beyond what the GameMode warning already conveys. --- configuration.c | 18 ++++++++++-------- frontend/drivers/platform_unix.c | 17 +++++++++++++++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/configuration.c b/configuration.c index f8ff8127c29..ac032214e89 100644 --- a/configuration.c +++ b/configuration.c @@ -4653,14 +4653,16 @@ static bool config_load_file(global_t *global, if (!(bool)RHMAP_HAS_STR(conf->entries_map, "user_language")) msg_hash_set_uint(MSG_HASH_USER_LANGUAGE, frontend_driver_get_user_language()); - if (frontend_driver_has_gamemode() && - !frontend_driver_set_gamemode(settings->bools.gamemode_enable) && - settings->bools.gamemode_enable) - { - RARCH_WARN("[Config] GameMode unsupported - disabling...\n"); - configuration_set_bool(settings, - settings->bools.gamemode_enable, false); - } + /* If GameMode is enabled in the config but libgamemode is not + * available, warn once. Do NOT clear the setting: that would + * silently overwrite the user's preference in retroarch.cfg, so + * a transient failure (sandbox path issue, library not yet + * installed, etc.) would force them to re-enable it manually on + * every subsequent launch. The probe is latched inside the + * frontend driver, so it will not be retried this session. */ + if ( settings->bools.gamemode_enable + && frontend_driver_has_gamemode()) + frontend_driver_set_gamemode(true); /* If this is the first run of an existing installation * after the independent favourites playlist size limit was diff --git a/frontend/drivers/platform_unix.c b/frontend/drivers/platform_unix.c index 0d51cecda5a..9c640b93150 100644 --- a/frontend/drivers/platform_unix.c +++ b/frontend/drivers/platform_unix.c @@ -2186,8 +2186,20 @@ static void android_app_destroy(struct android_app *android_app) static bool frontend_unix_set_gamemode(bool on) { #ifdef FERAL_GAMEMODE - int gamemode_status = gamemode_query_status(); - bool gamemode_active = (gamemode_status == 2); + /* Once gamemode_query_status() reports failure (typically because + * libgamemode.so is not installed), there is no point repeatedly + * re-probing on every config load or menu toggle - the result will + * not change for the lifetime of the process, and each probe emits + * a warning. Latch the unavailable state and short-circuit. */ + static bool gamemode_unavailable = false; + int gamemode_status; + bool gamemode_active; + + if (gamemode_unavailable) + return false; + + gamemode_status = gamemode_query_status(); + gamemode_active = (gamemode_status == 2); if (gamemode_status < 0) { @@ -2196,6 +2208,7 @@ static bool frontend_unix_set_gamemode(bool on) "https://github.com/FeralInteractive/gamemode needs to be installed.\n", gamemode_error_string()); + gamemode_unavailable = true; return false; }