mirror of
https://github.com/mauiaaron/apple2.git
synced 2024-06-17 12:29:31 +00:00
464 lines
17 KiB
C
464 lines
17 KiB
C
/*
|
|
* Apple // emulator for *ix
|
|
*
|
|
* This software package is subject to the GNU General Public License
|
|
* version 3 or later (your choice) as published by the Free Software
|
|
* Foundation.
|
|
*
|
|
* Copyright 2018 Aaron Culliney
|
|
*
|
|
*/
|
|
|
|
// NOTE: routines here are copied from William (Sheldon) Simms's NTSC implementation in AppleWin
|
|
// TODO: implement these in a shader?
|
|
|
|
/*
|
|
AppleWin : An Apple //e emulator for Windows
|
|
|
|
Copyright (C) 2010-2011, William S Simms
|
|
Copyright (C) 2014-2016, Michael Pohoreski, Tom Charlesworth
|
|
|
|
AppleWin is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
AppleWin is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with AppleWin; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "video/ntsc.h"
|
|
|
|
#define NTSC_NUM_PHASES 4
|
|
#define NTSC_NUM_SEQUENCES 4096
|
|
|
|
typedef void (*fb_plotter_fn)(color_mode_t, uint16_t, PIXEL_TYPE *);
|
|
typedef PIXEL_TYPE (*fb_scanline_fn)(PIXEL_TYPE color0, PIXEL_TYPE color2);
|
|
|
|
static uint8_t half_scanlines = 1;
|
|
static fb_plotter_fn pixelPlotter[NUM_COLOROPTS] = { NULL };
|
|
static fb_scanline_fn getHalfColor[NUM_COLOROPTS][2] = { { NULL } };
|
|
|
|
static unsigned int ntsc_color_phase = 0;
|
|
unsigned int ntsc_signal_bits = 0;
|
|
|
|
static PIXEL_TYPE monoPixelsMonitor [NTSC_NUM_SEQUENCES];
|
|
static PIXEL_TYPE monoPixelsTV [NTSC_NUM_SEQUENCES];
|
|
|
|
static PIXEL_TYPE colorPixelsMonitor[NTSC_NUM_PHASES][NTSC_NUM_SEQUENCES];
|
|
static PIXEL_TYPE colorPixelsTV [NTSC_NUM_PHASES][NTSC_NUM_SEQUENCES];
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// TV pixel updates
|
|
|
|
static PIXEL_TYPE halfScanlineTV(PIXEL_TYPE color0, PIXEL_TYPE color2) {
|
|
#if APPLE2IX
|
|
(void)color2;
|
|
PIXEL_TYPE color1 = ((color0 & 0xFEFEFEFE) >> 1) | (0xFF << SHIFT_A);
|
|
return color1;
|
|
#else
|
|
// HACK NOTE : original code for sampling color2 from 2 scanlines below requires at least 2.X full CYCLES_FRAME iterations
|
|
// - first pass does not pick up potentially changed color2
|
|
// - second pass will pick up correct color2
|
|
// - this leads to "interesting" artifacts for moving sprites
|
|
#error FIXME TODO ...
|
|
PIXEL_TYPE color1;
|
|
|
|
int r = (color0 >> SHIFT_R) & 0xFF;
|
|
int g = (color0 >> SHIFT_G) & 0xFF;
|
|
int b = (color0 >> SHIFT_B) & 0xFF;
|
|
uint32_t color2_prime = (color2 & 0xFCFCFCFC) >> 2;
|
|
r -= (color2_prime >> SHIFT_R) & 0xFF; if (r<0) { r=0; } // clamp to 0 on underflow
|
|
g -= (color2_prime >> SHIFT_G) & 0xFF; if (g<0) { g=0; } // clamp to 0 on underflow
|
|
b -= (color2_prime >> SHIFT_B) & 0xFF; if (b<0) { b=0; } // clamp to 0 on underflow
|
|
color1 = (r<<SHIFT_R) | (g<<SHIFT_G) | (b<<SHIFT_B) | (0xFF<<SHIFT_A);
|
|
|
|
return color1;
|
|
#endif
|
|
}
|
|
|
|
static PIXEL_TYPE doubleScanlineTV(PIXEL_TYPE color0, PIXEL_TYPE color2) {
|
|
#if APPLE2IX
|
|
(void)color2;
|
|
return color0;
|
|
#else
|
|
#error FIXME TODO ...
|
|
PIXEL_TYPE color1 = ((color0 & 0xFEFEFEFE) >> 1) + ((color2 & 0xFEFEFEFE) >> 1); // 50% Blend
|
|
return color1 | (0xFF<<SHIFT_A);
|
|
#endif
|
|
}
|
|
|
|
static void updateFBCommon(color_mode_t mode, uint16_t signal, PIXEL_TYPE *fb_ptr, PIXEL_TYPE *color_table) {
|
|
PIXEL_TYPE *fb_ptr0 = fb_ptr;
|
|
PIXEL_TYPE *fb_ptr1 = fb_ptr + (SCANWIDTH<<0);
|
|
PIXEL_TYPE *fb_ptr2 = fb_ptr + (SCANWIDTH<<1);
|
|
|
|
PIXEL_TYPE color0;
|
|
{
|
|
ntsc_signal_bits = ((ntsc_signal_bits << 1) | signal) & 0xFFF; // 12-bit ?
|
|
color0 = color_table[ntsc_signal_bits];
|
|
}
|
|
PIXEL_TYPE color2 = *fb_ptr2;
|
|
PIXEL_TYPE color1 = getHalfColor[mode][half_scanlines](color0, color2);
|
|
|
|
*fb_ptr0 = color0;
|
|
*fb_ptr1 = color1;
|
|
|
|
ntsc_color_phase = (ntsc_color_phase + 1) & 0x03;
|
|
}
|
|
|
|
static void updateFBMonoTV(color_mode_t mode, uint16_t signal, PIXEL_TYPE *fb_ptr) {
|
|
updateFBCommon(mode, signal, fb_ptr, monoPixelsTV);
|
|
}
|
|
|
|
static void updateFBColorTV(color_mode_t mode, uint16_t signal, PIXEL_TYPE *fb_ptr) {
|
|
updateFBCommon(mode, signal, fb_ptr, colorPixelsTV[ntsc_color_phase]);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Monitor pixel updates
|
|
|
|
static PIXEL_TYPE halfScanlineMonitor(PIXEL_TYPE color0, PIXEL_TYPE color2) {
|
|
(void)color2;
|
|
PIXEL_TYPE color1 = ((color0 & 0xFEFEFEFE) >> 1) | (0xFF << SHIFT_A);
|
|
return color1;
|
|
}
|
|
|
|
static PIXEL_TYPE doubleScanlineMonitor(PIXEL_TYPE color0, PIXEL_TYPE color2) {
|
|
(void)color2;
|
|
return color0;
|
|
}
|
|
|
|
static void updateFBMonoMonitor(color_mode_t mode, uint16_t signal, PIXEL_TYPE *fb_ptr) {
|
|
updateFBCommon(mode, signal, fb_ptr, monoPixelsMonitor);
|
|
}
|
|
|
|
static void updateFBColorMonitor(color_mode_t mode, uint16_t signal, PIXEL_TYPE *fb_ptr) {
|
|
updateFBCommon(mode, signal, fb_ptr, colorPixelsMonitor[ntsc_color_phase]);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void ntsc_plotBits(color_mode_t mode, uint16_t bits14, PIXEL_TYPE *fb_ptr) {
|
|
assert(!(mode == COLOR_MODE_COLOR || mode == COLOR_MODE_INTERP));
|
|
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); bits14 >>= 1; ++fb_ptr;
|
|
pixelPlotter[mode](mode, bits14 & 1, fb_ptr); ++fb_ptr;
|
|
}
|
|
|
|
void ntsc_flushScanline(color_mode_t mode, PIXEL_TYPE *fb_ptr) {
|
|
|
|
pixelPlotter[mode](mode, 0, fb_ptr); ++fb_ptr;
|
|
pixelPlotter[mode](mode, 0, fb_ptr); ++fb_ptr;
|
|
pixelPlotter[mode](mode, 0, fb_ptr); ++fb_ptr;
|
|
pixelPlotter[mode](mode, 0, fb_ptr); ++fb_ptr;
|
|
|
|
ntsc_color_phase = 0;
|
|
ntsc_signal_bits = 0;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
// pixel color creation
|
|
|
|
#define CHROMA_ZEROS 2
|
|
#define CHROMA_POLES 2
|
|
#define CHROMA_GAIN 7.438011255f // Should this be 7.15909 MHz ?
|
|
#define CHROMA_0 -0.7318893645f
|
|
#define CHROMA_1 1.2336442711f
|
|
|
|
#define LUMA_ZEROS 2
|
|
#define LUMA_POLES 2
|
|
#define LUMA_GAIN 13.71331570f // Should this be 14.318180 MHz ?
|
|
#define LUMA_0 -0.3961075449f
|
|
#define LUMA_1 1.1044202472f
|
|
|
|
#define SIGNAL_ZEROS 2
|
|
#define SIGNAL_POLES 2
|
|
#define SIGNAL_GAIN 7.614490548f // Should this be 7.15909 MHz ?
|
|
#define SIGNAL_0 -0.2718798058f
|
|
#define SIGNAL_1 0.7465656072f
|
|
|
|
#if !defined(M_PI)
|
|
# error M_PI not defined on this platform?
|
|
# define M_PI 3.1415926535898f
|
|
#endif
|
|
|
|
#define DEG_TO_RAD(x) (M_PI*(x)/180.f)
|
|
#define RAD_45 (M_PI*0.25f)
|
|
#define RAD_90 (M_PI*0.5f)
|
|
#define RAD_360 (M_PI*2.f)
|
|
|
|
#define CYCLESTART (DEG_TO_RAD(45))
|
|
|
|
#define clampZeroOne(x) ({ \
|
|
typeof(x) _rc = x; \
|
|
if (_rc < 0.f) { _rc = 0.f; } \
|
|
if (_rc > 1.f) { _rc = 1.f; } \
|
|
_rc; \
|
|
})
|
|
|
|
// What filter is this ??
|
|
// Filter Order: 2 -> poles for low pass
|
|
static double initFilterChroma(double z) {
|
|
static double x[CHROMA_ZEROS + 1] = {0,0,0};
|
|
static double y[CHROMA_POLES + 1] = {0,0,0};
|
|
|
|
x[0] = x[1]; x[1] = x[2]; x[2] = z / CHROMA_GAIN;
|
|
y[0] = y[1]; y[1] = y[2]; y[2] = -x[0] + x[2] + (CHROMA_0*y[0]) + (CHROMA_1*y[1]); // inverted x[0]
|
|
|
|
return y[2];
|
|
}
|
|
|
|
// Butterworth Lowpass digital filter
|
|
// Filter Order: 2 -> poles for low pass
|
|
static double initFilterLuma0(double z) {
|
|
static double x[LUMA_ZEROS + 1] = { 0,0,0 };
|
|
static double y[LUMA_POLES + 1] = { 0,0,0 };
|
|
|
|
x[0] = x[1]; x[1] = x[2]; x[2] = z / LUMA_GAIN;
|
|
y[0] = y[1]; y[1] = y[2]; y[2] = x[0] + x[2] + (2.f*x[1]) + (LUMA_0*y[0]) + (LUMA_1*y[1]);
|
|
|
|
return y[2];
|
|
}
|
|
|
|
// Butterworth Lowpass digital filter
|
|
// Filter Order: 2 -> poles for low pass
|
|
static double initFilterLuma1(double z) {
|
|
static double x[LUMA_ZEROS + 1] = { 0,0,0};
|
|
static double y[LUMA_POLES + 1] = { 0,0,0};
|
|
|
|
x[0] = x[1]; x[1] = x[2]; x[2] = z / LUMA_GAIN;
|
|
y[0] = y[1]; y[1] = y[2]; y[2] = x[0] + x[2] + (2.f*x[1]) + (LUMA_0*y[0]) + (LUMA_1*y[1]);
|
|
|
|
return y[2];
|
|
}
|
|
|
|
// Butterworth Lowpass digital filter
|
|
// Filter Order: 2 -> poles for low pass
|
|
static double initFilterSignal(double z) {
|
|
static double x[SIGNAL_ZEROS + 1] = { 0,0,0 };
|
|
static double y[SIGNAL_POLES + 1] = { 0,0,0 };
|
|
|
|
x[0] = x[1]; x[1] = x[2]; x[2] = z / SIGNAL_GAIN;
|
|
y[0] = y[1]; y[1] = y[2]; y[2] = x[0] + x[2] + (2.f*x[1]) + (SIGNAL_0*y[0]) + (SIGNAL_1*y[1]);
|
|
|
|
return y[2];
|
|
}
|
|
|
|
// Build the 4 phase chroma lookup table
|
|
// The YI'Q' colors are hard-coded
|
|
static void initChromaPhaseTables(color_mode_t color_mode, mono_mode_t mono_mode) {
|
|
int phase,s,t,n;
|
|
double z = 0;
|
|
double y0,y1,c,i,q = 0;
|
|
double phi,zz;
|
|
float brightness;
|
|
double r64,g64,b64;
|
|
float r32,g32,b32;
|
|
|
|
for (phase = 0; phase < 4; phase++) {
|
|
phi = (phase * RAD_90) + CYCLESTART;
|
|
for (s = 0; s < NTSC_NUM_SEQUENCES; s++) {
|
|
t = s;
|
|
y0 = y1 = c = i = q = 0.0;
|
|
|
|
for (n = 0; n < 12; n++) {
|
|
z = (double)(0 != (t & 0x800));
|
|
t = t << 1;
|
|
|
|
for (unsigned int k = 0; k < 2; k++) {
|
|
//z = z * 1.25;
|
|
zz = initFilterSignal(z);
|
|
c = initFilterChroma(zz); // "Mostly" correct _if_ CYCLESTART = PI/4 = 45 degrees
|
|
y0 = initFilterLuma0 (zz);
|
|
y1 = initFilterLuma1 (zz - c);
|
|
|
|
c = c * 2.f;
|
|
i = i + (c * cos(phi) - i) / 8.f;
|
|
q = q + (c * sin(phi) - q) / 8.f;
|
|
|
|
phi += RAD_45;
|
|
} // k
|
|
} // samples
|
|
|
|
brightness = clampZeroOne((float)z);
|
|
if (color_mode == COLOR_MODE_MONO && mono_mode == MONO_MODE_GREEN) {
|
|
monoPixelsMonitor[s] = (((uint8_t)(brightness * 0xFF)) << SHIFT_G) | (0x00 << SHIFT_R) | (0x00 << SHIFT_B) | (0xFF << SHIFT_A);
|
|
brightness = clampZeroOne((float)y1);
|
|
monoPixelsTV[s] = (((uint8_t)(brightness * 0xFF)) << SHIFT_G) | (0x00 << SHIFT_R) | (0x00 << SHIFT_B) | (0xFF << SHIFT_A);
|
|
} else {
|
|
monoPixelsMonitor[s] = (((uint8_t)(brightness * 0xFF)) << SHIFT_R) |
|
|
(((uint8_t)(brightness * 0xFF)) << SHIFT_G) |
|
|
(((uint8_t)(brightness * 0xFF)) << SHIFT_B) |
|
|
(0xFF << SHIFT_A);
|
|
|
|
brightness = clampZeroOne((float)y1);
|
|
monoPixelsTV[s] = (((uint8_t)(brightness * 0xFF)) << SHIFT_R) |
|
|
(((uint8_t)(brightness * 0xFF)) << SHIFT_G) |
|
|
(((uint8_t)(brightness * 0xFF)) << SHIFT_B) |
|
|
(0xFF << SHIFT_A);
|
|
}
|
|
|
|
/*
|
|
YI'V' to RGB
|
|
[r g b] = [y i v][ 1 1 1 ]
|
|
[0.956 -0.272 -1.105]
|
|
[0.621 -0.647 1.702]
|
|
[r] [1 0.956 0.621][y]
|
|
[g] = [1 -0.272 -0.647][i]
|
|
[b] [1 -1.105 1.702][v]
|
|
*/
|
|
#define I_TO_R 0.956f
|
|
#define I_TO_G -0.272f
|
|
#define I_TO_B -1.105f
|
|
|
|
#define Q_TO_R 0.621f
|
|
#define Q_TO_G -0.647f
|
|
#define Q_TO_B 1.702f
|
|
|
|
r64 = y0 + (I_TO_R * i) + (Q_TO_R * q);
|
|
g64 = y0 + (I_TO_G * i) + (Q_TO_G * q);
|
|
b64 = y0 + (I_TO_B * i) + (Q_TO_B * q);
|
|
|
|
r32 = clampZeroOne((float)r64);
|
|
g32 = clampZeroOne((float)g64);
|
|
b32 = clampZeroOne((float)b64);
|
|
|
|
int color = s & 15;
|
|
|
|
// NTSC_REMOVE_WHITE_RINGING
|
|
if (color == IDX_WHITE) {
|
|
r32 = 1;
|
|
g32 = 1;
|
|
b32 = 1;
|
|
}
|
|
|
|
// NTSC_REMOVE_BLACK_GHOSTING
|
|
if (color == IDX_BLACK) {
|
|
r32 = 0;
|
|
g32 = 0;
|
|
b32 = 0;
|
|
}
|
|
|
|
// NTSC_REMOVE_GRAY_CHROMA
|
|
if (color == IDX_DARKGREY) {
|
|
const float g = (float) 0x83 / (float) 0xFF;
|
|
r32 = g;
|
|
g32 = g;
|
|
b32 = g;
|
|
}
|
|
|
|
if (color == IDX_LIGHTGREY) { // Gray2 & Gray1
|
|
const float g = (float) 0x78 / (float) 0xFF;
|
|
r32 = g;
|
|
g32 = g;
|
|
b32 = g;
|
|
}
|
|
|
|
colorPixelsMonitor[phase][s] = (((uint8_t)(r32 * 255)) << SHIFT_R) |
|
|
(((uint8_t)(g32 * 255)) << SHIFT_G) |
|
|
(((uint8_t)(b32 * 255)) << SHIFT_B) |
|
|
(0xFF << SHIFT_A);
|
|
|
|
r64 = y1 + (I_TO_R * i) + (Q_TO_R * q);
|
|
g64 = y1 + (I_TO_G * i) + (Q_TO_G * q);
|
|
b64 = y1 + (I_TO_B * i) + (Q_TO_B * q);
|
|
|
|
r32 = clampZeroOne((float)r64);
|
|
g32 = clampZeroOne((float)g64);
|
|
b32 = clampZeroOne((float)b64);
|
|
|
|
// NTSC_REMOVE_WHITE_RINGING
|
|
if (color == IDX_WHITE) {
|
|
r32 = 1;
|
|
g32 = 1;
|
|
b32 = 1;
|
|
}
|
|
|
|
// NTSC_REMOVE_BLACK_GHOSTING
|
|
if (color == IDX_BLACK) {
|
|
r32 = 0;
|
|
g32 = 0;
|
|
b32 = 0;
|
|
}
|
|
|
|
colorPixelsTV[phase][s] = (((uint8_t)(r32 * 255)) << SHIFT_R) |
|
|
(((uint8_t)(g32 * 255)) << SHIFT_G) |
|
|
(((uint8_t)(b32 * 255)) << SHIFT_B) |
|
|
(0xFF << SHIFT_A);
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
static void ntsc_prefsChanged(const char *domain) {
|
|
|
|
long lVal = 0;
|
|
color_mode_t color_mode = prefs_parseLongValue(domain, PREF_COLOR_MODE, &lVal, /*base:*/10) ? getColorMode(lVal) : COLOR_MODE_DEFAULT;
|
|
|
|
lVal = MONO_MODE_BW;
|
|
mono_mode_t mono_mode = prefs_parseLongValue(domain, PREF_MONO_MODE, &lVal, /*base:*/10) ? getMonoMode(lVal) : MONO_MODE_DEFAULT;
|
|
|
|
bool bVal = false;
|
|
half_scanlines = prefs_parseBoolValue(domain, PREF_SHOW_HALF_SCANLINES, &bVal) ? (bVal ? 1 : 0) : 1;
|
|
|
|
initChromaPhaseTables(color_mode, mono_mode);
|
|
}
|
|
|
|
static void _init_ntsc(void) {
|
|
LOG("Initializing NTSC renderer");
|
|
initChromaPhaseTables(COLOR_MODE_DEFAULT, MONO_MODE_DEFAULT);
|
|
|
|
pixelPlotter[COLOR_MODE_MONO] = updateFBMonoMonitor;
|
|
pixelPlotter[COLOR_MODE_COLOR] = updateFBMonoMonitor;
|
|
pixelPlotter[COLOR_MODE_INTERP] = updateFBMonoMonitor;
|
|
pixelPlotter[COLOR_MODE_COLOR_MONITOR] = updateFBColorMonitor;
|
|
pixelPlotter[COLOR_MODE_MONO_TV] = updateFBMonoTV;
|
|
pixelPlotter[COLOR_MODE_COLOR_TV] = updateFBColorTV;
|
|
|
|
getHalfColor[COLOR_MODE_MONO][0] = doubleScanlineMonitor;
|
|
getHalfColor[COLOR_MODE_MONO][1] = halfScanlineMonitor;
|
|
getHalfColor[COLOR_MODE_COLOR][0] = doubleScanlineMonitor;
|
|
getHalfColor[COLOR_MODE_COLOR][1] = halfScanlineMonitor;
|
|
getHalfColor[COLOR_MODE_INTERP][0] = doubleScanlineMonitor;
|
|
getHalfColor[COLOR_MODE_INTERP][1] = halfScanlineMonitor;
|
|
getHalfColor[COLOR_MODE_COLOR_MONITOR][0] = doubleScanlineMonitor;
|
|
getHalfColor[COLOR_MODE_COLOR_MONITOR][1] = halfScanlineMonitor;
|
|
getHalfColor[COLOR_MODE_MONO_TV][0] = doubleScanlineTV;
|
|
getHalfColor[COLOR_MODE_MONO_TV][1] = halfScanlineTV;
|
|
getHalfColor[COLOR_MODE_COLOR_TV][0] = doubleScanlineTV;
|
|
getHalfColor[COLOR_MODE_COLOR_TV][1] = halfScanlineTV;
|
|
|
|
prefs_registerListener(PREF_DOMAIN_VIDEO, &ntsc_prefsChanged);
|
|
}
|
|
|
|
static __attribute__((constructor)) void __init_ntsc(void) {
|
|
emulator_registerStartupCallback(CTOR_PRIORITY_LATE, &_init_ntsc);
|
|
}
|