Overscan / Scanline / Aspect Ratio control

Episode 1: Snes9x

For a project aiming to bring a common set of features, RA/libretro suffers a lot from inconsistency.

I’ve been trying various cores and I just don’t understand what the different options do.

This option has no effect in SNES9x (I guess this is good)

Example:

This is snes9x, crop overscan off

crop overscan on

There is an auto option that seems to default to AUTO which seems to be… ON, I don’t see any real auto behavior in the code (I may be wrong)

    if (crop_overscan_mode == OVERSCAN_CROP_ON)
        height = SNES_HEIGHT;
    else if (crop_overscan_mode == OVERSCAN_CROP_OFF)
        height = SNES_HEIGHT_EXTENDED;

    info->geometry.base_width = width;
    info->geometry.base_height = height;
    info->geometry.max_width = MAX_SNES_WIDTH;
    info->geometry.max_height = MAX_SNES_HEIGHT;
    info->geometry.aspect_ratio = get_aspect_ratio(width, height);
    info->timing.sample_rate = 32040;
    info->timing.fps = retro_get_region() == RETRO_REGION_NTSC ? 21477272.0 / 357366.0 : 21281370.0 / 425568.0;

This is done at startup, basically it has two defined height values depending on overscan on or off. It can also change whenever the option is called.

That’s what the options do at least, basically a geometry change.

Then there is this code here:

bool8 S9xDeinitUpdate(int width, int height)
{
    int overscan_offset = 0;

    if (crop_overscan_mode == OVERSCAN_CROP_ON)
    {
        if (height > SNES_HEIGHT * 2)
        {
            overscan_offset = 14;
            height = SNES_HEIGHT * 2;
        }
        else if ((height > SNES_HEIGHT) && (height != SNES_HEIGHT * 2))
        {
            overscan_offset = 7;
            height = SNES_HEIGHT;
        }
    }
    else if (crop_overscan_mode == OVERSCAN_CROP_OFF)
    {
        if (height > SNES_HEIGHT_EXTENDED)
        {
            if (height < SNES_HEIGHT_EXTENDED * 2)
            {
                overscan_offset = -16;
                memset(GFX.Screen + (GFX.Pitch >> 1) * height,0,GFX.Pitch * ((SNES_HEIGHT_EXTENDED << 1) - height));
            }
            height = SNES_HEIGHT_EXTENDED * 2;
        }
        else
        {
            if (height < SNES_HEIGHT_EXTENDED)
            {
                overscan_offset = -8;
                memset(GFX.Screen + (GFX.Pitch >> 1) * height,0,GFX.Pitch * (SNES_HEIGHT_EXTENDED - height));
            }
            height = SNES_HEIGHT_EXTENDED;
        }
    }


    if (width == MAX_SNES_WIDTH && hires_blend)
    {
        #define AVERAGE_565(el0, el1) (((el0) & (el1)) + ((((el0) ^ (el1)) & 0xF7DE) >> 1))

        if (hires_blend == 1) /* Blur method */
        {
            for (int y = 0; y < height; y++)
            {
                uint16 *input = (uint16 *) ((uint8 *) GFX.Screen + y * GFX.Pitch);
                uint16 *output = (uint16 *) ((uint8 *) GFX.Screen + y * GFX.Pitch);
                uint16 l, r;

                l = 0;
                for (int x = 0; x < (width >> 1); x++)
                {
                    r = *input++;
                    *output++ = AVERAGE_565 (l, r);
                    l = r;

                    r = *input++;
                    *output++ = AVERAGE_565 (l, r);
                    l = r;
                }
            }
        }
        else if (hires_blend == 2) /* Merge method */
        {
            for (int y = 0; y < height; y++)
            {
                uint16 *input = (uint16 *) ((uint8 *) GFX.Screen + y * GFX.Pitch);
                uint16 *output = (uint16 *) ((uint8 *) GFX.Screen + y * GFX.Pitch);
                uint16 l, r;

                for (int x = 0; x < (width >> 1); x++)
                {
                    l = *input++;
                    r = *input++;
                    *output++ = AVERAGE_565 (l, r);
                }
            }

            width >>= 1;
        }

        video_cb(GFX.Screen + ((int)(GFX.Pitch >> 1) * overscan_offset), width, height, GFX.Pitch);
    }
    else
    {
        video_cb(GFX.Screen + ((int)(GFX.Pitch >> 1) * overscan_offset), width, height, GFX.Pitch);
    }

    return TRUE;
}

