Issue with texture with transparency when using open gl es2.0 (google angle) on macos

I am running into a very weird issue and I do not know where to start to ask for help. I am using raylib which under the cover uses glfw. I actually have a project on github to quickly and easily use raylib with Google Angle on macos. Due to the number of frameworks used, I do not know where/which layer the issue is…

The issue is best described with a screenshot:

raylib-egl-issue

On the left side, it is using raylib / opengl es 2.0 / google angle

INFO: GLAD: OpenGL ES2.0 loaded successfully
INFO: GL: Supported extensions count: 101
INFO: GL: OpenGL device information:
INFO:     > Vendor:   Google Inc. (Apple)
INFO:     > Renderer: ANGLE (Apple, Apple M2 Max, OpenGL 4.1 Metal - 83.1)
INFO:     > Version:  OpenGL ES 3.0.0 (ANGLE 2.1.20982 git hash: f3e3810b917c)
INFO:     > GLSL:     OpenGL ES GLSL ES 3.00 (ANGLE 2.1.20982 git hash: f3e3810b917c)

On the right side of the image it is using the build-in OpenGL api (yet deprecated) on macOS

INFO: GLAD: OpenGL extensions loaded successfully
INFO: GL: Supported extensions count: 43
INFO: GL: OpenGL device information:
INFO:     > Vendor:   Apple
INFO:     > Renderer: Apple M2 Max
INFO:     > Version:  4.1 Metal - 83.1
INFO:     > GLSL:     4.10

What the code does is simply clearing the background to color {153, 153, 153, 255} then rendering a texture.

The texture rendered on the left side is incorrect and has a “halo” around it, where there is transparency involved.

Does anybody on this forum would have any idea what the issue could be? At the end of the day I feel like raylib is just a thin wrapper around glfw which makes me think that the issue could be in glfw. But I really don’t know.

Any help troubleshooting would be appreciated.

Thank you

GLFW provides a simple API for creating windows, contexts and surfaces, receiving input and events. It’s not a graphics API. So it’s unlikely that the issue you describe has anything to do with GLFW.

I think the issue is thus more related to the GL API in use and how you are using it. Have you checked that you are setting the states which could be relevant for this to the same values for both cases (blend factors etc.)?

You should also see if you can debug this with a tool like RenderDoc to check that you have the same states etc. in both cases.

Thank you for your response. Using a tool like RenderDoc sounds like a great idea I didn’t think of. I just have to find a tool that works on macOS (RenderDoc does not)

If you have a Virtual Machine like Parallels which supports OpenGL you could run your tests and RenderDoc on Linux.

Mac OS used to have a tool called OpenGL Profiler which can show state, but I don’t know if it will work on modern Macs.

I don’t know much about raylib, but having halo around transparency edges usually means blending alpha is not done premultiplied. You pretty much always want premulitplied alpha when blending.

See these:
https://www.realtimerendering.com/blog/gpus-prefer-premultiplication/

http://www.adriancourreges.com/blog/2017/05/09/beware-of-transparent-pixels/

Thank you for the links. It definitely seems like a blending issue of some sort. That being said I am now able to reproduce the problem without even using a texture. Just drawing a rectangle as I detailed in this github issue.

Although I could technically do the premultiplying alpha on the fly on the textures I am using, I don’t really have controls over other textures (like the font texture generated by raylib), nor the drawing that ImGui does (like all the colors in ImGui are wrong due to this issue).

I hope there is a solution that happens at configuration time (when OpenGL is configured).

That looks like sRGB vs non-sRGB framebuffer difference.

R=21 is what you get when blending 40 with 137 in linear space.
But R=82 is what you get when blending 40 with 137 and converting output from linear to sRGB.

This was my thought too.

Indeed there’s a difference between OpenGL and OpenGL ES in that the sRGB framebuffer conversion and blending on OpenGL ES cannot be enabled/disabled as they can for OpenGL.

For example see GL_FRAMEBUFFER_SRGB on glEnable for OpenGL:
https://registry.khronos.org/OpenGL-Refpages/gl4/html/glEnable.xhtml

and for OpenGL ES this is not present:
https://registry.khronos.org/OpenGL-Refpages/es3/html/glEnable.xhtml

There is a note on this in the GLFW Window documentation under GLFW_SRGB_CAPABLE copied below:

Note

  • OpenGL: If enabled and supported by the system, the GL_FRAMEBUFFER_SRGB enable will control sRGB rendering. By default, sRGB rendering will be disabled.
  • OpenGL ES: If enabled and supported by the system, the context will always have sRGB rendering enabled.

Thank you so much for pointing out what the issue is exactly. Concretely, what can I do to fix it?

If this is indeed the issue then there are two main approaches to solving it for OpenGL ES:

  1. Turn off sRGB by not setting the GLFW_SRGB_CAPABLE hint (it should default to GLFW_FALSE). Then in your 3D render you can implement an sRGB like gamma corrected output with pow(colour,vec3(1.0/2.2)) (this is for a gamma of 2.2).
  2. Keep sRGB on and convert the output of your 2D UI to linear using pow(colour,vec3(2.2)).

