1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18/*
19 * Atari TIA NTSC video filter
20 * Based on nes_ntsc 0.2.2. http://www.slack.net/~ant
21 *
22 * Copyright (C) 2006-2009 Shay Green. This module is free software; you
23 * can redistribute it and/or modify it under the terms of the GNU Lesser
24 * General Public License as published by the Free Software Foundation; either
25 * version 2.1 of the License, or (at your option) any later version. This
26 * module is distributed in the hope that it will be useful, but WITHOUT ANY
27 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
28 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
29 * details. You should have received a copy of the GNU Lesser General Public
30 * License along with this module; if not, write to the Free Software Foundation,
31 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
32 */
33
34/**
35 The class is basically a thin wrapper around atari_ntsc_xxx structs
36 and methods, so that the rest of the codebase isn't affected by
37 updated versions of Blargg code.
38*/
39
40#ifndef ATARI_NTSC_HXX
41#define ATARI_NTSC_HXX
42
43#include <cmath>
44#include <thread>
45
46#include "bspf.hxx"
47
48class AtariNTSC
49{
50 public:
51 static constexpr uInt32 palette_size = 256, entry_size = 2 * 14;
52
53 // By default, threading is turned off
54 AtariNTSC() { enableThreading(false); }
55
56 // Image parameters, ranging from -1.0 to 1.0. Actual internal values shown
57 // in parenthesis and should remain fairly stable in future versions.
58 struct Setup
59 {
60 // Basic parameters
61 float hue; // -1 = -180 degrees +1 = +180 degrees
62 float saturation; // -1 = grayscale (0.0) +1 = oversaturated colors (2.0)
63 float contrast; // -1 = dark (0.5) +1 = light (1.5)
64 float brightness; // -1 = dark (0.5) +1 = light (1.5)
65 float sharpness; // edge contrast enhancement/blurring
66
67 // Advanced parameters
68 float gamma; // -1 = dark (1.5) +1 = light (0.5)
69 float resolution; // image resolution
70 float artifacts; // artifacts caused by color changes
71 float fringing; // color artifacts caused by brightness changes
72 float bleed; // color bleed (color resolution reduction)
73 };
74
75 // Video format presets
76 static const Setup TV_Composite; // color bleeding + artifacts
77 static const Setup TV_SVideo; // color bleeding only
78 static const Setup TV_RGB; // crisp image
79 static const Setup TV_Bad; // badly adjusted TV
80
81 // Initializes and adjusts parameters.
82 void initialize(const Setup& setup, const uInt8* palette);
83 void initializePalette(const uInt8* palette);
84
85 // Set up threading
86 void enableThreading(bool enable);
87
88 // Set phosphor palette, for use in Blargg + phosphor mode
89 void setPhosphorPalette(uInt8 palette[256][256]) {
90 memcpy(myPhosphorPalette, palette, 256 * 256);
91 }
92
93 // Filters one or more rows of pixels. Input pixels are 8-bit Atari
94 // palette colors.
95 // In_row_width is the number of pixels to get to the next input row.
96 // Out_pitch is the number of *bytes* to get to the next output row.
97 void render(const uInt8* atari_in, const uInt32 in_width, const uInt32 in_height,
98 void* rgb_out, const uInt32 out_pitch, uInt32* rgb_in = nullptr);
99
100 // Number of input pixels that will fit within given output width.
101 // Might be rounded down slightly; use outWidth() on result to find
102 // rounded value.
103 /*static constexpr uInt32 inWidth( uInt32 out_width ) {
104 return (((out_width - 8) / PIXEL_out_chunk - 1) * PIXEL_in_chunk + 1);
105 }*/
106
107 // Number of output pixels written by blitter for given input width.
108 // Width might be rounded down slightly; use inWidth() on result to
109 // find rounded value. Guaranteed not to round 160 down at all.
110 static constexpr uInt32 outWidth(uInt32 in_width) {
111 return ((((in_width) - 1) / PIXEL_in_chunk + 1)* PIXEL_out_chunk) + 8;
112 }
113
114 private:
115 // Threaded rendering
116 void renderThread(const uInt8* atari_in, const uInt32 in_width,
117 const uInt32 in_height, const uInt32 numThreads, const uInt32 threadNum, void* rgb_out, const uInt32 out_pitch);
118 void renderWithPhosphorThread(const uInt8* atari_in, const uInt32 in_width,
119 const uInt32 in_height, const uInt32 numThreads, const uInt32 threadNum, uInt32* rgb_in, void* rgb_out, const uInt32 out_pitch);
120
121 /**
122 Used to calculate an averaged color for the 'phosphor' effect.
123
124 @param c RGB Color 1 (current frame)
125 @param cp RGB Color 2 (previous frame)
126
127 @return Averaged value of the two RGB colors
128 */
129 uInt32 getRGBPhosphor(const uInt32 c, const uInt32 cp) const;
130
131 private:
132 static constexpr Int32
133 PIXEL_in_chunk = 2, // number of input pixels read per chunk
134 PIXEL_out_chunk = 7, // number of output pixels generated per chunk
135 NTSC_black = 0, // palette index for black
136
137 alignment_count = 2,
138 burst_count = 1,
139 rescale_in = 8,
140 rescale_out = 7,
141
142 burst_size = entry_size / burst_count,
143 kernel_half = 16,
144 kernel_size = kernel_half * 2 + 1,
145 gamma_size = 256,
146
147 rgb_builder = ((1 << 21) | (1 << 11) | (1 << 1)),
148 rgb_kernel_size = burst_size / alignment_count,
149 rgb_bits = 8,
150 rgb_unit = (1 << rgb_bits),
151 rgb_bias = rgb_unit * 2 * rgb_builder,
152
153 std_decoder_hue = 0,
154 ext_decoder_hue = std_decoder_hue + 15
155 ;
156
157 #define artifacts_mid 1.5f
158 #define artifacts_max 2.5f
159 #define fringing_mid 1.0f
160 #define fringing_max 2.0f
161 #define rgb_offset (rgb_unit * 2 + 0.5f)
162
163 #define LUMA_CUTOFF 0.20f
164
165 uInt32 myColorTable[palette_size][entry_size];
166 uInt8 myPhosphorPalette[256][256];
167
168 // Rendering threads
169 unique_ptr<std::thread[]> myThreads;
170 // Number of rendering and total threads
171 uInt32 myWorkerThreads, myTotalThreads;
172
173 struct init_t
174 {
175 float to_rgb [burst_count * 6];
176 float to_float [gamma_size];
177 float contrast;
178 float brightness;
179 float artifacts;
180 float fringing;
181 float kernel [rescale_out * kernel_size * 2];
182
183 init_t() : contrast(0.0), brightness(0.0), artifacts(0.0), fringing(0.0) {
184 std::fill(to_rgb, to_rgb + burst_count * 6, 0.0);
185 std::fill(to_float, to_float + gamma_size, 0.0);
186 std::fill(kernel, kernel + rescale_out * kernel_size * 2, 0.0);
187 }
188 };
189 init_t myImpl;
190
191 struct pixel_info_t
192 {
193 int offset;
194 float negate;
195 float kernel [4];
196 };
197 static const pixel_info_t atari_ntsc_pixels[alignment_count];
198
199 static const float default_decoder[6];
200
201 void init(init_t& impl, const Setup& setup);
202 void initFilters(init_t& impl, const Setup& setup);
203 // Generate pixel at all burst phases and column alignments
204 void genKernel(init_t& impl, float y, float i, float q, uInt32* out);
205
206 // Begins outputting row and starts two pixels. First pixel will be cut
207 // off a bit. Use atari_ntsc_black for unused pixels.
208 #define ATARI_NTSC_BEGIN_ROW( pixel0, pixel1 ) \
209 unsigned const atari_ntsc_pixel0_ = (pixel0);\
210 uInt32 const* kernel0 = myColorTable[atari_ntsc_pixel0_];\
211 unsigned const atari_ntsc_pixel1_ = (pixel1);\
212 uInt32 const* kernel1 = myColorTable[atari_ntsc_pixel1_];\
213 uInt32 const* kernelx0;\
214 uInt32 const* kernelx1 = kernel0
215
216 // Begins input pixel
217 #define ATARI_NTSC_COLOR_IN( index, color ) {\
218 unsigned color_;\
219 kernelx##index = kernel##index;\
220 kernel##index = (color_ = (color), myColorTable[color_]);\
221 }
222
223 // Generates output in the specified 32-bit format (x = junk bits).
224 // native: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format)
225 // 8888: 00000000 RRRRRRRR GGGGGGGG BBBBBBBB (8-8-8-8 32-bit ARGB)
226 #define ATARI_NTSC_RGB_OUT_8888( index, rgb_out ) {\
227 uInt32 raw_ =\
228 kernel0 [index ] + kernel1 [(index+10)%7+14] +\
229 kernelx0 [(index+7)%14] + kernelx1 [(index+ 3)%7+14+7];\
230 ATARI_NTSC_CLAMP_( raw_, 0 );\
231 rgb_out = (raw_>>5 & 0x00FF0000)|(raw_>>3 & 0x0000FF00)|(raw_>>1 & 0x000000FF);\
232 }
233
234 // Common ntsc macros
235 #define atari_ntsc_clamp_mask (rgb_builder * 3 / 2)
236 #define atari_ntsc_clamp_add (rgb_builder * 0x101)
237 #define ATARI_NTSC_CLAMP_( io, shift ) {\
238 uInt32 sub = (io) >> (9-(shift)) & atari_ntsc_clamp_mask;\
239 uInt32 clamp = atari_ntsc_clamp_add - sub;\
240 io |= clamp;\
241 clamp -= sub;\
242 io &= clamp;\
243 }
244
245 // Kernel generation
246 #define ROTATE_IQ( i, q, sin_b, cos_b ) {\
247 float t;\
248 t = i * cos_b - q * sin_b;\
249 q = i * sin_b + q * cos_b;\
250 i = t;\
251 }
252 #define RGB_TO_YIQ( r, g, b, y, i ) (\
253 (y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\
254 (i = (r) * 0.595716f - (g) * 0.274453f - (b) * 0.321263f),\
255 ((r) * 0.211456f - (g) * 0.522591f + (b) * 0.311135f)\
256 )
257 #define YIQ_TO_RGB( y, i, q, to_rgb, type, r, g ) (\
258 r = type(y + to_rgb [0] * i + to_rgb [1] * q),\
259 g = type(y + to_rgb [2] * i + to_rgb [3] * q),\
260 type(y + to_rgb [4] * i + to_rgb [5] * q)\
261 )
262 #ifndef PACK_RGB
263 #define PACK_RGB( r, g, b ) ((r) << 21 | (g) << 11 | (b) << 1)
264 #endif
265
266 #define PIXEL_OFFSET_( ntsc, scaled ) \
267 (kernel_size / 2 + ntsc + (scaled != 0) + (rescale_out - scaled) % rescale_out + \
268 (kernel_size * 2 * scaled))
269
270 #define PIXEL_OFFSET( ntsc, scaled ) \
271 PIXEL_OFFSET_( ((ntsc) - (scaled) / rescale_out * rescale_in),\
272 (((scaled) + rescale_out * 10) % rescale_out) ),\
273 (1.0f - (((ntsc) + 100) & 2))
274
275 #define DISTRIBUTE_ERROR( a, b, c ) {\
276 uInt32 fourth = (error + 2 * rgb_builder) >> 2;\
277 fourth &= (rgb_bias >> 1) - rgb_builder;\
278 fourth -= rgb_bias >> 2;\
279 out [a] += fourth;\
280 out [b] += fourth;\
281 out [c] += fourth;\
282 out [i] += error - (fourth * 3);\
283 }
284
285 #define RGB_PALETTE_OUT( rgb, out_ )\
286 {\
287 unsigned char* out = (out_);\
288 uInt32 clamped = (rgb);\
289 ATARI_NTSC_CLAMP_( clamped, (8 - rgb_bits) );\
290 out [0] = (unsigned char) (clamped >> 21);\
291 out [1] = (unsigned char) (clamped >> 11);\
292 out [2] = (unsigned char) (clamped >> 1);\
293 }
294};
295
296#endif
297