This seems to calculate which part of the framebuffer is “forwarded to the frontend” All good here, now for aspect ratio, I always use core provided, that’s what everyone should do imho assuming the core is nice enough to provide a correct aspect ratio, so let’s see the options

auto|ntsc|pal|4:3|uncorrected

auto seems to be ntsc on NTSC games and pal on PAL games, which seems correct. Then there is 4:3 and uncorrected

Auto

4:3 image

Uncorrected seems to be 1:1?

Can anyone explain uncorrected? and also NTSC vs 4:3, there is a minor difference but no idea of what it is.

Also what aspect do you use for SNES9x?

1 Like

SNES typically sends 256x224 active lines, but there is an overscan bit that games can toggle to give them a full 239 active lines. Without the overscan bit set, those other 15 lines are just blank but they were still there. People don’t like to see them, though, so crop overscan removes them. As I understand it, those overscan lines should be distributed evenly above and below the image (i.e, they way bsnes does it), but snes9x-git just adds them to the top(?), while snes9x2010 doesn’t add them at all (IIRC).

Re: aspect, ‘uncorrected’ is 8:7 (1:1 PAR), which I’m not a fan of exposing vs making that a frontend thing. I’m not clear either on NTSC vs 4:3, but it might differ based on how they handle PAL games. /shrug

1 Like

FCEUMM

The global crop overscan setting seems to do nothing, so at least that’s consistent so far. I know for NES I’d use 8:7 PAR and crop overscan would vary on a per-game basis

So by default this is how SMB3 looks like:

Options are:

  • Preferred Aspect Ratio: 8:7 PAR|4:3
  • Crop Overscan Horizontal: off|on
  • Crop Overscan Vertical: off|on

So on with the questions. What does 8:7 PAR mean in layman terms? because it’s certainly not 8/7. I kinda understand that it means but it needs a clear explanation.

4:3 is certainly not just 4/3 either on defaults.

It says so right there, aspect is 1.43, while 4/3 is 1.33

If I disable vertical overscan (on by default) then the results are more in-line with the numbers for 4:3

8:7 PAR is still not 8/7

Furthermore, this game certainly needs horizontal overscan cropping to look proper regardless of the AR so:

8:7

4:3

This is more like it but… I know there is no single answer to these questions but… I’ll ask anyway

Why?

Why is there so much control over the viewport in the core? what’s the point of a common API with good frontend/backend separation when you’re gonna reimplement everything differently in every single client?

We’ve been patching this using core options for years to try to please everyone but ultimately it seems to be always bad.

And we haven’t even got to mednafen yet!

SNES typically sends 256x224 active lines, but there is an overscan bit that games can toggle to give them a full 239 active lines. Without the overscan bit set, those other 15 lines are just blank but they were still there. People don’t like to see them, though, so crop overscan removes them. As I understand it, those overscan lines should be distributed evenly above and below the image (i.e, they way bsnes does it), but snes9x-git just adds them to the top(?), while snes9x2010 doesn’t add them at all (IIRC).

Re: aspect, ‘uncorrected’ is 8:7 (1:1 PAR), which I’m not a fan of exposing vs making that a frontend thing. I’m not clear either on NTSC vs 4:3, but it might differ based on how they handle PAL games. /shrug

Ok so overscan was a feature on SNES, not an artifact, and that is why it behaves… sorta preoperly as far as I can tell in snes9x-git.

This is CT with crop on

And Off, so with it on there are some lines on top, only and not below, is that what you meant?

(i.e, they way bsnes does it), but snes9x-git just adds them to the top(?), while snes9x2010 doesn’t add them at all (IIRC).

This is exactly what I mean, the inconsistency is a problem

bsnes core has no core options, so it uses the dreaded setting under video settings which needs a restart, also has no aspect ratio correction.

In the end the results are similar but the UX is far worse than snes9x.

And then you go to… higan, it has internal resolution?? What’s the deal, I know it does pixel doubling but if you are just a newb, you’d expect… higher res pixels?

And as far as I can tell it doesn’t crop overscan:

People would call me… negative, but don’t you think this is an issue?

2 Likes

Another example, beetle saturn, defaults:

Horizontal Overscan Mask 20

