Closed ghost closed 3 years ago
Yes, currently alpha blending emulation is rather missing. I also checked angrylions plugin code and found that proper alpha blending requires coverage information. Lack of that functionality caused several issues, which I fixed with special hacks. I suppose that issue #728 is also related to alpha blending. @standard-two-simplex, good luck with it!
proper alpha blending requires coverage information
Yes that is true. I was hoping, however, that an accurate coverge value is used only for antialiasing purposes and that I could get away with assuming cvg = 1.0
for non-discarded pixels. This way alpha blending woul be partially supported.
728 is also related to alpha blending
It seems Pokemon stadium uses alpha values written to framebuffer very often. The black bars use mode CVG_FULL
and thus show in my WIP code.
Mario Tennis also seems to use this alpha values a lot, but the red explosion doesn't show as it uses mode CVG_WRAP
and alpha value of 0.0
. Those fragments are discarded by the alpha test when rendering the auxiliary buffer to main buffer.
I can't figure out Mario Tennis explosions effects.
Correct effect achieved via hacks |
The alpha test is used to decide whether it is shown with a constant threshold around 0.063
. Currently, the explosions don't pass this test because the fragments have an alpha of 0.0
.
I'm not very sure how the effect is working, but an auxiliary buffer is cleared in fill mode, then I guess the explosion texture is rendered over it and at last it is rendered in 1cycle mode to the main buffer.
In 1a9d370, hacking into fill color alpha makes the explosions visible because CVG_WRAP
is used and the clear alpha is passed to the call rendering the auxiliary buffer into the main buffer. I haven't found much information about the rendering of flame textures.
Playing around with uCvGXAlpha
hasn't resulted in any improvements.
I have spent lots of time on this and I'm quite tired about it, so I put it in a decent shape where cases CVG_DST_CLAMP
and CVG_DST_WRAP
use pixel alpha like master and cases CVG_DST_FULL
and CVG_DST_SAVE
are emulated.
I would expect no regressions and at least the warning sign in Pokemon stadium is fixed.
https://github.com/standard-two-simplex/GLideN64/tree/blender_changes
Maybe @Themaister can help you understand what Mario Tennis is doing behind the curtain. He is optimizing that case in paraLLEl-RDP.
tested one game and got a bug
see how the letters are now
This is how I see them. Can you post your graphic settings so I can compare to mine?
Edit: Nevermind, it's native rects as gonetz pointed out in the PR.
yes u are right, something odd must have happened
@standard-two-simplex could you give me a patch with your hack for Mario Tennis? I can't guess, how to hack it to get the explosion visible.
@standard-two-simplex could you give me a patch with your hack for Mario Tennis?
Here 00278ce0563a9a6d7675df5057dd3177f8a19ef2
The auxiliary framebuffer is drawn in cvg_wrap mode (uCvgDest ==1
). The alpha held by clampedColor.a
is 0.0
. When the auxiliary buffer is rendered to the main buffer, it fails the alpha test, which has a threshold somewhere around 0.065
Also the textures which are used to render the explosion look like this which have non transparent background. The black part never appeared in the screen in my tries, so I suspect some strange blending is happening.
which have non transparent background. The black part never appeared in the screen in my tries, so I suspect some strange blending is happening.
You are right and wrong about some strange blending. So, what I found about the explosion.
The explosion texture has format 8bit CI. It is totally opaque. Even black texels have full alpha.
Step 1: render explosion textures into an aux buffer. Texrect, 1 cycle Color combiner: (TEXEL0 - 0) PRIMITIVE + 0 Alpha combiner (1 - 0) TEXEL0 + PRIMITIVE Primitive color is just 0xFFFFFFFF, so output color is TEXEL0, output alpha is TEXEL0 + 0xFF. Since texel alpha is 0xFF, output alpha is 0xFF 2 = 0x1FE Blender is enabled, but blender equation is in 0 + in * 1, that is it just passes the color to the buffer. We should have full alpha in the buffer, since 0x1FE would be clamped to 0xFF. cvg_wrap mode is set here, which in our code means just combiner alpha. But there is also such thing as "N64 color wrap and clamp". It wraps our 0x1FE to zero, so all pixels in the aux buffer have zero alpha. Your hack overrides it.
Step 2 render aux buffer to the main one. Again with texrect, but 2 cycle this time. Blender is enabled, but again it uses the simple pass-through equation in both cycles: in 0 + in 1, So, the blender is not responsible for the transparency here. Alpha test is also enabled. It discards the explosion when texels in aux buffer texture have zero alpha. When the texels have full alpha it does nothing. I removed alpha test completely just to check, what it does here. Lots of things got broken but the explosion did not change a bit.
The interesting part here is the combiner.
Color combiner: cycle 1 (TEXEL0 - 0) PRIMITIVE_ALPHA + 0 cycle 2 (NOISE - TEXEL0) COMBINED + TEXEL0
Alpha combiner: cycle 1 0, 0, 0, TEXEL0 cycle 2 0, 0, 0, COMBINED
resulted alpha is just texel alpha from the aux buffer texture. It has no interest for us since the blender equation does not use it.
Primitive color is again 0xFFFFFFFF, so the result of the first cycle of color combiner is TEXEL0, that is color of texel from the aux buffer. As you know, TEXEL0 in the second cycle of the color combiner means TEXEL1, that is the second texture. The first texture is the aux buffer. The second texture is (surprise!) the main color buffer where we are currently rendering! That is the second cycle of the color combiner works as a blender: it blends the aux buffer texture with the current buffer. When texel of aux buffer texture is black the result is just main buffer color. Actually, dark texels of explosion texture are not black. They are rather dark grey. Thus they are not disappear completely after the "blending". Dark area of the explosion texture is barely visible but still visible: Btw, I'm not sure how it works with OpenGL, since it is prohibited to use the same texture as input and output one for the same shader (shader blender). Unless I made a special code for that and forgot about it.
Since "N64 color wrap and clamp" turns alpha in the Step 1 to zero, another way to hack this effect is to replace
" lowp vec4 wrappedColor = WRAP(cmbRes, -0.51, 1.51); \n"
by
" lowp vec4 wrappedColor = cmbRes; \n"
The open question is how to make the explosion work without hacks?
Alpha test is also enabled. It discards the explosion when texels in aux buffer texture have zero alpha. When the texels have full alpha it does nothing. I removed alpha test completely just to check, what it does here. Lots of things got broken but the explosion did not change a bit.
Do you mean that the alpha test is currently discarding all texels and the expected value is not to discard any texel, whether its black or orange?
We should have full alpha in the buffer, since 0x1FE would be clamped to 0xFF. cvg_wrap mode is set here, which in our code means just combiner alpha. But there is also such thing as "N64 color wrap and clamp". It wraps our 0x1FE to zero, so all pixels in the aux buffer have zero alpha. Your hack overrides it.
I check angrylions code. It's doing ((a-b)*c + (d<<8) + 0x80)>>8
after sign-extending a,b,c,d. The addition of 0x80
seems to be rounding to closest rather than down.
In fixed point arithmetic, the alpha equation (1-0)*TEXEL0+PRIMITIVE
should be calculating,
(0xFF - 0x00) * 0xFF + 0xFF00 + 0x80 = 0xFE01 + 0xFF00 + 0x80 = 0x1FD81
after the bit shift it becomes 0x1FD
which clamps to 0x0
as you mentioned.
In floating point arithmetic, we' re doing (if I'm not mistaken)
(1.0 - 0.0) * 1.0 + 1.0 = 2.0
which is wrapped to 0.0
so it should be accurate enough.
In principle, CVG_WRAP
means adding combined alpha to memory alpha and keeping the last 3 bits. Since, pixel alpha is 0.0
this should preserve previous memory alpha. Thus it keeps the alpha of the FillRect()
command that cleared the auxiliary buffer.
This is currently emulated, but the auxiliary buffer alpha is cleared to 0.0
. If you hack gDPFillRectangle()
to set gDP.rectColor.a = 1.0
(or any value that will pass the alpha test) the explosions also show. I don't know which color angrylions code clears the auxiliary buffer to.
The cycle1 of the alpha combiner when rendering into main buffer does (0-0)*0+TEXEL0, so the alpha output is 0.0
. At this stage the alpha test fails and the explosion fragments are discarded.
I don't know which step is incorrect. Color and alpha clamp are supposed to happen, the combiner equation outputs looks correct, and the fillrect and alpha test code is pretty straightforward.
cycle 2 (NOISE - TEXEL0) * COMBINED + TEXEL0
Is this combined color or alpha?
The equation is quite strange. It linearly interpolates between TEXEL0 and NOISE (random rgb?) using combined (color or alpha?) as interpolation factor. I guess it may explain why the black pixels disappear, but I can't understand what it does otherwise.
Do you mean that the alpha test is currently discarding all texels
Yes
and the expected value is not to discard any texel, whether its black or orange?
I'm not sure, but I don't see how resulted alpha can differ from 0 or 1. As I can see, alpha test discards either all or nothing here.
In principle, CVG_WRAP means adding combined alpha to memory alpha and keeping the last 3 bits.
May be CVG_WRAP means adding combined cvg to memory cvg and keeping the last 3 bits?
Is this combined color or alpha?
Combined color. It is color from the aux buffer texture in our case.
The equation is quite strange. It linearly interpolates between TEXEL0 and NOISE (random rgb?) using combined (color or alpha?) as interpolation factor. I guess it may explain why the black pixels disappear, but I can't understand what it does otherwise.
If COMBINED is black, the result is main buffer color. If COMBINED is white, the result is some random color. Otherwise the result is something in between main color and random color. I also don't quite understand it, but I guess that explosion looks so dynamic because of randomness.
May be CVG_WRAP means adding combined cvg to memory cvg and keeping the last 3 bits?
If CVG_X_ALPHA == 0
, then pixel coverage is used, If CVG_X_ALPHA == 1
then, pixel coverage times pixel alpha is used. Notice that pixel coverage is most of the times 0xFF
, so this means pixel alpha is used in the latter case.
I had tried writing lowp float cvg = uCvgXAlpha == 0 ? 1.0 ? clampedColor.a;
in ShaderBlenderAlpha() but the explosions don't appear and I'm not sure if I encountered regressions.
Closing this as alpha blending is emulated. I'll open another issue about Mario Tennis.
In current master the alpha output of the color combiner is blent with memory alpha using the same source factor and destination factor in OpenGL's color blend equation.
I believe angrylions software plugin uses the following code in
finalize_spanalpha()
to write to the framebuffer.After the color combiner takes place, pixel alpha might be multiplied to pixel coverage if
cvg_times_alpha
flag is set. Socurpixel_cvg
might actually contain pixel alpha.The first and second cases add pixels coverage to memory coverage. This may be used to simply write pixel coverage into memory or for antialiasing purposes for pixels where different polygons meet.
CVG_WRAP
is probably the most problematic one to emulate because OpenGL's blending doesn't support wrapping (I believe) and games may initialize memory coverage to0xFF
to automatically subtract one from pixel coverage (because(pixel.cvg + 0xFF) & 0x7 = pixel.cvg - 1
).The third and four cases should be straightforward to implement and might be critical for some framebuffer effects to show up.
Since pixel coverage calculation isn't emulated, it may be defaulted to
1.0
for the time being.Of course, this is all theoretical, so we'll have to see how it goes and if it brings results. Wish me luck!