nes_ntsc 0.2.2: NES NTSC Video Filter ------------------------------------- Author : Shay Green <gblargg@gmail.com> Website : http://www.slack.net/~ant/ Forum : http://groups.google.com/group/blargg-sound-libs License : GNU Lesser General Public License (LGPL) Language: C or C++ Overview -------- To perform NTSC filtering, first allocate memory for a nes_ntsc_t object and call nes_ntsc_init(), then call nes_ntsc_blit() to perform filtering. You can call nes_ntsc_init() at any time to change image parameters. nes_ntsc_blit() reads NES pixels and writes RGB pixels (16-bit by default). The NES pixels are 6-bit raw palette values (0 to 0x3F). Edit nes_ntsc_config.h to change this. Color Emphasis Support ---------------------- Support for the three color emphasis bits in PPU register $2001 can be enabled in nes_ntsc_config.h. By default, support is disabled and a 64-color palette is used. If enabled, a 512-color palette is used and the three color emphasis bits from PPU $2001 are stored in bits 6-8 (bit mask 0x1C0) of the input pixels to the blitter. Form the index as follows: index = (ppu_2001 << 1 & 0x1C0) | (palette_index & 0x3F). RGB Palettes ------------ An RGB palette can be generated for use in a normal blitter. If color emphasis is disabled, it's 64 colors (192 bytes); if enabled, it's 512 colors (1536 bytes). In your nes_ntsc_setup_t structure, point palette_out to a buffer to hold the palette, then call nes_ntsc_init(). If you only need the palette and aren't going to be using the NTSC filter, pass 0 for the first parameter. A custom source RGB palette can also be used to replace the standard NES color generation hardware. See the palette and base_palette members of nes_ntsc_setup_t in nes_ntsc.h for more. Image Parameters ---------------- Many image parameters can be adjusted and presets are provided for composite video, S-video, RGB, and monochrome. Most are floating-point values with a general range of -1.0 to 1.0, where 0 is normal. The ranges are adjusted so that one parameter at an extreme (-1 or +1) and the rest at zero shouldn't result in any internal overflow (garbage pixels). Setting multiple parameters to their extreme can produce garbage. Put another way, the state space defined by all parameters within the range -1 to +1 is not fully usable, but some extreme corners are very useful so I don't want to reduce the parameter ranges. The sharpness and resolution parameters have similar effects. Resolution affects how crisp pixels are. Sharpness merely enhances the edges by increasing contrast, which makes things brighter at the edges. Artifacts sets how much "junk" is around the edges where colors and brightness change in the image, where -1 completely eliminates them. (Color) bleed affects how much colors blend together and the artifact colors at the edges of pixels surrounded by black. (Color) fringing affects how much color fringing occurs around the edges of bright objects, especially white text on a black background. When using custom settings, initialize your nes_ntsc_setup_t using one of the standard setups before customizing it. This will ensure that all fields are properly initialized, including any added in future releases of the library that your current code can't even know about. nes_ntsc_setup_t setup; setup = nes_ntsc_composite; /* do this first */ setup.sharpness = custom_sharpness; nes_ntsc_init( ntsc, &setup ); Image Size ---------- For proper aspect ratio, the image generated by the library must be doubled vertically. Use the NES_NTSC_OUT_WIDTH() and NES_NTSC_IN_WIDTH() macros to convert between input and output widths that the blitter uses. For example, if you are blitting an image 256 pixels wide, use NES_NTSC_OUT_WIDTH( 256 ) to find out how many output pixels are written per row. Another example, use NES_NTSC_IN_WIDTH( 640 ) to find how many input pixels will fit within 640 output pixels. The blitter rounds the input width down in some cases, so the requested width might not be possible. Use NES_NTSC_IN_WIDTH( NES_NTSC_OUT_WIDTH( in_width ) ) to find what a given in_width would be rounded down to. Burst Phase ----------- The burst_phase parameter to nes_ntsc_blit() should generally toggle values between frames, i.e. 0 on first call to nes_ntsc_blit(), 1 on second call, 0 on third call, 1 on fourth, etc. If merge_fields is enabled (see below), you should always pass 0. Read further for more detailed operation. If you're using nes_ntsc_blit() to do partial screen updates, burst_phase should be calculated as (burst_phase + row) % 3, where row is the starting row (0 through 239). For example, if burst_phase is 1 for the current frame and you make two calls to nes_ntsc_blit() to blit rows 0 to 100, then rows 101 to 239, for the first call you should pass 1 for burst_phase, and for the second call you should pass 0 for burst_phase: (1 + 101) % 3 = 0. Do the same regardless of the merge_fields setting. For more accurate burst_phase operation, it should be adjusted at the beginning of a frame based on the length of scanline 20: if 341 clocks (normal), burst_phase = (burst_phase + 1) % 3, otherwise burst_phase = (burst_phase + 2) % 3 (for shorter 340 clock scanline). The included test ROMs verify proper burst_phase implementation. They must pass in order; an earlier failing test means that later tests will give meaningless results. The first two tests will pass with either method of burst_phase handling described above; the third will only pass with the more accurate handling. The tests flash sets of dots quickly with the dot color being the only important aspect. 1.line_phase.nes - Tests for proper burst_phase on each scanline. All dots on screen should be the same color. 2.frame_phase.nes - Tests for proper burst_phase toggling between frames. Each row of dots should alternate between the same two colors (if merge_fields is set to 1, they should all be the same color). 3.special_frame_phase.nes - Tests for proper burst_phase incrementing between frames when $2001 rendering is enabled late in the frame. Each rectangle of dots should be one color, and there should be three different colors of rectangles (if merge_fields is set to 1, each rectangle should be made of three colors of dots). There is a visual glitch near the top of the screen for the first line of dots; this is unrelated the test and should be ignored. Flickering ---------- The displayed image toggles between two different pixel artifact patterns at a steady rate, making it appear stable. For an emulator to duplicate this effect, its frame rate must match the host monitor's refresh rate, it must be synchronizing to the refresh (vsync), and it must not be skipping any frames. If any of these don't hold, the image will probably flicker much more than it would on a TV. It is important that you play around with these factors to get a good feel for the issue, and document it clearly for end-users, otherwise they will have difficulty getting an authentic image. The library includes a partial workaround for this issue, for the cases where all the conditions can't be met. When merge_fields is set to 1, nes_ntsc_blit() does the equivalent of blitting the image twice with the two different phases and then mixes them together, but without any performance impact. The result is similar to what you'd see if the monitor's refresh rate were the same as the emulator's. It does reduce the shimmer effect when scrolling, so it's not a complete solution to the refresh rate issue. The merge_fields option is also useful when taking a screenshot. If you capture without merge_fields set to 1, you'll only get the even or odd artifacts, which will make the image look more grainy than when the emulator is running. Again, play around with this to get an idea of the difference. It might be best to simply allow the user to choose when to enable this option. Note that when you have merge_fields set to 1, you should always pass 0 for the burst_phase parameter to nes_ntsc_blit() (unless doing partial screen updates). If you don't, you'll still get some flicker. Custom Blitter -------------- You can write your own blitter, allowing customization of how input pixels are obtained, the format of output pixels (15, 16, or 32-bit RGB), optimizations for your platform, and additional effects like efficient scanline doubling during blitting. Macros are included in nes_ntsc.h for writing your blitter so that your code can be carried over without changes to improved versions of the library. The default blitter at the end of nes_ntsc.c shows how to use the macros. Contact me for further assistance. The NES_NTSC_BEGIN_ROW macro allows starting up to three pixels. The first pixel is cut off; its use is in specifying a background color other than black for the sliver on the left edge. The next two pixels can be used to handle the extra one or two pixels not handled by the main chunks of three pixels. For example if you want to blit 257 input pixels on a row (for whatever odd reason), you would start the first two with NES_NTSC_BEGIN_ROW( ... nes_ntsc_black, line_in [0], line_in [1] ), then do the remaining 255 in chunks of three (255 is divisible by 3). Limitations ----------- The library's horizontal rescaling is too wide by about 3% in order to allow a much more optimal implementation. This means that a 256 pixel wide input image should appear as 581 output pixels, but with this library appears as 602 output pixels. TV aspect ratios probably vary by this much anyway. If you really need unscaled output, contact me and I'll see about adding it. Thanks ------ Thanks to NewRisingSun for his original code and explanations of NTSC, which was a starting point for me learning about NTSC video and decoding. Thanks to the Nesdev forum for feedback and encouragement. Thanks to Martin Freij (Nestopia author) and Charles MacDonald (SMS Plus author) for significant ongoing testing and feedback as the library has improved. Thanks to byuu (bsnes author) and pagefault (ZSNES team) for feedback about the SNES version. -- Shay Green <gblargg@gmail.com>