Horizontal Overscan Mask 40

Horizontal Overscan Mask 60

The first two show the same image, stretched, the second two show some actual cropping. Documentation reads:

Horizontal Overscan Mask [beetle_saturn_horizontal_overscan] (0 to 60 in increments of 2. 0 is default)

Self-explanatory.

Certainly not. Also mednafen has this awkward weird of dealing with vertical display area (initial / last scanline) Why is this (I know it comes from upstream)? But do end users need to see this?

I know there is no perfect solution but I’m sure we could do so much better. Heck if someone really wanted to solve this he could check the content of such scanlines and if it’s just black adjust accordingly!

I just think there are far too many different knobs in the cores and the results are far too unpredictable.

What happens when I remove scanlines? Just removing them would make the image look stretched horizontally (it’s actually squashed vertically),

There must be a better way to deal with this.

The variables here are consoles, games, and resolutions. Maybe the real solution would be a database?

Running console X game Y current res Z, then the DB would give you how many rows/columns you should crop at that moment, and the user would set a preference for PAR or DAR, it would be a herculean effort but seems far better than what we have now?

2 Likes

Having cores act differently was always considered okay, I think, since the idea was that they’re independent programs whose authors may have different ideas of how things work. I agree that some of the options are weird and confusing, though.

We had started moving the overscan cropping functionality out of the frontend and into the cores at one point because the single frontend option simply wasn’t sufficiently granular enough to cover the many different core behaviors. Our plan was to eventually nuke the frontend option altogether but that obviously hasn’t happened.

The frontend option was originally a leftover from SSNES being a libsnes (i.e., bsnes) frontend exclusively, as SNES doesn’t really need any granularity, just a dumb crop / don’t crop. I tried to reconcile the cropping on snes9x2010 with snes9x-git at one point, but I couldn’t even get it to pad out the extra pixels, so I gave up.

For Higan, byuu decided to run the program at the highest possible res (512x448[?]) all the time instead of running at the more common 256x224 by default and then resizing as required (IIRC, this is because SNES can do this mid-scanline, which PC software/APIs can’t keep up with). This fucks up shaders, so when maister worked on the re-port, he added the option to downsample it to the more common, shader-friendly res. This is indeed confusing, but the alternative is just to have a core that makes all shaders look like shit, which would likely be confusing, as well.

For beetle-saturn, I believe it has black pillarboxing of varying widths, so when it appears to just widen the image, I think it’s actually cropping off black, and then when it’s cropping and stretching, that’s overshooting the black and cutting off actual image.

I actually wish more cores had the first/last scanline option, as it lets you get perfect scaling on CRTs easily.

Personally, I don’t think a database is feasible for the same reason all these goofy options are here in the first place: people are super-picky about cropping and aspect, and nobody can agree on what’s “correct”.

1 Like

it’s tricky because, for example, some NES games have garbage on the extreme horizontal (SMB3 has a border on the left, and scrolling artefacts on the right), and some have useful parts (Castlevania’s UI extends the full width), so you might want to crop for some, and not for others, and you might only want to crop the horizontal overscan, and not the vertical, or vice versa.

arcade emulation rarely seems to have overscan you’d want to crop because (theory) they didn’t have to worry about consumer TVs with various viewable amounts of overscan, because the CRT of each cabinet was adjusted by the engineer. with arcade stuff you mostly have scores and useful stuff right up to the edge.

personally, i think having a global ‘crop overscan’ option isn’t going to be much use across many cores, and it’s better to have it as core options (if at all) where the emulator authors can pick appropriate defaults, and appropriate levels of control. eg, with NES you’d want to control vertical and horizontal overscan separately, and with arcade you’d not want to have any overscan options.

2 Likes

As I see it, all the static DAR settings should be applied to the full size framebuffer, so you don’t get distortions if/when you decide to crop overscan afterwards. I actually play with the init/last scanline values, it’s mednafen overscan settings. Some games use the full size (240px) while others don’t, ideally to get the best of the two worlds you would set this on a per-game basis, if you stick to one (say crop) some games will lose important information on the top or bottom (ie. PSX), on Saturn meanwhile you will get black bars most often, and you might decide to crop them so the game fills the screen.

