4
\$\begingroup\$

I'm trying to combine an ordinary shader with a Single Distance Field shader (for fonts and scalable icons) into one shader program. The idea is simple - if uv.x is less than 1.0, it is drawn as usual, and if more than 1.0 then SDF is used (using GL_REPEAT uv.x and uv.x + 1.0 look similar)

Fragment shader code:

precision mediump float;
precision mediump int;

uniform sampler2D u_texture;
varying vec4 v_color;
varying vec2 v_texCoords;
uniform float u_smoothing;

void main() {
  // SDF
  float distance = texture2D(u_texture, v_texCoords).a;
  float alpha = smoothstep(0.5 - u_smoothing, 0.5 + u_smoothing, distance);
  vec4 sdfColor = vec4(v_color.rgb, alpha * v_color.a);

  // Regular
  vec4 regularColor = v_color * texture2D(u_texture, v_texCoords);

  // Making a choice
  gl_FragColor = mix(regularColor, sdfColor, step(1.0, v_texCoords.x));
}

It works and looks fine on desktop and most of popular Android devices but not on Oneplus One, Xiaomi Redmi 4A and Moto G devices. This is how it must look like and how it actually looks on desktops and many other devices (Samsung S8, Xiaomi 3s, Pixel 2 etc.): Normaly rendered

And this is how it looks like on Moto G, Oneplus One & some others: enter image description here

I tried to find a problematic function, but without success. First I tried to use the usual condition instead of mix():

void main() {
  // ...

  // Making a choice
  // gl_FragColor = mix(regularColor, sdfColor, step(1.0, v_texCoords.x));
  if (v_texCoords.x < 1.0) {
    gl_FragColor = regularColor;
  } else {
    gl_FragColor = sdfColor;
  }
}

Nothing has changed - this shader works well on most devices and is still bad on some others. I decided that the problem lies in the calculation of sdfColor and changed fragment shader code a bit to check:

void main() {
  // ...

  // Making a choice
  gl_FragColor = sdfColor;  // Always using SDF
}

But this shader worked equally on all devices, which indicates that sdfColor is calculated correctly. This next screenshot was taken on Moto G: enter image description here

Then I tried to differentiate the regions of textures with different colors:

void main() {
  // ...

  // Making a choice
  if (v_texCoords.x < 1.0) {
    gl_FragColor = regularColor;
    gl_FragColor.r = 1.0;  // Full red channel for regular texture regions without SDF (UV.x < 1.0)
  } else {
    gl_FragColor = sdfColor;
    gl_FragColor.r = 0.0;  // No red channel for SDF 
  }
}

On Moto G and other devices it looks as expected: enter image description here

It seems to me that using mix() or conditions implicitly changes something else.

Update 1:

Using different precision qualifier (highp / lowp) or completely removing it didn't help and made no visual changes.

This bug starts to appear on desktop (OpenGL) if I add #version 100. Changing it to #version 110 or higher returns normal rendering.

When #version 100 is used on Android (OpenGL ES 2.0) it renders as if there were no #version qualifier at all (exactly the same as described in my question - on Nexus 5x it looks good, on Moto G it doesn't).

Using #version 300 es on Android (OpenGL ES 3.0, looks like it is supported Moto G 3rd) with respective changes to the code (uniform, varying, texture2D replaced by in, out, texture) doesn't fix the problem (good on Nexus, bad on Moto).

Precision qualifier was not used in both cases. Looks like #version does no effect to this problem on Android but makes it appear on desktop when set to 100 (is it OpenGL 1.0?...)

Update 2:

Writing v_texCoords.x to the separate float and using it in mix() fixes this bug but only for desktop when #version 100 is also used:

#version 100
uniform sampler2D u_texture;
varying vec4 v_color;
varying vec2 v_texCoords;
uniform float u_smoothing;

void main() {
  float x = v_texCoords.x; // Writing to the separate float

  // SDF
  float distance = texture2D(u_texture, v_texCoords).a;
  float alpha = smoothstep(0.5 - u_smoothing, 0.5 + u_smoothing, distance);
  vec4 sdfColor = vec4(v_color.rgb, alpha * v_color.a);

  // Regular
  vec4 regularColor = v_color * texture2D(u_texture, v_texCoords);

  // Making a choice
  gl_FragColor = mix(regularColor, sdfColor, step(1.0, x)); // Using x instead of v_texCoords.x
}

It doesn't fix the problem on Moto G. Also when I move float x = v_texCoords.x; to the line right before gl_FragColor = ... the problem remains on desktop:

void main() {
  // SDF
  float distance = texture2D(u_texture, v_texCoords).a;
  float alpha = smoothstep(0.5 - u_smoothing, 0.5 + u_smoothing, distance);
  vec4 sdfColor = vec4(v_color.rgb, alpha * v_color.a);

  // Regular
  vec4 regularColor = v_color * texture2D(u_texture, v_texCoords);

  // Making a choice
  float x = v_texCoords.x; // Writing to the separate float, this time it doesn't fix the bug
  gl_FragColor = mix(regularColor, sdfColor, step(1.0, x)); // Using x instead of v_texCoords.x
}
\$\endgroup\$
  • \$\begingroup\$ I always calculated sdfColor in the same way. I did not replace it with anything else because it seemed to me that sdfColor is calculated correctly (when I used only sdfColor it worked well) \$\endgroup\$ – therainycat Jun 13 '18 at 9:41
  • \$\begingroup\$ No, replacing mix() with condition doesn't fix the problem, it behaves exactly as with mix() \$\endgroup\$ – therainycat Jun 13 '18 at 9:50
  • \$\begingroup\$ Oops, my bad, I have been misunderstanding the problem, sorry for wasting your time. \$\endgroup\$ – gan_ Jun 13 '18 at 9:51
  • \$\begingroup\$ Do you actually have all those devices how did you manage to test this? Also you might want to try vulkan and see if you get the same issues. The shader wouldn't have to change, but the driver will be consuming SPIR-V instead of the actual shader code text, so its possible that there are driver implementation differences for EGL on those devices that cause this issue, if it worked the same everywhere on vulkan it would hint a problem with the implementation of EGL on those devices, or you are using undefined behavior. \$\endgroup\$ – opa Jun 13 '18 at 15:24
  • \$\begingroup\$ also try using highp instead of mediump, or get rid of the precision qualifiers as recommended for more modern versions of OpenGL ES. Try lowp and see if you see any noticable difference as well and try doing the same for int. Maybe try making the code even simpler for the compiler by always assigning first before using members, its possible the GLSL compiler is bugged on those systems. Also you should probably specify the GLSL version with #version. \$\endgroup\$ – opa Jun 13 '18 at 15:34
0
\$\begingroup\$

For whatever reason the texel values are different ( > 1.0) on the other platforms. Are they using high-res screens? The pixel depth might be double (e.g. check for uv.x > 2.0) Might need to write a separate shader for those devices.

\$\endgroup\$
  • \$\begingroup\$ Why do you think the texel values are different? \$\endgroup\$ – therainycat Jun 18 '18 at 22:44
  • \$\begingroup\$ try it out and see. Clearly the shader never reaches the ( > 1.0) branch \$\endgroup\$ – Maverick Jun 19 '18 at 11:41
  • \$\begingroup\$ uv.x is set to >1.0 by me in the code outside of GLSL to mark texture regions that must be drawn with SDF. It is a part of vertex data passed to the shader and it is same for every platform. What are you suggesting to try? \$\endgroup\$ – therainycat Jun 19 '18 at 11:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.