| 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 | |