I have already made a small database for some games, that I feed to the frontend and does the pertinent job; custom next-integer resolutions based on display, system and configured aspect ratio per game, plus a zoom variable (for letterboxed games). I think options like these should be within RA anyways. I’m adjusting to in-game values, but you have to be aware games (specially on the 32-bit era) showed different resolutions for intros, FMV, Start Screen… only Core Provided does set DAR dynamically.

Edit: As I observed there’s one core that isn’t consistent, at least for ST-V Titan, the Kronos core outputs some games at 448 and others at 480 pixels and this is automatic as the core doesn’t show overscan settings.

Maybe this video can solve your aspect ratio doubts:

Basically, in 90’s the horizontal resolution always were stretched to the screen margins (4:3). But some resolutions were thinner or wider. So, in the present, all screens are “pixel aspect ratio fixed”. Then when you set 1:1 aspect ratio in the core settings you can see the image thinner or wider depending of game resolution.

The problem is. If you set 1:1 the emulation shows pixel perfect but in the case of SNES, you see “taller” images. If you set 4:3 the image is like in 90s but is stretched and can cause artifacts if shaders/filters aren’t applied.

About the difference between 4:3 and NTSC. There is no difference because standard screens in Ntsc are 4:3 display aspect ratio.

1 Like

I would like to see custom cropping options for every edge.

Change how many pixels to crop on each side.

1 Like

I do agree. The current overscan crop should be replaced with a “crop lines” for each side, so it also deals with the Master System width overscan.

@Juandiegous, thanks for the video. I made a thread on the topic a long time ago putting together all the available data on the subject and I’m mostly using those values. For example some Saturn games should be played at 3/2 DAR, Dreamcast PAL at 5/4, and so on. You also have to be aware to apply 4/3 to the full height (240px) frame, not the cropped one (usually).

1 Like

Yeah, because no two games are the same when it comes to overscan, and it can get tricky to crop the borders with shaders and get them just right.

just to add, the UI based overscan option never do anything but just do nothing. the core still has to decide how many pix to take or on what side. previously in fceumm, this used to remove 8px from all sides of NES which is not significant but takes up some essential status bar labels for Castlevania for one. SMB3, though not officially done can work (or look) better with 16px remove from left and right to remove that loading seams. IMHO.

The gui/libreto ovescan crop is basically a signal that tells the core to crop overscan. It working or not depended on the core logic.

1 Like

my point exactly. its just an on/off switch and just for two modes only in this case.

The console cores are at least arguably in much better shape than the coomputer cores regarding this, maybe because the original emulators are somewhat lacking there. E.g. Vice changes aspect ratio depending on either NTSC or PAL setting, but the NTSC number of lines is rather large (247) with no option to crop. Hatari for Atari ST has some internal resolutions which allow for some limited control of borders, but seems to always assume square pixels etc.

Is someone know how to set the fceux core (or any) to get a PAL aspect ratio? I mean I’d like to get that two black borders on the top and bottom with a proper main view position. I want to an exact feeling as I had years ago but all cores that I’ve tested wasn’t able to do that. Can someone help me?

Some cores definitely change the aspect ratio, but that doesn’t necessarly translate into the same borders depending on the display.

You can set the scaling for anything however into the video output settings, then scaling and choosing custom for aspect ratio. E.g. for NES on a standard 1080p display you would ideally integer scale x4, then stretch horizontally (non-integer) to something like 1420 (assuming no horizontal overscan enabled).

For emulation purpose I have an old laptop with 1366x768 resolution. I’ve tried some scaling but I have no idea how to set it right. In PAL games the top border is smaller then the other and all display is pressed between. So the first thing is what dimensions should have my screen (without borders) an then set it in the right position in the way I mentioned but I don’t know how to do it. And one more thing I’m gonna play ntsc roms slowed down to 50Hz just like that like it was on all famiclones. Please give me some advice because I completely don’t know how I should set all together.

I don’t know how important vertical overscan is for PAL, for NTSC I usually turn it off and leave it on for PAL in core options. Basically, the vertical NES height for your screen should be 3x= either 672 or 720.

Pixel aspect for NES PAL is supposed to be like 1.38 Horizontally this means 3x 256 (no overscan) x1.38 = 1060. You could also just use 4x horizontal (1024) since there can be downsides from uneven stretching. As for positioning, if you have custom aspect and disable integer scaling, then you should see custom x and y position settings. NTSC roms slowed: I think this should work by just choosing PAL as a region in core options.