Implement blargg ntsc filters
- this implements blargg's nes ntsc filters using core options - an optional height doubling is also added but disabled for performance reasons (might make that optional as a core option) - since PS2 and PSP have their own blitter branches, these platforms do not have the ntsc filters since i dont have the means to test on those systems. - compile with HAVE_NTSC=1 to have these options, HAVE_NTSC=0 disabled filter including core options - HAVE_NTSC=1 is set as default, other than PS2 and PSP as stated above.
This commit is contained in:
218
src/ntsc/nes_ntsc.txt
Normal file
218
src/ntsc/nes_ntsc.txt
Normal file
@@ -0,0 +1,218 @@
|
||||
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>
|
||||
Reference in New Issue
Block a user