• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Why do surfaces make the GUI Draw look much worse? [solved]

kupo15

Member
I'm very confused why drawing things to a surface in the GUI event then drawing that surface destroys the quality completely vs simply drawing them in the GUI event. This is a problem because I need to bm_subtract sprite masks to "poke holes" in the GUI which seems like can only be done if I draw things to a surface first, thus destroying the quality. Why does this happen and what can I do differently to retain the quality and still poke holes in it?

no surface
1632681755463.png

clearing via c_black
1632681726668.png

clearing via c_white
1632681800207.png
 
If your holes are static, can't they just be poked out of the sprite directly? Also, do you really need to poke holes or can't you draw the "hole's content" over?

Regarding surfaces, more experienced people will have better answers, but maybe the surface's size is too small or stretched? Does it match the GUI's size?
 

kupo15

Member
If your holes are static, can't they just be poked out of the sprite directly? Also, do you really need to poke holes or can't you draw the "hole's content" over?

Regarding surfaces, more experienced people will have better answers, but maybe the surface's size is too small or stretched? Does it match the GUI's size?
The surface is the same size as the gui resolution, and the holes I'm poking aren't static, they are the going to be the animated characters that I want to not be covered up by the GUI layer. I do currently have the GUI layer simulated in the Draw event old school style which works great even if its super messy because I'm subtracting the camera position and applying cam zoom scale to compensate, but the quality is much worse than simply drawing it on the GUI layer, which draws over the players. So I need to poke holes in the GUI layer to achieve this, or draw the players in the GUI layer which pretty much would be as messy as before so no point.

I wonder if what Foxy posted is the same reason simulating the GUI layer on the draw event is worse quality, though even drawing that on the app surface looks worse. GUI layer poking holes in it just seems the cleanest approach

@FoxyOfJungle Thank you so much for this! I love the Revernds Tuts, this should be what I need to figure solve this. I'll report back after I've gone through it and tested things out
 

FoxyOfJungle

Kazan Games
I wonder if what Foxy posted is the same reason simulating the GUI layer on the draw event is worse quality, though even drawing that on the app surface looks worse. GUI layer poking holes in it just seems the cleanest approach
This video should help you troubleshoot surfaces with these visual artifacts.
 

FredFredrickson

Artist, designer, & developer
GMC Elder
Have you tried disabling alpha writes when you draw transparent graphics to the surface? I recently had a similar issue, and doing that fixed my problem.
GML:
gpu_set_colorwriteenable(true, true, true, false);
/* draw stuff to surface here */
gpu_set_colorwriteenable(true, true, true, true);
If you don't do this, it will change the alpha of everything drawn behind it when you draw sprites with alpha to the surface.
 

CMAllen

Member
Surfaces contain the RBG values of every pixel, drawn to or not, and not just the alpha values. What you're running into trouble with is the fact that when you draw to your surface it's blending the sprite or image's source pixel data with the surface's destination colors. If you draw a 100% white pixel with 50% alpha to a 100% black transparent pixel, you don't end up with the sprite or image's original pixel data. You get a blend of black and white (a mid-tone grey). So when you draw that surface to the application surface, instead of the 50% visible white pixel you're expecting, you get the mid-tone grey. Whereas, when you draw directly to the application surface, the 100% white source pixel at 50% alpha is blended to with the destination pixel on the application surface directly. It's never blended with any other colors.
 

kupo15

Member
Have you tried disabling alpha writes when you draw transparent graphics to the surface? I recently had a similar issue, and doing that fixed my problem.
GML:
gpu_set_colorwriteenable(true, true, true, false);
/* draw stuff to surface here */
gpu_set_colorwriteenable(true, true, true, true);
If you don't do this, it will change the alpha of everything drawn behind it when you draw sprites with alpha to the surface.
I will give that a try too, it was also mentioned in the video Foxy posted but that solution didn't seem like it applied to me

Surfaces contain the RBG values of every pixel, drawn to or not, and not just the alpha values. What you're running into trouble with is the fact that when you draw to your surface it's blending the sprite or image's source pixel data with the surface's destination colors. If you draw a 100% white pixel with 50% alpha to a 100% black transparent pixel, you don't end up with the sprite or image's original pixel data. You get a blend of black and white (a mid-tone grey). So when you draw that surface to the application surface, instead of the 50% visible white pixel you're expecting, you get the mid-tone grey. Whereas, when you draw directly to the application surface, the 100% white source pixel at 50% alpha is blended to with the destination pixel on the application surface directly. It's never blended with any other colors.
Ahh that's great information to know, thanks! So what is your solution to getting around this and making it act like you'd expect it to? What Fred posted or the final solution in the Reverends video or something else? It kinda sounds like what you are saying is for GUI drawing for surfaces I should set the bm to that that doesn't blend with the initial cleared surface at all, like doing bm_zero, or what Fred did or something like that that way it doesn't take alpha blending into account with the initial surface at all.
 

CMAllen

