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 | #include <thread> |
19 | #include "AtariNTSC.hxx" |
20 | |
21 | // blitter related |
22 | #ifndef restrict |
23 | #if defined (__GNUC__) |
24 | #define restrict __restrict__ |
25 | #elif defined (_MSC_VER) && _MSC_VER > 1300 |
26 | #define restrict __restrict |
27 | #else |
28 | /* no support for restricted pointers */ |
29 | #define restrict |
30 | #endif |
31 | #endif |
32 | |
33 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
34 | void AtariNTSC::initialize(const Setup& setup, const uInt8* palette) |
35 | { |
36 | init(myImpl, setup); |
37 | initializePalette(palette); |
38 | } |
39 | |
40 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
41 | void AtariNTSC::initializePalette(const uInt8* palette) |
42 | { |
43 | // Palette stores R/G/B data for 'palette_size' entries |
44 | for ( uInt32 entry = 0; entry < palette_size; ++entry ) |
45 | { |
46 | float r = myImpl.to_float [*palette++]; |
47 | float g = myImpl.to_float [*palette++]; |
48 | float b = myImpl.to_float [*palette++]; |
49 | |
50 | float y, i, q = RGB_TO_YIQ( r, g, b, y, i ); |
51 | |
52 | // Generate kernel |
53 | int ir, ig, ib = YIQ_TO_RGB( y, i, q, myImpl.to_rgb, int, ir, ig ); |
54 | uInt32 rgb = PACK_RGB( ir, ig, ib ); |
55 | |
56 | uInt32* kernel = myColorTable[entry]; |
57 | genKernel(myImpl, y, i, q, kernel); |
58 | |
59 | for ( uInt32 c = 0; c < rgb_kernel_size / 2; ++c ) |
60 | { |
61 | uInt32 error = rgb - |
62 | kernel [c ] - kernel [(c+10)%14+14] - |
63 | kernel [c + 7] - kernel [c + 3 +14]; |
64 | kernel [c + 3 + 14] += error; |
65 | } |
66 | } |
67 | } |
68 | |
69 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
70 | void AtariNTSC::enableThreading(bool enable) |
71 | { |
72 | uInt32 systemThreads = enable ? std::thread::hardware_concurrency() : 0; |
73 | if(systemThreads <= 1) |
74 | { |
75 | myWorkerThreads = 0; |
76 | myTotalThreads = 1; |
77 | } |
78 | else |
79 | { |
80 | systemThreads = std::max(1u, std::min(4u, systemThreads - 1)); |
81 | |
82 | myWorkerThreads = systemThreads - 1; |
83 | myTotalThreads = systemThreads; |
84 | |
85 | myThreads = make_unique<std::thread[]>(myWorkerThreads); |
86 | } |
87 | } |
88 | |
89 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
90 | void AtariNTSC::render(const uInt8* atari_in, const uInt32 in_width, const uInt32 in_height, |
91 | void* rgb_out, const uInt32 out_pitch, uInt32* rgb_in) |
92 | { |
93 | // Spawn the threads... |
94 | for(uInt32 i = 0; i < myWorkerThreads; ++i) |
95 | { |
96 | myThreads[i] = std::thread([=] { |
97 | rgb_in == nullptr ? |
98 | renderThread(atari_in, in_width, in_height, myTotalThreads, i+1, rgb_out, out_pitch) : |
99 | renderWithPhosphorThread(atari_in, in_width, in_height, myTotalThreads, i+1, rgb_in, rgb_out, out_pitch); |
100 | }); |
101 | } |
102 | // Make the main thread busy too |
103 | rgb_in == nullptr ? |
104 | renderThread(atari_in, in_width, in_height, myTotalThreads, 0, rgb_out, out_pitch) : |
105 | renderWithPhosphorThread(atari_in, in_width, in_height, myTotalThreads, 0, rgb_in, rgb_out, out_pitch); |
106 | // ...and make them join again |
107 | for(uInt32 i = 0; i < myWorkerThreads; ++i) |
108 | myThreads[i].join(); |
109 | |
110 | // Copy phosphor values into out buffer |
111 | if(rgb_in != nullptr) |
112 | memcpy(rgb_out, rgb_in, in_height * out_pitch); |
113 | } |
114 | |
115 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
116 | void AtariNTSC::renderThread(const uInt8* atari_in, const uInt32 in_width, |
117 | const uInt32 in_height, const uInt32 numThreads, const uInt32 threadNum, |
118 | void* rgb_out, const uInt32 out_pitch) |
119 | { |
120 | // Adapt parameters to thread number |
121 | const uInt32 yStart = in_height * threadNum / numThreads; |
122 | const uInt32 yEnd = in_height * (threadNum + 1) / numThreads; |
123 | atari_in += in_width * yStart; |
124 | rgb_out = static_cast<char*>(rgb_out) + out_pitch * yStart; |
125 | |
126 | uInt32 const chunk_count = (in_width - 1) / PIXEL_in_chunk; |
127 | |
128 | for(uInt32 y = yStart; y < yEnd; ++y) |
129 | { |
130 | const uInt8* line_in = atari_in; |
131 | ATARI_NTSC_BEGIN_ROW(NTSC_black, line_in[0]); |
132 | uInt32* restrict line_out = static_cast<uInt32*>(rgb_out); |
133 | ++line_in; |
134 | |
135 | // shift right by 2 pixel |
136 | line_out[0] = line_out[1] = 0; |
137 | line_out += 2; |
138 | |
139 | for(uInt32 n = chunk_count; n; --n) |
140 | { |
141 | // order of input and output pixels must not be altered |
142 | ATARI_NTSC_COLOR_IN(0, line_in[0]) |
143 | ATARI_NTSC_RGB_OUT_8888(0, line_out[0]) |
144 | ATARI_NTSC_RGB_OUT_8888(1, line_out[1]) |
145 | ATARI_NTSC_RGB_OUT_8888(2, line_out[2]) |
146 | ATARI_NTSC_RGB_OUT_8888(3, line_out[3]) |
147 | |
148 | ATARI_NTSC_COLOR_IN(1, line_in[1]) |
149 | ATARI_NTSC_RGB_OUT_8888(4, line_out[4]) |
150 | ATARI_NTSC_RGB_OUT_8888(5, line_out[5]) |
151 | ATARI_NTSC_RGB_OUT_8888(6, line_out[6]) |
152 | |
153 | line_in += 2; |
154 | line_out += 7; |
155 | } |
156 | |
157 | // finish final pixels |
158 | ATARI_NTSC_COLOR_IN(0, line_in[0]) |
159 | ATARI_NTSC_RGB_OUT_8888(0, line_out[0]) |
160 | ATARI_NTSC_RGB_OUT_8888(1, line_out[1]) |
161 | ATARI_NTSC_RGB_OUT_8888(2, line_out[2]) |
162 | ATARI_NTSC_RGB_OUT_8888(3, line_out[3]) |
163 | |
164 | ATARI_NTSC_COLOR_IN(1, NTSC_black) |
165 | ATARI_NTSC_RGB_OUT_8888(4, line_out[4]) |
166 | ATARI_NTSC_RGB_OUT_8888(5, line_out[5]) |
167 | ATARI_NTSC_RGB_OUT_8888(6, line_out[6]) |
168 | |
169 | line_in += 2; |
170 | line_out += 7; |
171 | |
172 | ATARI_NTSC_COLOR_IN(0, NTSC_black) |
173 | ATARI_NTSC_RGB_OUT_8888(0, line_out[0]) |
174 | ATARI_NTSC_RGB_OUT_8888(1, line_out[1]) |
175 | ATARI_NTSC_RGB_OUT_8888(2, line_out[2]) |
176 | ATARI_NTSC_RGB_OUT_8888(3, line_out[3]) |
177 | |
178 | ATARI_NTSC_COLOR_IN(1, NTSC_black) |
179 | ATARI_NTSC_RGB_OUT_8888(4, line_out[4]) |
180 | #if 0 |
181 | ATARI_NTSC_RGB_OUT_8888(5, line_out[5]) |
182 | ATARI_NTSC_RGB_OUT_8888(6, line_out[6]) |
183 | #endif |
184 | |
185 | atari_in += in_width; |
186 | rgb_out = static_cast<char*>(rgb_out) + out_pitch; |
187 | } |
188 | } |
189 | |
190 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
191 | void AtariNTSC::renderWithPhosphorThread(const uInt8* atari_in, const uInt32 in_width, |
192 | const uInt32 in_height, const uInt32 numThreads, const uInt32 threadNum, |
193 | uInt32* rgb_in, void* rgb_out, const uInt32 out_pitch) |
194 | { |
195 | // Adapt parameters to thread number |
196 | const uInt32 yStart = in_height * threadNum / numThreads; |
197 | const uInt32 yEnd = in_height * (threadNum + 1) / numThreads; |
198 | uInt32 bufofs = AtariNTSC::outWidth(in_width) * yStart; |
199 | uInt32* out = static_cast<uInt32*>(rgb_out); |
200 | atari_in += in_width * yStart; |
201 | rgb_out = static_cast<char*>(rgb_out) + out_pitch * yStart; |
202 | |
203 | uInt32 const chunk_count = (in_width - 1) / PIXEL_in_chunk; |
204 | |
205 | for(uInt32 y = yStart; y < yEnd; ++y) |
206 | { |
207 | const uInt8* line_in = atari_in; |
208 | ATARI_NTSC_BEGIN_ROW(NTSC_black, line_in[0]); |
209 | uInt32* restrict line_out = static_cast<uInt32*>(rgb_out); |
210 | ++line_in; |
211 | |
212 | // shift right by 2 pixel |
213 | line_out[0] = line_out[1] = 0; |
214 | line_out += 2; |
215 | |
216 | for(uInt32 n = chunk_count; n; --n) |
217 | { |
218 | // order of input and output pixels must not be altered |
219 | ATARI_NTSC_COLOR_IN(0, line_in[0]) |
220 | ATARI_NTSC_RGB_OUT_8888(0, line_out[0]) |
221 | ATARI_NTSC_RGB_OUT_8888(1, line_out[1]) |
222 | ATARI_NTSC_RGB_OUT_8888(2, line_out[2]) |
223 | ATARI_NTSC_RGB_OUT_8888(3, line_out[3]) |
224 | |
225 | ATARI_NTSC_COLOR_IN(1, line_in[1]) |
226 | ATARI_NTSC_RGB_OUT_8888(4, line_out[4]) |
227 | ATARI_NTSC_RGB_OUT_8888(5, line_out[5]) |
228 | ATARI_NTSC_RGB_OUT_8888(6, line_out[6]) |
229 | |
230 | line_in += 2; |
231 | line_out += 7; |
232 | } |
233 | |
234 | // finish final pixels |
235 | ATARI_NTSC_COLOR_IN(0, line_in[0]) |
236 | ATARI_NTSC_RGB_OUT_8888(0, line_out[0]) |
237 | ATARI_NTSC_RGB_OUT_8888(1, line_out[1]) |
238 | ATARI_NTSC_RGB_OUT_8888(2, line_out[2]) |
239 | ATARI_NTSC_RGB_OUT_8888(3, line_out[3]) |
240 | |
241 | ATARI_NTSC_COLOR_IN(1, NTSC_black) |
242 | ATARI_NTSC_RGB_OUT_8888(4, line_out[4]) |
243 | ATARI_NTSC_RGB_OUT_8888(5, line_out[5]) |
244 | ATARI_NTSC_RGB_OUT_8888(6, line_out[6]) |
245 | |
246 | line_in += 2; |
247 | line_out += 7; |
248 | |
249 | ATARI_NTSC_COLOR_IN(0, NTSC_black) |
250 | ATARI_NTSC_RGB_OUT_8888(0, line_out[0]) |
251 | ATARI_NTSC_RGB_OUT_8888(1, line_out[1]) |
252 | ATARI_NTSC_RGB_OUT_8888(2, line_out[2]) |
253 | ATARI_NTSC_RGB_OUT_8888(3, line_out[3]) |
254 | |
255 | ATARI_NTSC_COLOR_IN(1, NTSC_black) |
256 | ATARI_NTSC_RGB_OUT_8888(4, line_out[4]) |
257 | #if 0 |
258 | ATARI_NTSC_RGB_OUT_8888(5, line_out[5]) |
259 | ATARI_NTSC_RGB_OUT_8888(6, line_out[6]) |
260 | #endif |
261 | |
262 | // Do phosphor mode (blend the resulting frames) |
263 | // Note: The unrolled code assumed that AtariNTSC::outWidth(kTIAW) == outPitch == 565 |
264 | // Now this got changed to 568 so he final 5 calculations got removed. |
265 | for (uInt32 x = AtariNTSC::outWidth(in_width) / 8; x; --x) |
266 | { |
267 | // Store back into displayed frame buffer (for next frame) |
268 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
269 | ++bufofs; |
270 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
271 | ++bufofs; |
272 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
273 | ++bufofs; |
274 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
275 | ++bufofs; |
276 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
277 | ++bufofs; |
278 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
279 | ++bufofs; |
280 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
281 | ++bufofs; |
282 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
283 | ++bufofs; |
284 | } |
285 | // finish final 565 % 8 = 5 pixels |
286 | /*rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
287 | ++bufofs; |
288 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
289 | ++bufofs; |
290 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
291 | ++bufofs; |
292 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
293 | ++bufofs; |
294 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
295 | ++bufofs;*/ |
296 | #if 0 |
297 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
298 | ++bufofs; |
299 | rgb_in[bufofs] = getRGBPhosphor(out[bufofs], rgb_in[bufofs]); |
300 | ++bufofs; |
301 | #endif |
302 | |
303 | atari_in += in_width; |
304 | rgb_out = static_cast<char*>(rgb_out) + out_pitch; |
305 | } |
306 | } |
307 | |
308 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
309 | inline uInt32 AtariNTSC::getRGBPhosphor(const uInt32 c, const uInt32 p) const |
310 | { |
311 | #define TO_RGB(color, red, green, blue) \ |
312 | const uInt8 red = uInt8(color >> 16); \ |
313 | const uInt8 green = uInt8(color >> 8);\ |
314 | const uInt8 blue = uInt8(color); |
315 | |
316 | TO_RGB(c, rc, gc, bc) |
317 | TO_RGB(p, rp, gp, bp) |
318 | |
319 | // Mix current calculated frame with previous displayed frame |
320 | const uInt8 rn = myPhosphorPalette[rc][rp]; |
321 | const uInt8 gn = myPhosphorPalette[gc][gp]; |
322 | const uInt8 bn = myPhosphorPalette[bc][bp]; |
323 | |
324 | return (rn << 16) | (gn << 8) | bn; |
325 | } |
326 | |
327 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
328 | void AtariNTSC::init(init_t& impl, const Setup& setup) |
329 | { |
330 | impl.brightness = setup.brightness * (0.5f * rgb_unit) + rgb_offset; |
331 | impl.contrast = setup.contrast * (0.5f * rgb_unit) + rgb_unit; |
332 | |
333 | impl.artifacts = setup.artifacts; |
334 | if ( impl.artifacts > 0 ) |
335 | impl.artifacts *= artifacts_max - artifacts_mid; |
336 | impl.artifacts = impl.artifacts * artifacts_mid + artifacts_mid; |
337 | |
338 | impl.fringing = setup.fringing; |
339 | if ( impl.fringing > 0 ) |
340 | impl.fringing *= fringing_max - fringing_mid; |
341 | impl.fringing = impl.fringing * fringing_mid + fringing_mid; |
342 | |
343 | initFilters(impl, setup); |
344 | |
345 | /* generate gamma table */ |
346 | if (true) /* was (gamma_size > 1) */ |
347 | { |
348 | float const to_float = 1.0f / (gamma_size - 1/*(gamma_size > 1)*/); |
349 | float const gamma = 1.1333f - setup.gamma * 0.5f; |
350 | /* match common PC's 2.2 gamma to TV's 2.65 gamma */ |
351 | int i; |
352 | for ( i = 0; i < gamma_size; i++ ) |
353 | impl.to_float [i] = |
354 | powf( i * to_float, gamma ) * impl.contrast + impl.brightness; |
355 | } |
356 | |
357 | /* setup decoder matricies */ |
358 | { |
359 | float hue = setup.hue * BSPF::PI_f + BSPF::PI_f / 180 * ext_decoder_hue; |
360 | float sat = setup.saturation + 1; |
361 | hue += BSPF::PI_f / 180 * (std_decoder_hue - ext_decoder_hue); |
362 | |
363 | float s = sinf( hue ) * sat; |
364 | float c = cosf( hue ) * sat; |
365 | float* out = impl.to_rgb; |
366 | int n; |
367 | |
368 | n = burst_count; |
369 | do |
370 | { |
371 | float const* in = default_decoder; |
372 | int n2 = 3; |
373 | do |
374 | { |
375 | float i = *in++; |
376 | float q = *in++; |
377 | *out++ = i * c - q * s; |
378 | *out++ = i * s + q * c; |
379 | } |
380 | while ( --n2 ); |
381 | #if 0 // burst_count is always 0 |
382 | if ( burst_count > 1 ) |
383 | ROTATE_IQ( s, c, 0.866025f, -0.5f ); /* +120 degrees */ |
384 | #endif |
385 | } |
386 | while ( --n ); |
387 | } |
388 | } |
389 | |
390 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
391 | void AtariNTSC::initFilters(init_t& impl, const Setup& setup) |
392 | { |
393 | float kernels [kernel_size * 2]; |
394 | |
395 | /* generate luma (y) filter using sinc kernel */ |
396 | { |
397 | /* sinc with rolloff (dsf) */ |
398 | float const rolloff = 1 + setup.sharpness * 0.032f; |
399 | constexpr float maxh = 32; |
400 | float const pow_a_n = powf( rolloff, maxh ); |
401 | float sum; |
402 | /* quadratic mapping to reduce negative (blurring) range */ |
403 | float to_angle = setup.resolution + 1; |
404 | to_angle = BSPF::PI_f / maxh * LUMA_CUTOFF * (to_angle * to_angle + 1.f); |
405 | |
406 | kernels [kernel_size * 3 / 2] = maxh; /* default center value */ |
407 | for ( int i = 0; i < kernel_half * 2 + 1; i++ ) |
408 | { |
409 | int x = i - kernel_half; |
410 | float angle = x * to_angle; |
411 | /* instability occurs at center point with rolloff very close to 1.0 */ |
412 | if ( x || pow_a_n > 1.056f || pow_a_n < 0.981f ) |
413 | { |
414 | float rolloff_cos_a = rolloff * cosf( angle ); |
415 | float num = 1 - rolloff_cos_a - |
416 | pow_a_n * cosf( maxh * angle ) + |
417 | pow_a_n * rolloff * cosf( (maxh - 1) * angle ); |
418 | float den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff; |
419 | float dsf = num / den; |
420 | kernels [kernel_size * 3 / 2 - kernel_half + i] = dsf - 0.5f; |
421 | } |
422 | } |
423 | |
424 | /* apply blackman window and find sum */ |
425 | sum = 0; |
426 | for ( int i = 0; i < kernel_half * 2 + 1; i++ ) |
427 | { |
428 | float x = BSPF::PI_f * 2 / (kernel_half * 2) * i; |
429 | float blackman = 0.42f - 0.5f * cosf( x ) + 0.08f * cosf( x * 2 ); |
430 | sum += (kernels [kernel_size * 3 / 2 - kernel_half + i] *= blackman); |
431 | } |
432 | |
433 | /* normalize kernel */ |
434 | sum = 1.0f / sum; |
435 | for ( int i = 0; i < kernel_half * 2 + 1; i++ ) |
436 | { |
437 | int x = kernel_size * 3 / 2 - kernel_half + i; |
438 | kernels [x] *= sum; |
439 | } |
440 | } |
441 | |
442 | /* generate chroma (iq) filter using gaussian kernel */ |
443 | { |
444 | constexpr float cutoff_factor = -0.03125f; |
445 | float cutoff = setup.bleed; |
446 | |
447 | if ( cutoff < 0 ) |
448 | { |
449 | /* keep extreme value accessible only near upper end of scale (1.0) */ |
450 | cutoff *= cutoff; |
451 | cutoff *= cutoff; |
452 | cutoff *= cutoff; |
453 | cutoff *= -30.0f / 0.65f; |
454 | } |
455 | cutoff = cutoff_factor - 0.65f * cutoff_factor * cutoff; |
456 | |
457 | for ( int i = -kernel_half; i <= kernel_half; i++ ) |
458 | kernels [kernel_size / 2 + i] = expf( i * i * cutoff ); |
459 | |
460 | /* normalize even and odd phases separately */ |
461 | for ( int i = 0; i < 2; i++ ) |
462 | { |
463 | float sum = 0; |
464 | int x; |
465 | for ( x = i; x < kernel_size; x += 2 ) |
466 | sum += kernels [x]; |
467 | |
468 | sum = 1.0f / sum; |
469 | for ( x = i; x < kernel_size; x += 2 ) |
470 | { |
471 | kernels [x] *= sum; |
472 | } |
473 | } |
474 | } |
475 | |
476 | /* generate linear rescale kernels */ |
477 | float weight = 1.0f; |
478 | float* out = impl.kernel; |
479 | int n = rescale_out; |
480 | do |
481 | { |
482 | float remain = 0; |
483 | weight -= 1.0f / rescale_in; |
484 | for ( int i = 0; i < kernel_size * 2; i++ ) |
485 | { |
486 | float cur = kernels [i]; |
487 | float m = cur * weight; |
488 | *out++ = m + remain; |
489 | remain = cur - m; |
490 | } |
491 | } |
492 | while ( --n ); |
493 | } |
494 | |
495 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
496 | // Generate pixel at all burst phases and column alignments |
497 | void AtariNTSC::genKernel(init_t& impl, float y, float i, float q, uInt32* out) |
498 | { |
499 | /* generate for each scanline burst phase */ |
500 | float const* to_rgb = impl.to_rgb; |
501 | int burst_remain = burst_count; |
502 | y -= rgb_offset; |
503 | do |
504 | { |
505 | /* Encode yiq into *two* composite signals (to allow control over artifacting). |
506 | Convolve these with kernels which: filter respective components, apply |
507 | sharpening, and rescale horizontally. Convert resulting yiq to rgb and pack |
508 | into integer. Based on algorithm by NewRisingSun. */ |
509 | pixel_info_t const* pixel = atari_ntsc_pixels; |
510 | int alignment_remain = alignment_count; |
511 | do |
512 | { |
513 | /* negate is -1 when composite starts at odd multiple of 2 */ |
514 | float const yy = y * impl.fringing * pixel->negate; |
515 | float const ic0 = (i + yy) * pixel->kernel [0]; |
516 | float const qc1 = (q + yy) * pixel->kernel [1]; |
517 | float const ic2 = (i - yy) * pixel->kernel [2]; |
518 | float const qc3 = (q - yy) * pixel->kernel [3]; |
519 | |
520 | float const factor = impl.artifacts * pixel->negate; |
521 | float const ii = i * factor; |
522 | float const yc0 = (y + ii) * pixel->kernel [0]; |
523 | float const yc2 = (y - ii) * pixel->kernel [2]; |
524 | |
525 | float const qq = q * factor; |
526 | float const yc1 = (y + qq) * pixel->kernel [1]; |
527 | float const yc3 = (y - qq) * pixel->kernel [3]; |
528 | |
529 | float const* k = &impl.kernel [pixel->offset]; |
530 | int n; |
531 | ++pixel; |
532 | for ( n = rgb_kernel_size; n; --n ) |
533 | { |
534 | float fi = k[0]*ic0 + k[2]*ic2; |
535 | float fq = k[1]*qc1 + k[3]*qc3; |
536 | float fy = k[kernel_size+0]*yc0 + k[kernel_size+1]*yc1 + |
537 | k[kernel_size+2]*yc2 + k[kernel_size+3]*yc3 + rgb_offset; |
538 | if ( k < &impl.kernel [kernel_size * 2 * (rescale_out - 1)] ) |
539 | k += kernel_size * 2 - 1; |
540 | else |
541 | k -= kernel_size * 2 * (rescale_out - 1) + 2; |
542 | { |
543 | int r, g, b = YIQ_TO_RGB( fy, fi, fq, to_rgb, int, r, g ); |
544 | *out++ = PACK_RGB( r, g, b ) - rgb_bias; |
545 | } |
546 | } |
547 | } |
548 | while ( /*alignment_count > 1 && */ --alignment_remain ); |
549 | } |
550 | while ( --burst_remain ); |
551 | } |
552 | |
553 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
554 | const AtariNTSC::Setup AtariNTSC::TV_Composite = { |
555 | 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.15f, 0.0f, 0.0f, 0.0f |
556 | }; |
557 | const AtariNTSC::Setup AtariNTSC::TV_SVideo = { |
558 | 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.45f, -1.0f, -1.0f, 0.0f |
559 | }; |
560 | const AtariNTSC::Setup AtariNTSC::TV_RGB = { |
561 | 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.70f, -1.0f, -1.0f, -1.0f |
562 | }; |
563 | const AtariNTSC::Setup AtariNTSC::TV_Bad = { |
564 | 0.1f, -0.3f, 0.3f, 0.25f, 0.2f, 0.0f, 0.1f, 0.5f, 0.5f, 0.5f |
565 | }; |
566 | |
567 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
568 | const AtariNTSC::pixel_info_t AtariNTSC::atari_ntsc_pixels[alignment_count] = { |
569 | { PIXEL_OFFSET( -4, -9 ), { 1, 1, 1, 1 } }, |
570 | { PIXEL_OFFSET( 0, -5 ), { 1, 1, 1, 1 } }, |
571 | }; |
572 | |
573 | const float AtariNTSC::default_decoder[6] = { |
574 | 0.9563f, 0.6210f, -0.2721f, -0.6474f, -1.1070f, 1.7046f |
575 | }; |
576 | |