bsnes-emu / bsnes

bsnes is a Super Nintendo (SNES) emulator focused on performance, features, and ease of use.
Other
1.69k stars 158 forks source link

Rationale for Hardcoded 1.5 Gamma? #318

Open ChthonVII opened 2 months ago

ChthonVII commented 2 months ago

(I'd have create a "discussion" rather than an "issue" if this repo has "discussions" enabled.)

For the standalone build, there's a default 1.5 gamma.

For the libretro build, it's a hardcoded 1.5 gamma.

Why?

Where did this come from? Does this reflect something the SNES is doing internally? Or is it a kludge to make thing look better on sRGB monitors?

(In the latter case, that's quite the mean joke on all the developers who've poured countless hours into developing shaders that assume they're getting raw R'G'B' from the emulator when they're not...)

Edit: Additionally, the gamma ramp that Byuu said was a kludge is disabled for the standalone build (perhaps because the 1.5 gamma does a similar job?), but apparently not for libretro. Which looks like it means that libretro is getting the gamma ramp and 1.5 gamma on top of that. And neither one can be turned off in libretro to give shader authors the raw R'G'B' they think they're getting.

carmiker commented 2 months ago

I added the ability to modify the gamma to the main bsnes libretro port. I had planned to backport the work to this upstream repo as well, but have lacked free time. If you want to adjust gamma, for now you can just use libretro's fork of bsnes: https://github.com/libretro/bsnes-libretro

ChthonVII commented 2 months ago

I added the ability to modify the gamma to the main bsnes libretro port. I had planned to backport the work to this upstream repo as well, but have lacked free time. If you want to adjust gamma, for now you can just use libretro's fork of bsnes: https://github.com/libretro/bsnes-libretro

That's good!

What about the gamma ramp kludge? Is it active by default in that fork, and, if so, can it be turned off?

Also, do you have any insight into why the default gamma was 1.5 in the first place?

carmiker commented 2 months ago

I really have no idea. I did try to figure it out while I was working on that feature, but nobody seems to know for sure, at least nobody I know seems to know. Yes, you can just set the gamma to 1.0 to get something closer to the raw colours (although they have been converted from 5-bit to 8-bit per channel). In bsnes-jg I just removed this completely and only use the raw RGB values.

Screwtapello commented 2 months ago

At one point Near did some amateur colorimetry, trying to match bsnes' output to an actual SNES plugged into a CRT television. He came up with this whole approximate gamma-correction curve, I think based on the sRGB piece-wise gamma function, a mixture of linear and logarithmic. That called "colour emulation", and I think there was one function that took the values of the saturation, gamma, and brightness sliders, and the "colour emulation" toggle, combined them all, and produced a palette that was applied to all SNES output before it was displayed.

It makes sense that the filtering was not disabled for shaders, since there are many shaders like AANN and Quilez that just change the shape of the rendered pixels, not the colour - for those, you'd want all the colour-correction settings to still be in effect. On the other hand, it's true that it would be a problem for shaders that try to do colour-calculations themselves, like CRT scanline shaders.

In v106r43 (b14c6bf1554933ca82da6c37dd32f4a29053db4b) on 2018-06-27, Near removed the "colour emulation" option, saying:

default to 150% gamma instead (it's a touch brighter but similar)

And that's all I can quickly remember or look up about the 1.5 gamma correction default.

ChthonVII commented 2 months ago

In v106r43 (b14c6bf) on 2018-06-27, Near removed the "colour emulation" option, saying:

default to 150% gamma instead (it's a touch brighter but similar)

I see. Then I think someone broke something somewhere along the line. As best I can tell:

And so it appears colorEmulation remains true for the libretro build, so the gamma ramp is applied, on top of the 1.5 gamma. Someone who's set up to compile might want to quickly toss a printf in there to verify, but this looks wrong to me.

I believe the best fix would be to initialize colorEmulation to false when it's declared. That avoids the possibility of a future refactor accidentally turning it back on again.

If not for its cult status, I'd suggest just deleting it entirely.

FYI, it looks like the gamma ramp is x < 0.5 ? 0.5*((2.0 * x)^1.9) : x, that then gets multiplied by L and 0x101. (Or at least the 1.9 curve rounds back to the right numbers when quantized to 0-255. 1.825 is a better fit at higher resolution.)

[Edit:

Also, that commit message is sufficient explanation for me about where the 1.5 gamma came from. It's an unprincipled kludge, replacing another unprincipled kludge, that aims to make R'G'B' that was gamma encoded for CRT televisions look better on sRGB LCD screens.

On the other hand, it's true that it would be a problem for shaders that try to do colour-calculations themselves, like CRT scanline shaders.

It's a problem for pretty much every retroarch shader preset these days. Pretty much everything either starts with a ^2.4 and ends with a ^(1/2.2), or does something more complicated. Libretro, at least, really, really needs an option to access to raw R'G'B'.

It's also going to be a problem going forward as everything slowly transitions to HDR. You don't want a CRT->sRGB gamma kludge when your display device is rec2020 and not sRGB.

The user-facing setting should probably be named something like "Gamma Conversion," and have options "none" and "CRT->sRGB," leaving room for a future "CRT->HDR."

I believe the really "right" implementation for CRT->sRGB gamma would be to apply the EOTF function from BT.1886 Appendix 1 (sample C++ implementation), chop off the black lift and renormalize, then apply the inverse EOTF function from IEC 61966-2-1 (sample C++ implementation).

end edit]

carmiker commented 2 months ago

@ChthonVII good find. I'll take a second look at this soon and try to backport the changes to this repo's libretro port when I get some time.

edit: The libretro port never calls Interface::color, so the kludge is never activated. That said, it doesn't hurt to specifically disable it in the libretro port. Upon a deeper look, I don't see it being called in the standalone port either.