obsproject / obs-studio

OBS Studio - Free and open source software for live streaming and screen recording
https://obsproject.com
GNU General Public License v2.0
60.37k stars 7.99k forks source link

Reconsider doing proper alpha blending #5674

Closed CryZe closed 2 years ago

CryZe commented 2 years ago

Operating System Info

Other

Other OS

All of them

OBS Studio Version

27.1.3

OBS Studio Version (Other)

No response

OBS Studio Log URL

No

OBS Studio Crash Log URL

No response

Expected Behavior

White Text on a white background shouldn't ever yield black pixels.

Current Behavior

There's lots of black / greyish pixels showing up on semi transparent pixels.

Steps to Reproduce

  1. Create white text (if you use the text source, make sure sRGB is on)
  2. Put it above a white background
  3. Scale the text up enough to see the grey pixels. ...

Anything else we should know?

THIS AFFECTS ALL SOURCES EXCEPT THE BROWSER SOURCE, EVEN IF THE TEXT SOURCE HAPPENS TO BEHAVE (IN SOME CIRCUMSTANCES) IT'S STILL A BUG WITH ALL OTHER SOURCES.

This was the original PR that fixed this: https://github.com/obsproject/obs-studio/pull/1578

It unfortunately got reverted.

https://camo.githubusercontent.com/b9e65f16f4aa0262f881524cb9478bfe18f4a46da52baaff52aaddabd9a5e0cf/68747470733a2f2f692e696d6775722e636f6d2f5668686b51635a2e706e67

CryZe commented 2 years ago

The PR got reverted because people abuse the wrong alpha blending on text as a fake text border. Obligatory xkcd:

[obligatory xkcd https://imgs.xkcd.com/comics/workflow.png](https://imgs.xkcd.com/comics/workflow.png)

I don't mind it being broken on the text as much (though it bothers me every time I see it on someones stream), but game capture is majorly affected by this problem, where it probably shouldn't have been reverted. (Also apparently the image source is broken too)

jpark37 commented 2 years ago

I noticed you copied the image from your old PR. Are you sure this is still happening? From 27.1.3: image

CryZe commented 2 years ago

You did not zoom in / scale it up far enough, it only happens on the transparent pixels (however there's different text sources on the different operating systems, so maybe your OS is unaffected).

https://i.imgur.com/6kU88jy.png

That weird rectangle inside the e is an image source going from white on the left to transparent on the right... it does not look white to me.

Also someone somewhat confirmed that the vtuber application that people seem to be using is affected by this bug (through game capture) as well:

https://media.discordapp.net/attachments/444485391823667200/922171959918338058/unknown.png

Here the white whiskers appear much darker when they are above the white background (where OBS does the blending) as opposed to when they are in front the cat (where the application does the blending).

And here LiveSplit being game captured (which is configured to not use any shadows or text border here) is having the same problem:

https://i.imgur.com/2yXVnAU.png

jpark37 commented 2 years ago

Sorry, I though you meant scale outside OBS, i.e. look closely.

I looked at the text source example, and the problem there is that the source image is in straight alpha, e.g. 1/1/1/0.5 instead of 0.5/0.5/0.5/0.5, so the Default bilinear filtering will not be mathematically correct. You should be able to work around this by enabling a scale filter on the source, which samples the text texture texels 1:1 and writes to a render target with premultiplied alpha, then scales into the canvas filtering with premultiplied colors. Any scale filter will fix the edge darkening, but Bilinear will match Default behavior in terms of filtering.

Hopefully the same workaround will work for game capture, but I haven't tested.

Making this work properly for the Default case seems like a drag. For game capture, it would mean running a full screen shader to convert from straight to premultiplied, which we don't want to pay for by default since most games are completely opaque. We could add a toggle, but every checkbox is more complexity.

EDIT: Could also do manual bilinear filtering in the shader to avoid the extra fullscreen pass, but the last time I tried that, it was even slower IIRC.

In theory, LiveSplit could render to a DXGI swapchain with premultiplied colors, and specify DXGI_ALPHA_MODE_PREMULTIPLIED, assuming it works as advertised since I've never tried it.

CryZe commented 2 years ago

For game capture, it would mean running a full screen shader to convert from straight to premultiplied, which we don't want to pay for by default since most games are completely opaque.

There already is a checkbox for whether the source is even captured transparently in the first place, so it could just be tied to whether that is active.

Also are you sure this has anything to do with bilinear filtering? It seems to happen if it's a 1:1 transform too:

https://i.imgur.com/UEiCl6Z.png

(Also consider the background behind the text, which is also various levels of semi transparent white, which shouldn't look like this)

In theory, LiveSplit could render to a DXGI swapchain with premultiplied colors, and specify DXGI_ALPHA_MODE_PREMULTIPLIED, assuming it works as advertised since I've never tried it.

I believe it does that, but I can check again.

Any scale filter will fix the edge darkening, but Bilinear will match Default behavior in terms of filtering.

The scale filter does nothing:

https://i.imgur.com/yRQW9Dl.png

jpark37 commented 2 years ago

There already is a checkbox for whether the source is even captured transparently in the first place, so it could just be tied to whether that is active.

We don't want to pay an extra cost if they don't rescale the image though.

Also are you sure this has anything to do with bilinear filtering? It seems to happen if it's a 1:1 transform too:

There's the larger issue that OBS blends in linear space, which is what GPU/PNG specs want. Others cheat, e.g. web/GDI, either out of ignorance or power savings. Ultimately, we chose to be linear-correct going forward. A toggle to blend naively in sRGB-encoded space isn't impossible, but it's not just a few lines of code. Significant plumbing would be involved.

EDIT: This may or may not contribute to the issue, but I can dig a bit if you can share your LiveSplit files.

The scale filter does nothing:

You need to set a resolution other than None for that filter to work, but I meant the "Scale Filtering" setting if you right-click on the source itself. Either way should trigger an extra draw that avoids filtering colors with straight alpha though.

jpark37 commented 2 years ago

Also, is this regular LiveSplit? If you can share the splits/layout, I can try debugging that.

FiniteSingularity commented 2 years ago

I am seeing similar issues with any sources containing alpha channels, including the edges of chroma keyed sources using the OBS Chroma key filter. Here is an exaggerated example using StreamFX Dynamic mask plugin. My scene sources are ordered as follows:

  1. Source containing image of chalkboard: This source is filtered with dynamic mask, using the black/white mask shown below (where black is alpha=0, white is alpha=1)
  2. Source containing exact same image of chalkboard.

Since I am placing the same image on top of itself (with the top copy masked a bit), I would expect to not see the mask at all- it should just look like the original image. Instead I am getting a grey halo around the mask, in the area where alpha is "fuzzy" (neither 0 nor 1) as seen below: mask-halo

In order to make sure the problem isn't in the dynamic mask plugin, I wrote a simple OBS shader to do exactly what this scene is doing- pick a top source that is masked, and a bottom source that is not, and combine them. And the result is exactly what I would expect- you cant see the mask at all. Here is a snippet of the shader code I am using, where InputA is the dynamic-masked source (1. above) and Background is the background source that we're putting InputA over (2. above):

  float4 pixel = InputA.Sample(maskSamplerA, v_in.uv);
  float3 pixelRGB = float3(pixel[0], pixel[1], pixel[2]);
  float4 bgPixel = Background.Sample(maskSamplerB, v_in.uv);
  float3 bgPixelRGB = float3(maskPixel[0], maskPixel[1], maskPixel[2]);
  float3 outputRGB = pixelRGB + bgPixelRGB * (1.0 - pixel[3]);

I've noticed that if the last line of my shader is:

float3 outputRGB = pixelRGB * pixel[3] + bgPixelRGB * (1.0 - pixel[3]);

I get the exact same result as using OBS to compose everything- the exact same fuzzy halo.

mask

y19940406 commented 2 years ago

I've got a similar problem with browser source. I have a website which is transparent. It contains a div with css like this: {color: #000000; background: #ffffff33;} I expected it apperanced fully white background when the background is white and a little bit white when the background is black. But is get gray when background is white... image

jpark37 commented 2 years ago

I see three issues here.

We should probably just close this out. If anyone still has an issue, please file a clean ticket, ideally with clear, minimal repro steps/assets.

CryZe commented 2 years ago

No, the issue still exists and I still want it fixed. I'll try to create an executable for you to reproduce it.

gin3715 commented 2 years ago

Operating System Info

Windows 10

OBS Studio Version

27.1.1, 27.1.3

Expected Behavior

No black edge around text (27.1.1)

Current Behavior

OBS produces a black edge around text (since 27.1.3)

Steps to Reproduce

Load the html page provided below as a browser source. Put a whilte color source below the browser source

Similar issue happed in browser source with transparency.

<html>
<body style="background-color: transparent; padding-top: 20px">
<span style="background-color: #fff; border-radius: 30px; padding: 20px">test</span>
</body>
</html>

A browser source loads this simple webpage with transparent background, over a white color source. Results shouldn't produce a black edge around the span element.

black-edge

PS: The problem wasn't there at 27.1.1

Fenrirthviti commented 2 years ago

There have a lot of changes in color handling with all the HDR work, is this issue still present?

If it is, please provide as close to exact reproduction steps as possible so that we can test if this is an expectation, if there is a workaround, or is a bug.

CryZe commented 2 years ago

I just checked and with v28.0.3 the issue still exists:

https://i.imgur.com/Mq3WCB4.png

Steps to Reproduce

  1. Create white text
  2. Put it above a white background
  3. Scale the text up enough to see the grey pixels. ...

Neither the foreground nor the background consist of any other color than white, there's no reason that there should be any darker pixels. This is an alpha blending bug that exists with almost all sources in OBS other than the browser source (where I guess it was so apparent that it got fixed a very long time ago). This may seem like a "minor issue", but this essentially means that only the browser source and other custom plugins can correctly visualize anything that's transparent. Game / window capturing a semi transparent window is essentially broken, which is a major blocker in me releasing support for semi transparent windows for LiveSplit.

Also the fix is super trivial, I could easily rebase my code and do a PR again, but I'm not sure "you want me to do a PR".

flaeri commented 2 years ago

Could you try to right click the text source > Blending Method > SRGB Off?

CryZe commented 2 years ago

~That somehow solves it?!~ I really don't think it should've though. That has nothing to do with sRGB and everything with how alpha is blended.

HOWEVER the problem still applies to game capture and toggling the blending method does not fix the problem there (this really suggests that all these code paths diverge a lot and none of the sources are at all consistent with each other).

https://i.imgur.com/06HNjIy.png

everything with how alpha is blended.

Well honestly it's both things, but they are orthogonal, technically you could interpret the alpha as linear or sRGB and on top of that as premultiplied or "straight". Browsers and games / transparent windows are typically premultiplied (it's much faster to work with that and they never have a reason to do a pass to turn it back straight, also your window manager will assume them to be premultiplied too), which is what OBS incorrectly assumes to be straight alpha.

If I could guess I would say OBS internally has it exactly backwards where it does all blending in straight alpha even though premultiplied would be faster and almost all sources should be premultiplied anyway (and the only source where that's not the case, i.e. the image source, can easily do it once).

Fenrirthviti commented 2 years ago

As the original report is resolved, marking as fixed.

CryZe commented 2 years ago

No it's not??? Only if you activate some obscure unrelated feature it essentially bugs out even more and happens to start being correct by complete accident. If anything it's even more bugged now and nothing is resolved at all.

Also this issue wasn't even about the text source at all, that was just an easy example of how to reproduce it. Only the text source now bugs out enough with this unrelated workaround to remotely "work".

I could either update the opening post and you reopen this issue or I can open a new issue with more details I guess (though honestly you really don't need more details as the OP still reproduces just fine).