Note that the actual sRGB curve is slightly more complex but this should get you most of the way there.

The first is likely easier but looses some precision in your 3D scene output colours.

@dougbinks @mmozeiko Thank you so much for all this help.

I will try to see if I can make it work. Note that I am not doing any 3D rendering. I am using raylib as a backend to ImGui. So the example given in the github issue, although stripped down to the most basic use case for reproducing the issue, is pretty much the extent of what I do

int main()
{

  InitWindow(800, 450, "raylib issue");

  constexpr Color ImGuiCol_FrameBg{40, 73, 122, 137};

  while (!WindowShouldClose())
  {
    BeginDrawing();
    ClearBackground(BLACK);
    DrawRectangle(10, 10, 100, 100, ImGuiCol_FrameBg);
    EndDrawing();
  }

  CloseWindow();

  return 0;
}

If it is not an option I can pass to GLFW/OpenGL (or raylib), I am not too sure what that would imply. I know that I can write (and use) my own shader in raylib so maybe I need to override the one provided to do the adjustment you are talking about.

For reference, this is the shader that raylib uses:

#if defined(GRAPHICS_API_OPENGL_ES2)
    "#version 100                       \n"
    "precision mediump float;           \n"     // Precision required for OpenGL ES2 (WebGL)
    "varying vec2 fragTexCoord;         \n"
    "varying vec4 fragColor;            \n"
    "uniform sampler2D texture0;        \n"
    "uniform vec4 colDiffuse;           \n"
    "void main()                        \n"
    "{                                  \n"
    "    vec4 texelColor = texture2D(texture0, fragTexCoord); \n"
    "    gl_FragColor = texelColor*colDiffuse*fragColor;      \n"
    "}                                  \n";
#endif

Looking at the code for Raylib on Github, it does not appear to be setting GLFW_SRGB_CAPABLE and the default is false. So it could be that the ImGui backend you are using is gamma correcting when it shouldn’t be.

I can certainly try to tweak the raylib’s code.

Are you saying that I should call glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE); at some point in the lifecyle and that would fix the issue?

Does it need to be called before the window is created?

In any case, assume there is no ImGui. The snippet of code I copy/pasted in my previous post does not use ImGui and exhibits the problem. If I can fix this code, then it will automatically fix the ImGui issue…

From what I can see of Raylib it sets the window hints to defaults itself, so you can’t use window hints from GLFW yourself.

So I recommend asking on the Raylib forums / github about this.

Since raylib is copied into my project I “hacked” the code and did glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE); right before glfwCreateWindow is called and it unfortunately has no effect :frowning: (note that I tried with GLFW_FALSE as well)

@mmozeiko I ran another experiment. I am not using textures anymore (raylib DrawRectangle ends up sampling a 1x1 pixel texture): I simply used this shader

#version 100
precision mediump float; // Precision required for OpenGL ES2 (WebGL)
varying vec2 fragTexCoord;
varying vec4 fragColor;
uniform sampler2D texture0;
void main()
{
    gl_FragColor = vec4(0.16, 0.29, 0.48, 0.54);
//    gl_FragColor = vec4(0.16, 0.29, 0.48, 1);
}

When I render (using the 0.54 alpha), I end up with a RGB value of 83/101/127 (which is not what I am expecting)
When I render uncommenting the second (hence it is opaque), I end up with a RGB value of 41/74/122 which is right.

From this experiment, it is clear that it is not the texture sampling which is wrong, since there is no texture.

So I am wondering what or who is doing the wrong alpha blending. What tells OpenGL to do it one way vs another?

Those things are not controlled by texture sampling. Premultiplied alpha is just a setting on blend mode - but values can be produced by shader, not necessarily by texture sampling. And framebuffer sRGB is setting on framebuffer, which is set by WGL/EGL or other means of creating GL context, and/or glEnable setting when supported.

In your example pow(0.16*0.54, 1/2.4)*255 = 83, which coressponds what happens when linear output of shader is converted to sRGB colorspace. To “counter” that you must not set sRGB on framebuffer, or manually apply pow(2.4) to be inverse of sRGB conversion. pow with 2.2 or 2.4 is just an approximation to sRGB equation, it’s a bit more involved - see http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html link with actual formulas.

After stepping through the code (raylib + glfw) I finally found a solution!

Defining the Angle platform type (GLFW_ANGLE_PLATFORM_TYPE) to be Metal (GLFW_ANGLE_PLATFORM_TYPE_METAL) totally fixes the issue:

// MUST be done BEFORE calling init (`glfwInit`)
glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE, GLFW_ANGLE_PLATFORM_TYPE_METAL);

Note that I do not know or understand what happens after setting this flag, but it just fixes the problem!

It does fix the issue BUT the framerate takes a massive hit. From 17000fps to 200fps (and very erratic). I guess I can’t win :disappointed_relieved:

My guess would be that it with such setting it chooses OpenGL or software-rendered backend instead of Metal. You can use glGetString with GL_VENDOR / GL_RENDERER to check what you get.

It really should be a question of disabling sRGB framebuffer setting for GLES when glfw window is created. Or other way around - enabling sRGB for desktop GL.