Member
Your sprites need to have premultiplied alpha, and drawn in the extended blend mode bm_one, bm_inv_alpha and your finished UI surface also need to be drawn in the extended blend mode bm_one, bm_inv_alpha which is what the video linked above discusses. Note that if you're using GM's built-in font system, it does not support premultipled alpha for fonts, so you will either need to create your own font sprites with premultiplied alpha, use a shader to do the premultiplication at run-time on the GPU, or don't use fonts that have alpha values between 0 and 1 (ie it only has pixels that are fully opaque or fully transparent). In GMS2, premultipled alpha is a toggle in the sprite properties that you can turn on or off at will. In GMS1.4 it's an image adjustment (iirc) and cannot be reversed, and it will throw up a warning prompt for your confirmation before making the changes.

I suggest watching the video to get a better understanding of what's going on.
 

mcube-12139

Member
Drawing transparent graphs on a surface will "corrode" it. Look at bm_normal blending formula:
Ao = As * As + Ad * (1 - As)
If Ad=1 we get:
Ao * 1 - As * (1 - As)
0 < As < 1 so 0 < Ao < 1, so the surface becomes transparent. Then you draw it to the screen and it blends with the black background, seeming strange.

Solution is:
gpu_set_blendenable(false);
draw_surface(...);
gpu_set_blendenable(true);
 

gnysek

Member
I've got similar issue with application surface, and solution was to use gpu_set_blendenable(); (false before draw, true after draw), but that might be useful only if you draw surface without transparent pixels (as that's what happens with application surface).
 

kupo15

Member
So much great info, thanks everyone! Still going through it and going through many tests to figure it out

Have you tried disabling alpha writes when you draw transparent graphics to the surface? I recently had a similar issue, and doing that fixed my problem.
GML:
gpu_set_colorwriteenable(true, true, true, false);
/* draw stuff to surface here */
gpu_set_colorwriteenable(true, true, true, true);
If you don't do this, it will change the alpha of everything drawn behind it when you draw sprites with alpha to the surface.
This is not a workable solution for me, because as the video mentioned, this technique only works if I'm drawing transparent elements to a completely non transparent surface which I'm not doing. Bc its just containing GUI elements, anything not a GUI element on the surface will be transparent. If I clear the surface to opaque black for example then use this it works, but then is there a way to remove all the black I added to make it transparent again?

Drawing transparent graphs on a surface will "corrode" it. Look at bm_normal blending formula:
Ao = As * As + Ad * (1 - As)
If Ad=1 we get:
Ao * 1 - As * (1 - As)
0 < As < 1 so 0 < Ao < 1, so the surface becomes transparent. Then you draw it to the screen and it blends with the black background, seeming strange.

Solution is:
gpu_set_blendenable(false);
draw_surface(...);
gpu_set_blendenable(true);
this also doesn't work for the same reasons as above. Anything that should be transparent turns to black which is no good for a surface that is supposed to be mostly transparent.

I've got similar issue with application surface, and solution was to use gpu_set_blendenable(); (false before draw, true after draw), but that might be useful only if you draw surface without transparent pixels (as that's what happens with application surface).
If that what happens with the App surface and the GUI draw basically draws on top of the app surface, which most likely has no transparent pixels, then perhaps an easy solution is to draw the application surface onto the surface I'm creating instead of draw_clear_alpha() it, that way I don't have to deal with any transparent pixels at all. There should really is no difference between redrawing the app surface again with the gui elements I need vs treating it like I'm layering a transparent surface with new elements on top of the application surface from a performance standpoint, right?

That's an interesting shift in thinking. Don't treat this like I'm an old school animator that has transparent cells that contain the character animation being layered on top of the background. Copy the background onto the surface, then draw the character on top of that. I'm going to give that a try

And you know what, now that I think about it, the application surface works differently because its not cleared to be transparent I'm pretty sure. The base layer of the app surface is black opaque, why else would you see a black void in games in spots where the application surface is not covered and it produces that copy effect? (because the app surface isn't cleared every step)
 
Last edited:

kupo15

Member
Thanks so much everyone for the help and ideas on how to solve this. I finally figured out a solution that works well. The solution I ended up going with was:

setting the surface
drawing the current state of the app surface to remove any transparent pixels
drew my HUD stuff
reset surface
disabled alpha blending when I drew the new HUD surface

Best of all is that this works well on the Draw Event so I don't have to poke holes in it anymore as draw order handles that. However, the text is still slightly less crisp than drawing on the GUI even for some reason, but loads better than what I posted originally. If there is an easy way to draw the characters to a surface so that they appear in scale and position on a surface correctly (because my camera zooms and moves) then I can easily use the GUI event instead. Now that I have a surface method that works on both events, using another surface for the characters to poke holes in it is going to be very easy to do.

There has to be a way to reset the camera matrix or something but barring that, my only other solution would be to clear the app surface after I copied it to my HUD surface to create the mask. But then I'll have to draw the players twice which seems wasteful
 
Last edited:
Top