libsdl-org / SDL

Simple Directmedia Layer
https://libsdl.org
zlib License
8.73k stars 1.64k forks source link

Add support for a pre-multiplied alpha blending mode #2485

Open SDLBugzilla opened 3 years ago

SDLBugzilla commented 3 years ago

This bug report was migrated from our old Bugzilla tracker.

These attachments are available in the static archive:

Reported in version: HG 2.1 Reported for operating system, platform: All, All

Comments on the original bug report:

On 2017-06-26 22:00:46 +0000, Ben Kurtovic wrote:

Created attachment 2778 Adds a SDL_BLENDMODE_PMA blending mode for pre-multiplied alpha blending

Hello!

Recently I have hit a snag relating to SDL's lack of support for a pre-multiplied alpha blending mode. While this is easy to do with raw OpenGL (my renderer backend), it is really painful/infeasible to manually adjust the GL blend function for a SDL texture in client code. Since this issue has come up before[1], I thought it would be a good idea to add support to SDL directly.

Wikipedia's article on alpha compositing[2] explains PMA better than I could hope to in here, so please review that if you want to understand the motivation better.

My patch adds a member to the SDL_BlendMode enum called SDL_BLENDMODE_PMA (not a great name I admit, maybe SDL_BLENDMODE_PREMULTIPLIED would be better, but it's easy to change). The idea is SDL_BLENDMODE_PMA blends just like SDL_BLENDMODE_BLEND, but assumes the RGB channels of the source texture/surface have had the alpha channel pre-multiplied in. I added support for it in all of the render backends that I could figure out, which is generally trivial thanks to how hardware blending functions are specified. I also added it to the unaccelerated software rendering code, which is also easy since a lot of that code just premultiplies the alpha value before doing its computations, so we only needed to skip that step.

Side note: since OpenGL/Direct3D/etc have similar ways of specifying blend functions, it would be really nice if SDL could let us chose our own without the hard-coding. However, that's a much bigger project than this.

If we want to be completely thorough, I believe PMA could also be supported in the surface RLE acceleration code, but it seems somewhat non-trivial and I don't have the motivation to figure it out, so I left it unimplemented there.

Also, I couldn't test this on the non-OpenGL backends because I don't have the right hardware. I tried to add test code to the existing testautomation_surface.c and testautomation_render.c files, but it appears that the existing tests for the other modes are broken anyway, so I'm not sure how much effort should be spent there.

[1] https://forums.libsdl.org/viewtopic.php?p=41646 [2] https://en.wikipedia.org/wiki/Alpha_compositing

On 2017-06-28 08:40:59 +0000, Ben Kurtovic wrote:

Created attachment 2781 Adds a SDL_BLENDMODE_PMA blending mode for pre-multiplied alpha blending

Realized my original solution didn't support SDL_TEXTUREMODULATE_ALPHA when used without SDL_TEXTUREMODULATE_COLOR because the modulated RGBA value wouldn't account for the pre-multiplied alpha channel. Fixed in the new patch, which pre-multiplies the alpha into the RGB value in SetAlphaMod and does a couple things differently in a few renderers that needed it.

Tested with OpenGL and the software renderer; as before, I don't have the hardware to test the other renderers.

On 2017-08-14 12:57:34 +0000, Sam Lantinga wrote:

This is partly fixed with this commit: https://hg.libsdl.org/SDL/rev/180e8906dc3c

    SDL_BLENDMODE_BLEND_PREMULTIPLIED = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,SDL_BLENDOPERATION_ADD);

You elegantly folded that into the software renderer, which still needs to be done here, and this patch doesn't handle the modulation you mentioned. What's a good way to test that?

Can you re-base your patch on top of this commit, and change the name to SDL_BLENDMODE_BLEND_PREMULTIPLIED?

Thanks!

iryont commented 4 months ago

I was actually thinking about this recently.

I did encounter a problem in which I use an intermediate target (framebuffer) to which I draw multiple things with alpha blending (SDL_BLENDMODE_BLEND). Now, that framebuffer contains premultiplied alpha within its RGB components. In order to draw it into the main scene we need to use an equation which does not multiply the source RGB with its alpha, but only does inversed alpha multiplication for the target (so basically as you said, SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD)).

I can see multiple possible use cases for blending mode with premultiplied alpha.

However, a quick look shows that multiplying source by its alpha is done only for SDL_COPY_BLEND and SDL_COPY_ADD:

if (flags & (SDL_COPY_BLEND|SDL_COPY_ADD)) {
    /* This goes away if we ever use premultiplied alpha */
    if (srcA < 255) {
        srcR = (srcR * srcA) / 255;
        srcG = (srcG * srcA) / 255;
        srcB = (srcB * srcA) / 255;
    }
}
if (blendMode == SDL_BLENDMODE_BLEND || blendMode == SDL_BLENDMODE_ADD) {
    r = DRAW_MUL(r, a);
    g = DRAW_MUL(g, a);
    b = DRAW_MUL(b, a);
}

And so on. Otherwise the code flow for premultiplied alpha would be identical to SDL_COPY_BLEND, so perhaps we can simplify the code by adding flag to SDL that we are using premultiplied alpha source (such as framebuffer or texture) and that there is no need to multiply source RGB by alpha value again.

kennedy0 commented 3 months ago

I ran into this issue as well using intermediate render target textures. Compositing several transparent textures together with SDL_BLENDMODE_BLEND gave unexpected results until I read the SDL_BlendMode documentation and realized the operation wasn't doing what I thought it would based on the name. I was expecting something similar to the "Normal" layer blend mode in Photoshop.

I have a setup where I'm using multiple render target textures to implement cameras and render passes. The order of operations is roughly:

image

After copying many of these textures together using SDL_BLENDMODE_BLEND , the result was a very washed out color:

image

Using the suggested blend mode above for all of the intermediate render target textures solved the issue for me:

SDL_ComposeCustomBlendMode(
    SDL_BLENDFACTOR_ONE,
    SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
    SDL_BLENDOPERATION_ADD,
    SDL_BLENDFACTOR_ONE,
    SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
    SDL_BLENDOPERATION_ADD,
)