Fairly large reorganization of the Blargg NTSC TV effects code:

- converted many pointers to references
  - merged code from several files into one class
  - broke up some methods into more managable chunks

This will allow it to be easy to add the phosphor code during
NTSC TV emulation.
This commit is contained in:
Stephen Anthony 2017-06-17 20:08:36 -02:30
parent 5da20a3a7a
commit 0f2df7e87b
9 changed files with 730 additions and 826 deletions

View File

@ -0,0 +1,428 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "AtariNTSC.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AtariNTSC::initialize(const Setup& setup, const uInt8* palette)
{
init(myImpl, setup);
initializePalette(palette);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AtariNTSC::initializePalette(const uInt8* palette)
{
// Palette stores R/G/B data for 'palette_size' entries
for ( uInt32 entry = 0; entry < palette_size; ++entry )
{
float r = myImpl.to_float [*palette++];
float g = myImpl.to_float [*palette++];
float b = myImpl.to_float [*palette++];
float y, i, q = RGB_TO_YIQ( r, g, b, y, i );
// Generate kernel
int ir, ig, ib = YIQ_TO_RGB( y, i, q, myImpl.to_rgb, int, ir, ig );
uInt32 rgb = PACK_RGB( ir, ig, ib );
uInt32* kernel = myNTSC.table[entry];
genKernel(myImpl, y, i, q, kernel);
for ( uInt32 i = 0; i < rgb_kernel_size / 2; i++ )
{
uInt32 error = rgb -
kernel [i ] - kernel [(i+10)%14+14] -
kernel [i + 7] - kernel [i + 3 +14];
kernel [i + 3 + 14] += error;
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AtariNTSC::blitSingle(const uInt8* atari_in, uInt32 in_width,
uInt32 in_height, void* rgb_out, uInt32 out_pitch)
{
uInt32 const chunk_count = (in_width - 1) / AN_in_chunk;
while ( in_height-- )
{
const uInt8* line_in = atari_in;
ATARI_NTSC_BEGIN_ROW( &myNTSC, AN_black, line_in[0] );
uInt32* restrict line_out = static_cast<uInt32*>(rgb_out);
++line_in;
for ( uInt32 n = chunk_count; n; --n )
{
/* order of input and output pixels must not be altered */
ATARI_NTSC_COLOR_IN( 0, &myNTSC, line_in[0] );
ATARI_NTSC_RGB_OUT_8888( 0, line_out[0] );
ATARI_NTSC_RGB_OUT_8888( 1, line_out[1] );
ATARI_NTSC_RGB_OUT_8888( 2, line_out[2] );
ATARI_NTSC_RGB_OUT_8888( 3, line_out[3] );
ATARI_NTSC_COLOR_IN( 1, &myNTSC, line_in[1] );
ATARI_NTSC_RGB_OUT_8888( 4, line_out[4] );
ATARI_NTSC_RGB_OUT_8888( 5, line_out[5] );
ATARI_NTSC_RGB_OUT_8888( 6, line_out[6] );
line_in += 2;
line_out += 7;
}
/* finish final pixels */
ATARI_NTSC_COLOR_IN( 0, &myNTSC, AN_black );
ATARI_NTSC_RGB_OUT_8888( 0, line_out[0] );
ATARI_NTSC_RGB_OUT_8888( 1, line_out[1] );
ATARI_NTSC_RGB_OUT_8888( 2, line_out[2] );
ATARI_NTSC_RGB_OUT_8888( 3, line_out[3] );
ATARI_NTSC_COLOR_IN( 1, &myNTSC, AN_black );
ATARI_NTSC_RGB_OUT_8888( 4, line_out[4] );
ATARI_NTSC_RGB_OUT_8888( 5, line_out[5] );
ATARI_NTSC_RGB_OUT_8888( 6, line_out[6] );
atari_in += in_width;
rgb_out = static_cast<char*>(rgb_out) + out_pitch;
}
}
#if 0
void atari_ntsc_blit_double( atari_ntsc_t const* ntsc,
atari_ntsc_in_t const* atari_in1, atari_ntsc_in_t const* atari_in2,
uInt32 in_width, uInt32 in_height, void* rgb_out, uInt32 out_pitch )
{
#define TO_DOUBLE(pixel1, pixel2) (((pixel1>>1)<<7)+(pixel2>>1))
uInt32 const chunk_count = (in_width - 1) / atari_ntsc_in_chunk;
while ( in_height-- )
{
atari_ntsc_in_t const* line_in1 = atari_in1;
atari_ntsc_in_t const* line_in2 = atari_in2;
ATARI_NTSC_BEGIN_ROW( ntsc,
TO_DOUBLE(atari_ntsc_black, atari_ntsc_black),
TO_DOUBLE(line_in1[0], line_in2[0]) );
uInt32* restrict line_out = static_cast<uInt32*>(rgb_out);
++line_in1;
++line_in2;
for ( uInt32 n = chunk_count; n; --n )
{
/* order of input and output pixels must not be altered */
ATARI_NTSC_COLOR_IN( 0, ntsc,
TO_DOUBLE(line_in1[0], line_in2[0]) );
ATARI_NTSC_RGB_OUT_8888( 0, line_out[0] );
ATARI_NTSC_RGB_OUT_8888( 1, line_out[1] );
ATARI_NTSC_RGB_OUT_8888( 2, line_out[2] );
ATARI_NTSC_RGB_OUT_8888( 3, line_out[3] );
ATARI_NTSC_COLOR_IN( 1, ntsc,
TO_DOUBLE(line_in1[1], line_in2[1]) );
ATARI_NTSC_RGB_OUT_8888( 4, line_out[4] );
ATARI_NTSC_RGB_OUT_8888( 5, line_out[5] );
ATARI_NTSC_RGB_OUT_8888( 6, line_out[6] );
line_in1 += 2;
line_in2 += 2;
line_out += 7;
}
/* finish final pixels */
ATARI_NTSC_COLOR_IN( 0, ntsc,
TO_DOUBLE(atari_ntsc_black, atari_ntsc_black) );
ATARI_NTSC_RGB_OUT_8888( 0, line_out[0] );
ATARI_NTSC_RGB_OUT_8888( 1, line_out[1] );
ATARI_NTSC_RGB_OUT_8888( 2, line_out[2] );
ATARI_NTSC_RGB_OUT_8888( 3, line_out[3] );
ATARI_NTSC_COLOR_IN( 1, ntsc,
TO_DOUBLE(atari_ntsc_black, atari_ntsc_black) );
ATARI_NTSC_RGB_OUT_8888( 4, line_out[4] );
ATARI_NTSC_RGB_OUT_8888( 5, line_out[5] );
ATARI_NTSC_RGB_OUT_8888( 6, line_out[6] );
atari_in1 += in_width;
atari_in2 += in_width;
rgb_out = static_cast<char*>(rgb_out) + out_pitch;
}
}
#endif
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AtariNTSC::init(init_t& impl, const Setup& setup)
{
impl.brightness = float(setup.brightness) * (0.5f * rgb_unit) + rgb_offset;
impl.contrast = float(setup.contrast) * (0.5f * rgb_unit) + rgb_unit;
impl.artifacts = float(setup.artifacts);
if ( impl.artifacts > 0 )
impl.artifacts *= artifacts_max - artifacts_mid;
impl.artifacts = impl.artifacts * artifacts_mid + artifacts_mid;
impl.fringing = float(setup.fringing);
if ( impl.fringing > 0 )
impl.fringing *= fringing_max - fringing_mid;
impl.fringing = impl.fringing * fringing_mid + fringing_mid;
initFilters(impl, setup);
/* generate gamma table */
if ( gamma_size > 1 )
{
float const to_float = 1.0f / (gamma_size - (gamma_size > 1));
float const gamma = 1.1333f - float(setup.gamma) * 0.5f;
/* match common PC's 2.2 gamma to TV's 2.65 gamma */
int i;
for ( i = 0; i < gamma_size; i++ )
impl.to_float [i] =
float(pow( i * to_float, gamma )) * impl.contrast + impl.brightness;
}
/* setup decoder matricies */
{
float hue = float(setup.hue) * PI + PI / 180 * ext_decoder_hue;
float sat = float(setup.saturation) + 1;
float const* decoder = setup.decoder_matrix;
if ( !decoder )
{
decoder = default_decoder;
hue += PI / 180 * (std_decoder_hue - ext_decoder_hue);
}
{
float s = float(sin( hue )) * sat;
float c = float(cos( hue )) * sat;
float* out = impl.to_rgb;
int n;
n = burst_count; // FIXME: dead code detected by llvm scan-build
do
{
float const* in = decoder;
int n2 = 3;
do
{
float i = *in++;
float q = *in++;
*out++ = i * c - q * s;
*out++ = i * s + q * c;
}
while ( --n2 );
if ( burst_count <= 1 )
break;
ROTATE_IQ( s, c, 0.866025f, -0.5f ); /* +120 degrees */
}
while ( --n );
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AtariNTSC::initFilters(init_t& impl, const Setup& setup)
{
float kernels [kernel_size * 2];
/* generate luma (y) filter using sinc kernel */
{
/* sinc with rolloff (dsf) */
float const rolloff = 1 + float(setup.sharpness) * 0.032;
float const maxh = 32;
float const pow_a_n = float(pow( rolloff, maxh ));
float sum;
int i;
/* quadratic mapping to reduce negative (blurring) range */
float to_angle = float(setup.resolution) + 1;
to_angle = PI / maxh * float(LUMA_CUTOFF) * (to_angle * to_angle + 1);
kernels [kernel_size * 3 / 2] = maxh; /* default center value */
for ( i = 0; i < kernel_half * 2 + 1; i++ )
{
int x = i - kernel_half;
float angle = x * to_angle;
/* instability occurs at center point with rolloff very close to 1.0 */
if ( x || pow_a_n > 1.056 || pow_a_n < 0.981 )
{
float rolloff_cos_a = rolloff * float(cos( angle ));
float num = 1 - rolloff_cos_a -
pow_a_n * float(cos( maxh * angle )) +
pow_a_n * rolloff * float(cos( (maxh - 1) * angle ));
float den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
float dsf = num / den;
kernels [kernel_size * 3 / 2 - kernel_half + i] = dsf - 0.5;
}
}
/* apply blackman window and find sum */
sum = 0;
for ( i = 0; i < kernel_half * 2 + 1; i++ )
{
float x = PI * 2 / (kernel_half * 2) * i;
float blackman = 0.42f - 0.5f * float(cos( x )) + 0.08f * float(cos( x * 2 ));
sum += (kernels [kernel_size * 3 / 2 - kernel_half + i] *= blackman);
}
/* normalize kernel */
sum = 1.0f / sum;
for ( i = 0; i < kernel_half * 2 + 1; i++ )
{
int x = kernel_size * 3 / 2 - kernel_half + i;
kernels [x] *= sum;
}
}
/* generate chroma (iq) filter using gaussian kernel */
{
float const cutoff_factor = -0.03125f;
float cutoff = float(setup.bleed);
int i;
if ( cutoff < 0 )
{
/* keep extreme value accessible only near upper end of scale (1.0) */
cutoff *= cutoff;
cutoff *= cutoff;
cutoff *= cutoff;
cutoff *= -30.0f / 0.65f;
}
cutoff = cutoff_factor - 0.65f * cutoff_factor * cutoff;
for ( i = -kernel_half; i <= kernel_half; i++ )
kernels [kernel_size / 2 + i] = float(exp( i * i * cutoff ));
/* normalize even and odd phases separately */
for ( i = 0; i < 2; i++ )
{
float sum = 0;
int x;
for ( x = i; x < kernel_size; x += 2 )
sum += kernels [x];
sum = 1.0f / sum;
for ( x = i; x < kernel_size; x += 2 )
{
kernels [x] *= sum;
}
}
}
/* generate linear rescale kernels */
float weight = 1.0f;
float* out = impl.kernel;
int n = rescale_out;
do
{
float remain = 0;
int i;
weight -= 1.0f / rescale_in;
for ( i = 0; i < kernel_size * 2; i++ )
{
float cur = kernels [i];
float m = cur * weight;
*out++ = m + remain;
remain = cur - m;
}
}
while ( --n );
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Generate pixel at all burst phases and column alignments
void AtariNTSC::genKernel(init_t& impl, float y, float i, float q, uInt32* out)
{
/* generate for each scanline burst phase */
float const* to_rgb = impl.to_rgb;
int burst_remain = burst_count;
y -= rgb_offset;
do
{
/* Encode yiq into *two* composite signals (to allow control over artifacting).
Convolve these with kernels which: filter respective components, apply
sharpening, and rescale horizontally. Convert resulting yiq to rgb and pack
into integer. Based on algorithm by NewRisingSun. */
pixel_info_t const* pixel = atari_ntsc_pixels;
int alignment_remain = alignment_count;
do
{
/* negate is -1 when composite starts at odd multiple of 2 */
float const yy = y * impl.fringing * pixel->negate;
float const ic0 = (i + yy) * pixel->kernel [0];
float const qc1 = (q + yy) * pixel->kernel [1];
float const ic2 = (i - yy) * pixel->kernel [2];
float const qc3 = (q - yy) * pixel->kernel [3];
float const factor = impl.artifacts * pixel->negate;
float const ii = i * factor;
float const yc0 = (y + ii) * pixel->kernel [0];
float const yc2 = (y - ii) * pixel->kernel [2];
float const qq = q * factor;
float const yc1 = (y + qq) * pixel->kernel [1];
float const yc3 = (y - qq) * pixel->kernel [3];
float const* k = &impl.kernel [pixel->offset];
int n;
++pixel;
for ( n = rgb_kernel_size; n; --n )
{
float fi = k[0]*ic0 + k[2]*ic2;
float fq = k[1]*qc1 + k[3]*qc3;
float fy = k[kernel_size+0]*yc0 + k[kernel_size+1]*yc1 +
k[kernel_size+2]*yc2 + k[kernel_size+3]*yc3 + rgb_offset;
if ( k < &impl.kernel [kernel_size * 2 * (rescale_out - 1)] )
k += kernel_size * 2 - 1;
else
k -= kernel_size * 2 * (rescale_out - 1) + 2;
{
int r, g, b = YIQ_TO_RGB( fy, fi, fq, to_rgb, int, r, g );
*out++ = PACK_RGB( r, g, b ) - rgb_bias;
}
}
}
while ( alignment_count > 1 && --alignment_remain );
if ( burst_count <= 1 )
break;
to_rgb += 6;
ROTATE_IQ( i, q, -0.866025f, -0.5f ); /* -120 degrees */
}
while ( --burst_remain );
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const AtariNTSC::Setup AtariNTSC::TV_Composite = {
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.15, 0.0, 0.0, 0.0, 0
};
const AtariNTSC::Setup AtariNTSC::TV_SVideo = {
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45, -1.0, -1.0, 0.0, 0
};
const AtariNTSC::Setup AtariNTSC::TV_RGB = {
0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.70, -1.0, -1.0, -1.0, 0
};
const AtariNTSC::Setup AtariNTSC::TV_Bad = {
0.1, -0.3, 0.3, 0.25, 0.2, 0.0, 0.1, 0.5, 0.5, 0.5, 0
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const AtariNTSC::pixel_info_t AtariNTSC::atari_ntsc_pixels[alignment_count] = {
{ PIXEL_OFFSET( -4, -9 ), { 1, 1, 1, 1 } },
{ PIXEL_OFFSET( 0, -5 ), { 1, 1, 1, 1 } },
};
const float AtariNTSC::default_decoder[6] = {
0.9563f, 0.6210f, -0.2721f, -0.6474f, -1.1070f, 1.7046f
};

View File

@ -0,0 +1,278 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
/*
* Atari TIA NTSC video filter
* Based on nes_ntsc 0.2.2. http://www.slack.net/~ant
*
* Copyright (C) 2006-2009 Shay Green. This module is free software; you
* can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version. This
* module 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 Lesser General Public License for more
* details. You should have received a copy of the GNU Lesser General Public
* License along with this module; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef ATARI_NTSC_HXX
#define ATARI_NTSC_HXX
#include <cmath>
#include "bspf.hxx"
class AtariNTSC
{
public:
enum {
palette_size = 256,
entry_size = 2 * 14,
};
// Image parameters, ranging from -1.0 to 1.0. Actual internal values shown
// in parenthesis and should remain fairly stable in future versions.
struct Setup // atari_ntsc_setup_t
{
// Basic parameters
double hue; // -1 = -180 degrees +1 = +180 degrees
double saturation; // -1 = grayscale (0.0) +1 = oversaturated colors (2.0)
double contrast; // -1 = dark (0.5) +1 = light (1.5)
double brightness; // -1 = dark (0.5) +1 = light (1.5)
double sharpness; // edge contrast enhancement/blurring
// Advanced parameters
double gamma; // -1 = dark (1.5) +1 = light (0.5)
double resolution; // image resolution
double artifacts; // artifacts caused by color changes
double fringing; // color artifacts caused by brightness changes
double bleed; // color bleed (color resolution reduction)
float const* decoder_matrix; // optional RGB decoder matrix, 6 elements
};
// Video format presets
static const Setup TV_Composite; // color bleeding + artifacts
static const Setup TV_SVideo; // color bleeding only
static const Setup TV_RGB; // crisp image
static const Setup TV_Bad; // badly adjusted TV
// Initializes and adjusts parameters.
void initialize(const Setup& setup, const uInt8* palette);
void initializePalette(const uInt8* palette);
// Filters one or more rows of pixels. Input pixels are 8-bit Atari
// palette colors.
// In_row_width is the number of pixels to get to the next input row.
// Out_pitch is the number of *bytes* to get to the next output row.
void blitSingle(const uInt8* atari_in, uInt32 in_width, uInt32 in_height,
void* rgb_out, uInt32 out_pitch);
// Number of output pixels written by blitter for given input width.
// Width might be rounded down slightly; use inWidth() on result to
// find rounded value. Guaranteed not to round 160 down at all.
static uInt32 outWidth(uInt32 in_width) {
return ((((in_width) - 1) / AN_in_chunk + 1)* AN_out_chunk);
}
// Number of input pixels that will fit within given output width.
// Might be rounded down slightly; use outWidth() on result to find
// rounded value.
static uInt32 inWidth( uInt32 out_width ) {
return (((out_width) / AN_out_chunk - 1) * AN_in_chunk + 1);
}
private:
enum {
AN_in_chunk = 2, // number of input pixels read per chunk
AN_out_chunk = 7, // number of output pixels generated per chunk
AN_black = 0, // palette index for black
alignment_count = 2,
burst_count = 1,
rescale_in = 8,
rescale_out = 7,
burst_size = entry_size / burst_count,
kernel_half = 16,
kernel_size = kernel_half * 2 + 1,
gamma_size = 256,
rgb_builder = ((1 << 21) | (1 << 11) | (1 << 1)),
rgb_kernel_size = burst_size / alignment_count,
rgb_bits = 8,
rgb_unit = (1 << rgb_bits),
rgb_bias = rgb_unit * 2 * rgb_builder,
std_decoder_hue = 0,
ext_decoder_hue = std_decoder_hue + 15
};
#define artifacts_mid 1.5f
#define artifacts_max 2.5f
#define fringing_mid 1.0f
#define fringing_max 2.0f
#define rgb_offset (rgb_unit * 2 + 0.5f)
#undef PI
#define PI 3.14159265358979323846f
#define LUMA_CUTOFF 0.20
struct atari_ntsc_t {
uInt32 table[palette_size][entry_size];
};
atari_ntsc_t myNTSC;
struct init_t
{
float to_rgb [burst_count * 6];
float to_float [gamma_size];
float contrast;
float brightness;
float artifacts;
float fringing;
float kernel [rescale_out * kernel_size * 2];
};
init_t myImpl;
struct pixel_info_t
{
int offset;
float negate;
float kernel [4];
};
static const pixel_info_t atari_ntsc_pixels[alignment_count];
static const float default_decoder[6];
void init(init_t& impl, const Setup& setup);
void initFilters(init_t& impl, const Setup& setup);
// Generate pixel at all burst phases and column alignments
void genKernel(init_t& impl, float y, float i, float q, uInt32* out);
// Begins outputting row and starts two pixels. First pixel will be cut
// off a bit. Use atari_ntsc_black for unused pixels.
#define ATARI_NTSC_BEGIN_ROW( ntsc, pixel0, pixel1 ) \
ATARI_NTSC_BEGIN_ROW_6_( pixel0, pixel1, ATARI_NTSC_ENTRY_, ntsc )
// Begins input pixel
#define ATARI_NTSC_COLOR_IN( in_index, ntsc, color_in ) \
ATARI_NTSC_COLOR_IN_( in_index, color_in, ATARI_NTSC_ENTRY_, ntsc )
// Generates output in the specified 32-bit format (x = junk bits).
// native: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format)
// 8888: 00000000 RRRRRRRR GGGGGGGG BBBBBBBB (8-8-8-8 32-bit ARGB)
#define ATARI_NTSC_RGB_OUT_8888( index, rgb_out ) {\
uInt32 raw_ =\
kernel0 [index ] + kernel1 [(index+10)%7+14] +\
kernelx0 [(index+7)%14] + kernelx1 [(index+ 3)%7+14+7];\
ATARI_NTSC_CLAMP_( raw_, 0 );\
rgb_out = (raw_>>5 & 0x00FF0000)|(raw_>>3 & 0x0000FF00)|(raw_>>1 & 0x000000FF);\
}
#define ATARI_NTSC_ENTRY_( ntsc, n ) (ntsc)->table [n]
// common 3->7 ntsc macros
#define ATARI_NTSC_BEGIN_ROW_6_( pixel0, pixel1, ENTRY, table ) \
unsigned const atari_ntsc_pixel0_ = (pixel0);\
uInt32 const* kernel0 = ENTRY( table, atari_ntsc_pixel0_ );\
unsigned const atari_ntsc_pixel1_ = (pixel1);\
uInt32 const* kernel1 = ENTRY( table, atari_ntsc_pixel1_ );\
uInt32 const* kernelx0;\
uInt32 const* kernelx1 = kernel0
// common ntsc macros
#define atari_ntsc_clamp_mask (rgb_builder * 3 / 2)
#define atari_ntsc_clamp_add (rgb_builder * 0x101)
#define ATARI_NTSC_CLAMP_( io, shift ) {\
uInt32 sub = (io) >> (9-(shift)) & atari_ntsc_clamp_mask;\
uInt32 clamp = atari_ntsc_clamp_add - sub;\
io |= clamp;\
clamp -= sub;\
io &= clamp;\
}
#define ATARI_NTSC_COLOR_IN_( index, color, ENTRY, table ) {\
unsigned color_;\
kernelx##index = kernel##index;\
kernel##index = (color_ = (color), ENTRY( table, color_ ));\
}
// kernel generation
#define ROTATE_IQ( i, q, sin_b, cos_b ) {\
float t;\
t = i * cos_b - q * sin_b;\
q = i * sin_b + q * cos_b;\
i = t;\
}
#define RGB_TO_YIQ( r, g, b, y, i ) (\
(y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\
(i = (r) * 0.595716f - (g) * 0.274453f - (b) * 0.321263f),\
((r) * 0.211456f - (g) * 0.522591f + (b) * 0.311135f)\
)
#define YIQ_TO_RGB( y, i, q, to_rgb, type, r, g ) (\
r = type(y + to_rgb [0] * i + to_rgb [1] * q),\
g = type(y + to_rgb [2] * i + to_rgb [3] * q),\
type(y + to_rgb [4] * i + to_rgb [5] * q)\
)
#ifndef PACK_RGB
#define PACK_RGB( r, g, b ) ((r) << 21 | (g) << 11 | (b) << 1)
#endif
#define PIXEL_OFFSET_( ntsc, scaled ) \
(kernel_size / 2 + ntsc + (scaled != 0) + (rescale_out - scaled) % rescale_out + \
(kernel_size * 2 * scaled))
#define PIXEL_OFFSET( ntsc, scaled ) \
PIXEL_OFFSET_( ((ntsc) - (scaled) / rescale_out * rescale_in),\
(((scaled) + rescale_out * 10) % rescale_out) ),\
(1.0f - (((ntsc) + 100) & 2))
#define DISTRIBUTE_ERROR( a, b, c ) {\
uInt32 fourth = (error + 2 * rgb_builder) >> 2;\
fourth &= (rgb_bias >> 1) - rgb_builder;\
fourth -= rgb_bias >> 2;\
out [a] += fourth;\
out [b] += fourth;\
out [c] += fourth;\
out [i] += error - (fourth * 3);\
}
#define RGB_PALETTE_OUT( rgb, out_ )\
{\
unsigned char* out = (out_);\
uInt32 clamped = (rgb);\
ATARI_NTSC_CLAMP_( clamped, (8 - rgb_bits) );\
out [0] = (unsigned char) (clamped >> 21);\
out [1] = (unsigned char) (clamped >> 11);\
out [2] = (unsigned char) (clamped >> 1);\
}
// blitter related
#ifndef restrict
#if defined (__GNUC__)
#define restrict __restrict__
#elif defined (_MSC_VER) && _MSC_VER > 1300
#define restrict __restrict
#else
/* no support for restricted pointers */
#define restrict
#endif
#endif
};
#endif

View File

@ -22,7 +22,7 @@
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NTSCFilter::NTSCFilter() NTSCFilter::NTSCFilter()
: mySetup(atari_ntsc_composite), : mySetup(AtariNTSC::TV_Composite),
myPreset(PRESET_OFF), myPreset(PRESET_OFF),
myCurrentAdjustable(0) myCurrentAdjustable(0)
{ {
@ -76,19 +76,19 @@ string NTSCFilter::setPreset(Preset preset)
switch(myPreset) switch(myPreset)
{ {
case PRESET_COMPOSITE: case PRESET_COMPOSITE:
mySetup = atari_ntsc_composite; mySetup = AtariNTSC::TV_Composite;
msg = "COMPOSITE"; msg = "COMPOSITE";
break; break;
case PRESET_SVIDEO: case PRESET_SVIDEO:
mySetup = atari_ntsc_svideo; mySetup = AtariNTSC::TV_SVideo;
msg = "S-VIDEO"; msg = "S-VIDEO";
break; break;
case PRESET_RGB: case PRESET_RGB:
mySetup = atari_ntsc_rgb; mySetup = AtariNTSC::TV_RGB;
msg = "RGB"; msg = "RGB";
break; break;
case PRESET_BAD: case PRESET_BAD:
mySetup = atari_ntsc_bad; mySetup = AtariNTSC::TV_Bad;
msg = "BAD ADJUST"; msg = "BAD ADJUST";
break; break;
case PRESET_CUSTOM: case PRESET_CUSTOM:
@ -220,13 +220,13 @@ void NTSCFilter::getAdjustables(Adjustable& adjustable, Preset preset) const
switch(preset) switch(preset)
{ {
case PRESET_COMPOSITE: case PRESET_COMPOSITE:
convertToAdjustable(adjustable, atari_ntsc_composite); break; convertToAdjustable(adjustable, AtariNTSC::TV_Composite); break;
case PRESET_SVIDEO: case PRESET_SVIDEO:
convertToAdjustable(adjustable, atari_ntsc_svideo); break; convertToAdjustable(adjustable, AtariNTSC::TV_SVideo); break;
case PRESET_RGB: case PRESET_RGB:
convertToAdjustable(adjustable, atari_ntsc_rgb); break; convertToAdjustable(adjustable, AtariNTSC::TV_RGB); break;
case PRESET_BAD: case PRESET_BAD:
convertToAdjustable(adjustable, atari_ntsc_bad); break; convertToAdjustable(adjustable, AtariNTSC::TV_Bad); break;
case PRESET_CUSTOM: case PRESET_CUSTOM:
convertToAdjustable(adjustable, myCustomSetup); break; convertToAdjustable(adjustable, myCustomSetup); break;
default: default:
@ -251,7 +251,7 @@ void NTSCFilter::setCustomAdjustables(Adjustable& adjustable)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void NTSCFilter::convertToAdjustable(Adjustable& adjustable, void NTSCFilter::convertToAdjustable(Adjustable& adjustable,
const atari_ntsc_setup_t& setup) const const AtariNTSC::Setup& setup) const
{ {
adjustable.hue = SCALE_TO_100(setup.hue); adjustable.hue = SCALE_TO_100(setup.hue);
adjustable.saturation = SCALE_TO_100(setup.saturation); adjustable.saturation = SCALE_TO_100(setup.saturation);
@ -266,7 +266,7 @@ void NTSCFilter::convertToAdjustable(Adjustable& adjustable,
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
atari_ntsc_setup_t NTSCFilter::myCustomSetup = atari_ntsc_composite; AtariNTSC::Setup NTSCFilter::myCustomSetup = AtariNTSC::TV_Composite;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const NTSCFilter::AdjustableTag NTSCFilter::ourCustomAdjustables[10] = { const NTSCFilter::AdjustableTag NTSCFilter::ourCustomAdjustables[10] = {

View File

@ -22,7 +22,7 @@ class TIASurface;
class Settings; class Settings;
#include "bspf.hxx" #include "bspf.hxx"
#include "atari_ntsc.hxx" #include "AtariNTSC.hxx"
#define SCALE_FROM_100(x) ((x/50.0)-1.0) #define SCALE_FROM_100(x) ((x/50.0)-1.0)
#define SCALE_TO_100(x) uInt32(50*(x+1.0)) #define SCALE_TO_100(x) uInt32(50*(x+1.0))
@ -76,7 +76,7 @@ class NTSCFilter
// have changed) // have changed)
inline void updateFilter() inline void updateFilter()
{ {
atari_ntsc_init(&myFilter, &mySetup, myTIAPalette); myNTSC.initialize(mySetup, myTIAPalette);
} }
// Get adjustables for the given preset // Get adjustables for the given preset
@ -111,33 +111,25 @@ class NTSCFilter
inline void blit_single(uInt8* src_buf, uInt32 src_width, uInt32 src_height, inline void blit_single(uInt8* src_buf, uInt32 src_width, uInt32 src_height,
uInt32* dest_buf, uInt32 dest_pitch) uInt32* dest_buf, uInt32 dest_pitch)
{ {
atari_ntsc_blit_single(&myFilter, src_buf, src_width, src_height, myNTSC.blitSingle(src_buf, src_width, src_height, dest_buf, dest_pitch);
dest_buf, dest_pitch);
}
inline void blit_double(uInt8* src_buf, uInt8* src_back_buf,
uInt32 src_width, uInt32 src_height,
uInt32* dest_buf, uInt32 dest_pitch)
{
atari_ntsc_blit_double(&myFilter, src_buf, src_back_buf, src_width,
src_height, dest_buf, dest_pitch);
} }
private: private:
// Convert from atari_ntsc_setup_t values to equivalent adjustables // Convert from atari_ntsc_setup_t values to equivalent adjustables
void convertToAdjustable(Adjustable& adjustable, void convertToAdjustable(Adjustable& adjustable,
const atari_ntsc_setup_t& setup) const; const AtariNTSC::Setup& setup) const;
private: private:
// The NTSC filter structure // The NTSC object
atari_ntsc_t myFilter; AtariNTSC myNTSC;
// Contains controls used to adjust the palette in the NTSC filter // Contains controls used to adjust the palette in the NTSC filter
// This is the main setup object used by the underlying ntsc code // This is the main setup object used by the underlying ntsc code
atari_ntsc_setup_t mySetup; AtariNTSC::Setup mySetup;
// This setup is used only in custom mode (after it is modified, // This setup is used only in custom mode (after it is modified,
// it is copied to mySetup) // it is copied to mySetup)
static atari_ntsc_setup_t myCustomSetup; static AtariNTSC::Setup myCustomSetup;
// Current preset in use // Current preset in use
Preset myPreset; Preset myPreset;
@ -152,7 +144,7 @@ class NTSCFilter
// 128x128 in first bytes of array // 128x128 in first bytes of array
// 128 in last bytes of array // 128 in last bytes of array
// Each colour is represented by 3 bytes, in R,G,B order // Each colour is represented by 3 bytes, in R,G,B order
uInt8 myTIAPalette[atari_ntsc_palette_size * 3]; uInt8 myTIAPalette[AtariNTSC::palette_size * 3];
struct AdjustableTag { struct AdjustableTag {
const char* type; const char* type;

View File

@ -1,205 +0,0 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "atari_ntsc.hxx"
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
atari_ntsc_setup_t const atari_ntsc_composite = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.15, 0.0, 0.0, 0.0, 0 };
atari_ntsc_setup_t const atari_ntsc_svideo = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.45, -1.0, -1.0, 0.0, 0 };
atari_ntsc_setup_t const atari_ntsc_rgb = { 0.0, 0.0, 0.0, 0.0, 0.2, 0.0, 0.70, -1.0, -1.0, -1.0, 0 };
atari_ntsc_setup_t const atari_ntsc_bad = { 0.1, -0.3, 0.3, 0.25, 0.2, 0.0, 0.1, 0.5, 0.5, 0.5, 0 };
#define alignment_count 2
#define burst_count 1
#define rescale_in 8
#define rescale_out 7
#define artifacts_mid 1.5f
#define artifacts_max 2.5f
#define fringing_mid 1.0f
#define std_decoder_hue 0
#define gamma_size 256
#include "atari_ntsc_impl.hxx"
/* 2 input pixels -> 8 composite samples */
pixel_info_t const atari_ntsc_pixels [alignment_count] = {
{ PIXEL_OFFSET( -4, -9 ), { 1, 1, 1, 1 } },
{ PIXEL_OFFSET( 0, -5 ), { 1, 1, 1, 1 } },
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void correct_errors( atari_ntsc_rgb_t color, atari_ntsc_rgb_t* out )
{
for ( uInt32 i = 0; i < rgb_kernel_size / 2; i++ )
{
atari_ntsc_rgb_t error = color -
out [i ] - out [(i+10)%14+14] -
out [i + 7] - out [i + 3 +14];
CORRECT_ERROR( i + 3 + 14 );
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void atari_ntsc_init( atari_ntsc_t* ntsc, atari_ntsc_setup_t const* setup,
atari_ntsc_in_t const* palette )
{
init_t impl;
if ( !setup )
setup = &atari_ntsc_composite;
init( &impl, setup );
// Palette stores R/G/B data for 'atari_ntsc_palette_size' entries
for ( uInt32 entry = 0; entry < atari_ntsc_palette_size; ++entry )
{
float r = impl.to_float [*palette++];
float g = impl.to_float [*palette++];
float b = impl.to_float [*palette++];
float y, i, q = RGB_TO_YIQ( r, g, b, y, i );
// Generate kernel
int ir, ig, ib = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, ir, ig );
atari_ntsc_rgb_t rgb = PACK_RGB( ir, ig, ib );
if ( ntsc )
{
atari_ntsc_rgb_t* kernel = ntsc->table [entry];
gen_kernel( &impl, y, i, q, kernel );
correct_errors( rgb, kernel );
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void atari_ntsc_blit_single( atari_ntsc_t const* ntsc,
atari_ntsc_in_t const* atari_in, uInt32 in_width, uInt32 in_height,
void* rgb_out, uInt32 out_pitch )
{
uInt32 const chunk_count = (in_width - 1) / atari_ntsc_in_chunk;
while ( in_height-- )
{
atari_ntsc_in_t const* line_in = atari_in;
ATARI_NTSC_BEGIN_ROW( ntsc, atari_ntsc_black, line_in[0] );
atari_ntsc_out_t* restrict line_out = static_cast<atari_ntsc_out_t*>(rgb_out);
++line_in;
for ( uInt32 n = chunk_count; n; --n )
{
/* order of input and output pixels must not be altered */
ATARI_NTSC_COLOR_IN( 0, ntsc, line_in[0] );
ATARI_NTSC_RGB_OUT_8888( 0, line_out[0] );
ATARI_NTSC_RGB_OUT_8888( 1, line_out[1] );
ATARI_NTSC_RGB_OUT_8888( 2, line_out[2] );
ATARI_NTSC_RGB_OUT_8888( 3, line_out[3] );
ATARI_NTSC_COLOR_IN( 1, ntsc, line_in[1] );
ATARI_NTSC_RGB_OUT_8888( 4, line_out[4] );
ATARI_NTSC_RGB_OUT_8888( 5, line_out[5] );
ATARI_NTSC_RGB_OUT_8888( 6, line_out[6] );
line_in += 2;
line_out += 7;
}
/* finish final pixels */
ATARI_NTSC_COLOR_IN( 0, ntsc, atari_ntsc_black );
ATARI_NTSC_RGB_OUT_8888( 0, line_out[0] );
ATARI_NTSC_RGB_OUT_8888( 1, line_out[1] );
ATARI_NTSC_RGB_OUT_8888( 2, line_out[2] );
ATARI_NTSC_RGB_OUT_8888( 3, line_out[3] );
ATARI_NTSC_COLOR_IN( 1, ntsc, atari_ntsc_black );
ATARI_NTSC_RGB_OUT_8888( 4, line_out[4] );
ATARI_NTSC_RGB_OUT_8888( 5, line_out[5] );
ATARI_NTSC_RGB_OUT_8888( 6, line_out[6] );
atari_in += in_width;
rgb_out = static_cast<char*>(rgb_out) + out_pitch;
}
}
void atari_ntsc_blit_double( atari_ntsc_t const* ntsc,
atari_ntsc_in_t const* atari_in1, atari_ntsc_in_t const* atari_in2,
uInt32 in_width, uInt32 in_height, void* rgb_out, uInt32 out_pitch )
{
#define TO_DOUBLE(pixel1, pixel2) (((pixel1>>1)<<7)+(pixel2>>1))
uInt32 const chunk_count = (in_width - 1) / atari_ntsc_in_chunk;
while ( in_height-- )
{
atari_ntsc_in_t const* line_in1 = atari_in1;
atari_ntsc_in_t const* line_in2 = atari_in2;
ATARI_NTSC_BEGIN_ROW( ntsc,
TO_DOUBLE(atari_ntsc_black, atari_ntsc_black),
TO_DOUBLE(line_in1[0], line_in2[0]) );
atari_ntsc_out_t* restrict line_out = static_cast<atari_ntsc_out_t*>(rgb_out);
++line_in1;
++line_in2;
for ( uInt32 n = chunk_count; n; --n )
{
/* order of input and output pixels must not be altered */
ATARI_NTSC_COLOR_IN( 0, ntsc,
TO_DOUBLE(line_in1[0], line_in2[0]) );
ATARI_NTSC_RGB_OUT_8888( 0, line_out[0] );
ATARI_NTSC_RGB_OUT_8888( 1, line_out[1] );
ATARI_NTSC_RGB_OUT_8888( 2, line_out[2] );
ATARI_NTSC_RGB_OUT_8888( 3, line_out[3] );
ATARI_NTSC_COLOR_IN( 1, ntsc,
TO_DOUBLE(line_in1[1], line_in2[1]) );
ATARI_NTSC_RGB_OUT_8888( 4, line_out[4] );
ATARI_NTSC_RGB_OUT_8888( 5, line_out[5] );
ATARI_NTSC_RGB_OUT_8888( 6, line_out[6] );
line_in1 += 2;
line_in2 += 2;
line_out += 7;
}
/* finish final pixels */
ATARI_NTSC_COLOR_IN( 0, ntsc,
TO_DOUBLE(atari_ntsc_black, atari_ntsc_black) );
ATARI_NTSC_RGB_OUT_8888( 0, line_out[0] );
ATARI_NTSC_RGB_OUT_8888( 1, line_out[1] );
ATARI_NTSC_RGB_OUT_8888( 2, line_out[2] );
ATARI_NTSC_RGB_OUT_8888( 3, line_out[3] );
ATARI_NTSC_COLOR_IN( 1, ntsc,
TO_DOUBLE(atari_ntsc_black, atari_ntsc_black) );
ATARI_NTSC_RGB_OUT_8888( 4, line_out[4] );
ATARI_NTSC_RGB_OUT_8888( 5, line_out[5] );
ATARI_NTSC_RGB_OUT_8888( 6, line_out[6] );
atari_in1 += in_width;
atari_in2 += in_width;
rgb_out = static_cast<char*>(rgb_out) + out_pitch;
}
}

View File

@ -1,157 +0,0 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
/* Atari TIA, CTIA, GTIA and MARIA NTSC video filter */
#ifndef ATARI_NTSC_H
#define ATARI_NTSC_H
#include "bspf.hxx"
using atari_ntsc_in_t = uInt8;
using atari_ntsc_out_t = uInt32;
#ifdef __cplusplus
extern "C" {
#endif
/* Image parameters, ranging from -1.0 to 1.0. Actual internal values shown
in parenthesis and should remain fairly stable in future versions. */
struct atari_ntsc_setup_t
{
/* Basic parameters */
double hue; /* -1 = -180 degrees +1 = +180 degrees */
double saturation; /* -1 = grayscale (0.0) +1 = oversaturated colors (2.0) */
double contrast; /* -1 = dark (0.5) +1 = light (1.5) */
double brightness; /* -1 = dark (0.5) +1 = light (1.5) */
double sharpness; /* edge contrast enhancement/blurring */
/* Advanced parameters */
double gamma; /* -1 = dark (1.5) +1 = light (0.5) */
double resolution; /* image resolution */
double artifacts; /* artifacts caused by color changes */
double fringing; /* color artifacts caused by brightness changes */
double bleed; /* color bleed (color resolution reduction) */
float const* decoder_matrix; /* optional RGB decoder matrix, 6 elements */
};
/* Video format presets */
extern atari_ntsc_setup_t const atari_ntsc_composite; /* color bleeding + artifacts */
extern atari_ntsc_setup_t const atari_ntsc_svideo; /* color bleeding only */
extern atari_ntsc_setup_t const atari_ntsc_rgb; /* crisp image */
extern atari_ntsc_setup_t const atari_ntsc_bad; /* badly adjusted TV */
enum { atari_ntsc_palette_size = 256 };
/* Initializes and adjusts parameters. Can be called multiple times on the same
atari_ntsc_t object. Can pass NULL for either parameter. */
typedef struct atari_ntsc_t atari_ntsc_t;
void atari_ntsc_init( atari_ntsc_t* ntsc, atari_ntsc_setup_t const* setup,
atari_ntsc_in_t const* palette );
/* Filters one or more rows of pixels. Input pixels are 8-bit Atari palette colors.
In_row_width is the number of pixels to get to the next input row. Out_pitch
is the number of *bytes* to get to the next output row. */
void atari_ntsc_blit_single( atari_ntsc_t const* ntsc,
atari_ntsc_in_t const* atari_in, uInt32 in_width, uInt32 in_height,
void* rgb_out, uInt32 out_pitch );
void atari_ntsc_blit_double( atari_ntsc_t const* ntsc,
atari_ntsc_in_t const* atari_in1, atari_ntsc_in_t const* atari_in2,
uInt32 in_width, uInt32 in_height, void* rgb_out, uInt32 out_pitch );
/* Number of output pixels written by blitter for given input width. Width might
be rounded down slightly; use ATARI_NTSC_IN_WIDTH() on result to find rounded
value. Guaranteed not to round 160 down at all. */
#define ATARI_NTSC_OUT_WIDTH( in_width ) \
((((in_width) - 1) / atari_ntsc_in_chunk + 1)* atari_ntsc_out_chunk)
/* Number of input pixels that will fit within given output width. Might be
rounded down slightly; use ATARI_NTSC_OUT_WIDTH() on result to find rounded
value. */
#define ATARI_NTSC_IN_WIDTH( out_width ) \
(((out_width) / atari_ntsc_out_chunk - 1) * atari_ntsc_in_chunk + 1)
/* Interface for user-defined custom blitters. */
enum { atari_ntsc_in_chunk = 2 }; /* number of input pixels read per chunk */
enum { atari_ntsc_out_chunk = 7 }; /* number of output pixels generated per chunk */
enum { atari_ntsc_black = 0 }; /* palette index for black */
/* Begins outputting row and starts two pixels. First pixel will be cut off a bit.
Use atari_ntsc_black for unused pixels. Declares variables, so must be before first
statement in a block (unless you're using C++). */
#define ATARI_NTSC_BEGIN_ROW( ntsc, pixel0, pixel1 ) \
ATARI_NTSC_BEGIN_ROW_6_( pixel0, pixel1, ATARI_NTSC_ENTRY_, ntsc )
/* Begins input pixel */
#define ATARI_NTSC_COLOR_IN( in_index, ntsc, color_in ) \
ATARI_NTSC_COLOR_IN_( in_index, color_in, ATARI_NTSC_ENTRY_, ntsc )
/* Generates output in the specified 32-bit format (x = junk bits).
native: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format)
8888: 00000000 RRRRRRRR GGGGGGGG BBBBBBBB (8-8-8-8 32-bit ARGB)
*/
#define ATARI_NTSC_RGB_OUT_8888( index, rgb_out ) {\
atari_ntsc_rgb_t raw_ =\
kernel0 [index ] + kernel1 [(index+10)%7+14] +\
kernelx0 [(index+7)%14] + kernelx1 [(index+ 3)%7+14+7];\
ATARI_NTSC_CLAMP_( raw_, 0 );\
rgb_out = (raw_>>5 & 0x00FF0000)|(raw_>>3 & 0x0000FF00)|(raw_>>1 & 0x000000FF);\
}
/* private */
enum { atari_ntsc_entry_size = 2 * 14 };
using atari_ntsc_rgb_t = uInt32;
struct atari_ntsc_t {
atari_ntsc_rgb_t table [atari_ntsc_palette_size] [atari_ntsc_entry_size];
};
#define ATARI_NTSC_ENTRY_( ntsc, n ) (ntsc)->table [n]
/* common 3->7 ntsc macros */
#define ATARI_NTSC_BEGIN_ROW_6_( pixel0, pixel1, ENTRY, table ) \
unsigned const atari_ntsc_pixel0_ = (pixel0);\
atari_ntsc_rgb_t const* kernel0 = ENTRY( table, atari_ntsc_pixel0_ );\
unsigned const atari_ntsc_pixel1_ = (pixel1);\
atari_ntsc_rgb_t const* kernel1 = ENTRY( table, atari_ntsc_pixel1_ );\
atari_ntsc_rgb_t const* kernelx0;\
atari_ntsc_rgb_t const* kernelx1 = kernel0
/* common ntsc macros */
#define atari_ntsc_rgb_builder ((1 << 21) | (1 << 11) | (1 << 1))
#define atari_ntsc_clamp_mask (atari_ntsc_rgb_builder * 3 / 2)
#define atari_ntsc_clamp_add (atari_ntsc_rgb_builder * 0x101)
#define ATARI_NTSC_CLAMP_( io, shift ) {\
atari_ntsc_rgb_t sub = (io) >> (9-(shift)) & atari_ntsc_clamp_mask;\
atari_ntsc_rgb_t clamp = atari_ntsc_clamp_add - sub;\
io |= clamp;\
clamp -= sub;\
io &= clamp;\
}
#define ATARI_NTSC_COLOR_IN_( index, color, ENTRY, table ) {\
unsigned color_;\
kernelx##index = kernel##index;\
kernel##index = (color_ = (color), ENTRY( table, color_ ));\
}
#ifdef __cplusplus
}
#endif
#endif

View File

@ -1,432 +0,0 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
/* Based on nes_ntsc 0.2.2. http://www.slack.net/~ant/ */
/* Common implementation of NTSC filters */
#include <math.h>
/* Copyright (C) 2006-2009 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module 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 Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#ifndef DISABLE_CORRECTION
#define DISABLE_CORRECTION 0
#endif
#undef PI
#define PI 3.14159265358979323846f
#ifndef LUMA_CUTOFF
#define LUMA_CUTOFF 0.20
#endif
#ifndef gamma_size
#define gamma_size 1
#endif
#ifndef rgb_bits
#define rgb_bits 8
#endif
#ifndef artifacts_max
#define artifacts_max (artifacts_mid * 1.5f)
#endif
#ifndef fringing_max
#define fringing_max (fringing_mid * 2)
#endif
#ifndef STD_HUE_CONDITION
#define STD_HUE_CONDITION( setup ) 1
#endif
#define ext_decoder_hue (std_decoder_hue + 15)
#define rgb_unit (1 << rgb_bits)
#define rgb_offset (rgb_unit * 2 + 0.5f)
enum { burst_size = atari_ntsc_entry_size / burst_count };
enum { kernel_half = 16 };
enum { kernel_size = kernel_half * 2 + 1 };
typedef struct init_t
{
float to_rgb [burst_count * 6];
float to_float [gamma_size];
float contrast;
float brightness;
float artifacts;
float fringing;
float kernel [rescale_out * kernel_size * 2];
} init_t;
#define ROTATE_IQ( i, q, sin_b, cos_b ) {\
float t;\
t = i * cos_b - q * sin_b;\
q = i * sin_b + q * cos_b;\
i = t;\
}
static void init_filters( init_t* impl, atari_ntsc_setup_t const* setup )
{
#if rescale_out > 1
float kernels [kernel_size * 2];
#else
float* const kernels = impl->kernel;
#endif
/* generate luma (y) filter using sinc kernel */
{
/* sinc with rolloff (dsf) */
float const rolloff = 1 + float(setup->sharpness) * 0.032;
float const maxh = 32;
float const pow_a_n = float(pow( rolloff, maxh ));
float sum;
int i;
/* quadratic mapping to reduce negative (blurring) range */
float to_angle = float(setup->resolution) + 1;
to_angle = PI / maxh * float(LUMA_CUTOFF) * (to_angle * to_angle + 1);
kernels [kernel_size * 3 / 2] = maxh; /* default center value */
for ( i = 0; i < kernel_half * 2 + 1; i++ )
{
int x = i - kernel_half;
float angle = x * to_angle;
/* instability occurs at center point with rolloff very close to 1.0 */
if ( x || pow_a_n > 1.056 || pow_a_n < 0.981 )
{
float rolloff_cos_a = rolloff * float(cos( angle ));
float num = 1 - rolloff_cos_a -
pow_a_n * float(cos( maxh * angle )) +
pow_a_n * rolloff * float(cos( (maxh - 1) * angle ));
float den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
float dsf = num / den;
kernels [kernel_size * 3 / 2 - kernel_half + i] = dsf - 0.5;
}
}
/* apply blackman window and find sum */
sum = 0;
for ( i = 0; i < kernel_half * 2 + 1; i++ )
{
float x = PI * 2 / (kernel_half * 2) * i;
float blackman = 0.42f - 0.5f * float(cos( x )) + 0.08f * float(cos( x * 2 ));
sum += (kernels [kernel_size * 3 / 2 - kernel_half + i] *= blackman);
}
/* normalize kernel */
sum = 1.0f / sum;
for ( i = 0; i < kernel_half * 2 + 1; i++ )
{
int x = kernel_size * 3 / 2 - kernel_half + i;
kernels [x] *= sum;
}
}
/* generate chroma (iq) filter using gaussian kernel */
{
float const cutoff_factor = -0.03125f;
float cutoff = float(setup->bleed);
int i;
if ( cutoff < 0 )
{
/* keep extreme value accessible only near upper end of scale (1.0) */
cutoff *= cutoff;
cutoff *= cutoff;
cutoff *= cutoff;
cutoff *= -30.0f / 0.65f;
}
cutoff = cutoff_factor - 0.65f * cutoff_factor * cutoff;
for ( i = -kernel_half; i <= kernel_half; i++ )
kernels [kernel_size / 2 + i] = float(exp( i * i * cutoff ));
/* normalize even and odd phases separately */
for ( i = 0; i < 2; i++ )
{
float sum = 0;
int x;
for ( x = i; x < kernel_size; x += 2 )
sum += kernels [x];
sum = 1.0f / sum;
for ( x = i; x < kernel_size; x += 2 )
{
kernels [x] *= sum;
}
}
}
/*
printf( "luma:\n" );
for ( i = kernel_size; i < kernel_size * 2; i++ )
printf( "%f\n", kernels [i] );
printf( "chroma:\n" );
for ( i = 0; i < kernel_size; i++ )
printf( "%f\n", kernels [i] );
*/
/* generate linear rescale kernels */
#if rescale_out > 1
{
float weight = 1.0f;
float* out = impl->kernel;
int n = rescale_out;
do
{
float remain = 0;
int i;
weight -= 1.0f / rescale_in;
for ( i = 0; i < kernel_size * 2; i++ )
{
float cur = kernels [i];
float m = cur * weight;
*out++ = m + remain;
remain = cur - m;
}
}
while ( --n );
}
#endif
}
static float const default_decoder [6] =
{ 0.9563f, 0.6210f, -0.2721f, -0.6474f, -1.1070f, 1.7046f };
static void init( init_t* impl, atari_ntsc_setup_t const* setup )
{
impl->brightness = float(setup->brightness) * (0.5f * rgb_unit) + rgb_offset;
impl->contrast = float(setup->contrast) * (0.5f * rgb_unit) + rgb_unit;
impl->artifacts = float(setup->artifacts);
if ( impl->artifacts > 0 )
impl->artifacts *= artifacts_max - artifacts_mid;
impl->artifacts = impl->artifacts * artifacts_mid + artifacts_mid;
impl->fringing = float(setup->fringing);
if ( impl->fringing > 0 )
impl->fringing *= fringing_max - fringing_mid;
impl->fringing = impl->fringing * fringing_mid + fringing_mid;
init_filters( impl, setup );
/* generate gamma table */
if ( gamma_size > 1 )
{
float const to_float = 1.0f / (gamma_size - (gamma_size > 1));
float const gamma = 1.1333f - float(setup->gamma) * 0.5f;
/* match common PC's 2.2 gamma to TV's 2.65 gamma */
int i;
for ( i = 0; i < gamma_size; i++ )
impl->to_float [i] =
float(pow( i * to_float, gamma )) * impl->contrast + impl->brightness;
}
/* setup decoder matricies */
{
float hue = float(setup->hue) * PI + PI / 180 * ext_decoder_hue;
float sat = float(setup->saturation) + 1;
float const* decoder = setup->decoder_matrix;
if ( !decoder )
{
decoder = default_decoder;
if ( STD_HUE_CONDITION( setup ) )
hue += PI / 180 * (std_decoder_hue - ext_decoder_hue);
}
{
float s = float(sin( hue )) * sat;
float c = float(cos( hue )) * sat;
float* out = impl->to_rgb;
int n;
n = burst_count; // FIXME: dead code detected by llvm scan-build
do
{
float const* in = decoder;
int n2 = 3;
do
{
float i = *in++;
float q = *in++;
*out++ = i * c - q * s;
*out++ = i * s + q * c;
}
while ( --n2 );
if ( burst_count <= 1 )
break;
ROTATE_IQ( s, c, 0.866025f, -0.5f ); /* +120 degrees */
}
while ( --n );
}
}
}
/* kernel generation */
#define RGB_TO_YIQ( r, g, b, y, i ) (\
(y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\
(i = (r) * 0.595716f - (g) * 0.274453f - (b) * 0.321263f),\
((r) * 0.211456f - (g) * 0.522591f + (b) * 0.311135f)\
)
#define YIQ_TO_RGB( y, i, q, to_rgb, type, r, g ) (\
r = type(y + to_rgb [0] * i + to_rgb [1] * q),\
g = type(y + to_rgb [2] * i + to_rgb [3] * q),\
type(y + to_rgb [4] * i + to_rgb [5] * q)\
)
#ifndef PACK_RGB
#define PACK_RGB( r, g, b ) ((r) << 21 | (g) << 11 | (b) << 1)
#endif
enum { rgb_kernel_size = burst_size / alignment_count };
enum { rgb_bias = rgb_unit * 2 * atari_ntsc_rgb_builder };
typedef struct pixel_info_t
{
int offset;
float negate;
float kernel [4];
} pixel_info_t;
#if rescale_in > 1
#define PIXEL_OFFSET_( ntsc, scaled ) \
(kernel_size / 2 + ntsc + (scaled != 0) + (rescale_out - scaled) % rescale_out + \
(kernel_size * 2 * scaled))
#define PIXEL_OFFSET( ntsc, scaled ) \
PIXEL_OFFSET_( ((ntsc) - (scaled) / rescale_out * rescale_in),\
(((scaled) + rescale_out * 10) % rescale_out) ),\
(1.0f - (((ntsc) + 100) & 2))
#else
#define PIXEL_OFFSET( ntsc, scaled ) \
(kernel_size / 2 + (ntsc) - (scaled)),\
(1.0f - (((ntsc) + 100) & 2))
#endif
extern pixel_info_t const atari_ntsc_pixels [alignment_count];
/* Generate pixel at all burst phases and column alignments */
static void gen_kernel( init_t* impl, float y, float i, float q, atari_ntsc_rgb_t* out )
{
/* generate for each scanline burst phase */
float const* to_rgb = impl->to_rgb;
int burst_remain = burst_count;
y -= rgb_offset;
do
{
/* Encode yiq into *two* composite signals (to allow control over artifacting).
Convolve these with kernels which: filter respective components, apply
sharpening, and rescale horizontally. Convert resulting yiq to rgb and pack
into integer. Based on algorithm by NewRisingSun. */
pixel_info_t const* pixel = atari_ntsc_pixels;
int alignment_remain = alignment_count;
do
{
/* negate is -1 when composite starts at odd multiple of 2 */
float const yy = y * impl->fringing * pixel->negate;
float const ic0 = (i + yy) * pixel->kernel [0];
float const qc1 = (q + yy) * pixel->kernel [1];
float const ic2 = (i - yy) * pixel->kernel [2];
float const qc3 = (q - yy) * pixel->kernel [3];
float const factor = impl->artifacts * pixel->negate;
float const ii = i * factor;
float const yc0 = (y + ii) * pixel->kernel [0];
float const yc2 = (y - ii) * pixel->kernel [2];
float const qq = q * factor;
float const yc1 = (y + qq) * pixel->kernel [1];
float const yc3 = (y - qq) * pixel->kernel [3];
float const* k = &impl->kernel [pixel->offset];
int n;
++pixel;
for ( n = rgb_kernel_size; n; --n )
{
float fi = k[0]*ic0 + k[2]*ic2;
float fq = k[1]*qc1 + k[3]*qc3;
float fy = k[kernel_size+0]*yc0 + k[kernel_size+1]*yc1 +
k[kernel_size+2]*yc2 + k[kernel_size+3]*yc3 + rgb_offset;
if ( rescale_out <= 1 )
k--;
else if ( k < &impl->kernel [kernel_size * 2 * (rescale_out - 1)] )
k += kernel_size * 2 - 1;
else
k -= kernel_size * 2 * (rescale_out - 1) + 2;
{
int r, g, b = YIQ_TO_RGB( fy, fi, fq, to_rgb, int, r, g );
*out++ = PACK_RGB( r, g, b ) - rgb_bias;
}
}
}
while ( alignment_count > 1 && --alignment_remain );
if ( burst_count <= 1 )
break;
to_rgb += 6;
ROTATE_IQ( i, q, -0.866025f, -0.5f ); /* -120 degrees */
}
while ( --burst_remain );
}
static void correct_errors( atari_ntsc_rgb_t color, atari_ntsc_rgb_t* out );
#if DISABLE_CORRECTION
#define CORRECT_ERROR( a ) { out [i] += rgb_bias; }
#define DISTRIBUTE_ERROR( a, b, c ) { out [i] += rgb_bias; }
#else
#define CORRECT_ERROR( a ) { out [a] += error; }
#define DISTRIBUTE_ERROR( a, b, c ) {\
atari_ntsc_rgb_t fourth = (error + 2 * atari_ntsc_rgb_builder) >> 2;\
fourth &= (rgb_bias >> 1) - atari_ntsc_rgb_builder;\
fourth -= rgb_bias >> 2;\
out [a] += fourth;\
out [b] += fourth;\
out [c] += fourth;\
out [i] += error - (fourth * 3);\
}
#endif
#define RGB_PALETTE_OUT( rgb, out_ )\
{\
unsigned char* out = (out_);\
atari_ntsc_rgb_t clamped = (rgb);\
ATARI_NTSC_CLAMP_( clamped, (8 - rgb_bits) );\
out [0] = (unsigned char) (clamped >> 21);\
out [1] = (unsigned char) (clamped >> 11);\
out [2] = (unsigned char) (clamped >> 1);\
}
/* blitter related */
#ifndef restrict
#if defined (__GNUC__)
#define restrict __restrict__
#elif defined (_MSC_VER) && _MSC_VER > 1300
#define restrict __restrict
#else
/* no support for restricted pointers */
#define restrict
#endif
#endif

View File

@ -2,10 +2,10 @@ MODULE := src/common/tv_filters
MODULE_OBJS := \ MODULE_OBJS := \
src/common/tv_filters/NTSCFilter.o \ src/common/tv_filters/NTSCFilter.o \
src/common/tv_filters/atari_ntsc.o src/common/tv_filters/AtariNTSC.o
MODULE_DIRS += \ MODULE_DIRS += \
src/common/tv_filters src/common/tv_filters
# Include common rules # Include common rules
include $(srcdir)/common.rules include $(srcdir)/common.rules

View File

@ -40,7 +40,7 @@ TIASurface::TIASurface(OSystem& system)
myNTSCFilter.loadConfig(myOSystem.settings()); myNTSCFilter.loadConfig(myOSystem.settings());
// Create a surface for the TIA image and scanlines; we'll need them eventually // Create a surface for the TIA image and scanlines; we'll need them eventually
myTiaSurface = myFB.allocateSurface(ATARI_NTSC_OUT_WIDTH(kTIAW), kTIAH); myTiaSurface = myFB.allocateSurface(AtariNTSC::outWidth(kTIAW), kTIAH);
// Generate scanline data, and a pre-defined scanline surface // Generate scanline data, and a pre-defined scanline surface
uInt32 scanData[kScanH]; uInt32 scanData[kScanH];
@ -258,7 +258,7 @@ void TIASurface::enableNTSC(bool enable)
myFilterType = FilterType(enable ? myFilterType | 0x10 : myFilterType & 0x01); myFilterType = FilterType(enable ? myFilterType | 0x10 : myFilterType & 0x01);
// Normal vs NTSC mode uses different source widths // Normal vs NTSC mode uses different source widths
myTiaSurface->setSrcSize(enable ? ATARI_NTSC_OUT_WIDTH(160) : 160, myTIA->height()); myTiaSurface->setSrcSize(enable ? AtariNTSC::outWidth(160) : 160, myTIA->height());
FBSurface::Attributes& tia_attr = myTiaSurface->attributes(); FBSurface::Attributes& tia_attr = myTiaSurface->attributes();
tia_attr.smoothing = myOSystem.settings().getBool("tia.inter"); tia_attr.smoothing = myOSystem.settings().getBool("tia.inter");