I'm optimizing a game that works for both, iPhone and Android. I'm using 4 shader to draw the scene and I noticed that if I change one of them to another the fps goes from 32 to 42, even though there's only 1 sprite being drawn with that shader, and the only difference in this 2 shaders is just a product in the fragmente shader.
These are the shaders:
default-2d-tex.shader
#ifdef GL_ES
precision highp float;
precision lowp int;
#endif
#ifdef VERTEX
uniform mat4 umvp;
attribute vec4 avertex;
attribute vec2 auv;
varying vec2 vuv;
void main()
{
// Pass the texture coordinate attribute to a varying.
vuv = auv;
// Here we set the final position to this vertex.
gl_Position = umvp * avertex;
}
#endif
#ifdef FRAGMENT
uniform sampler2D map0;
uniform vec4 ucolor;
varying vec2 vuv;
void main()
{
gl_FragColor = texture2D(map0, vuv) * ucolor;
}
#endif
default-2d-tex-white.shader
#ifdef GL_ES
precision highp float;
precision lowp int;
#endif
#ifdef VERTEX
uniform mat4 umvp;
attribute vec4 avertex;
attribute vec2 auv;
varying vec2 vuv;
void main()
{
// Pass the texture coordinate attribute to a varying.
vuv = auv;
// Here we set the final position to this vertex.
gl_Position = umvp * avertex;
}
#endif
#ifdef FRAGMENT
uniform sampler2D map0;
varying vec2 vuv;
void main()
{
gl_FragColor = texture2D(map0, vuv);
}
#endif
Again,
If I modify default-2d-tex.shader and remove the product "* ucolor", the fps goes from 32 to 42, and I'm using it for just one sprite in the scene!
Is this normal? Why is this shader being so slow and how can I improve it?
EDIT:
I see this performance slowdown on both iPod and Android in an equal ratio. Both are PowerVr SGX GPUs (iPod 3rd gen and Samsung Galaxy SL -PowerVR SGX 530-). iOS version is 4.1 and Android is 2.3.3
The sprite I'm drawing is scaled to fill the screen (scaled to 4x) and I'm drawing it once per frame. It's taken from a texture map so the texture is actually larger (1024x1024) but the portion taken is 80x120. Alpha blending is enabled.
EDIT 2
I made a mistake. The sprite is scaled 11x: its 32x48.
If I don't draw that sprite at all, fps goes to 45. I'm drawing a lot of sprites in the scene, why is that one taking so much time? Could it be because it's scaled so much?
When you remove "* ucolor" from the abovementioned code many things happen:
uniform ucolor becomes unused
GLSL compiler removes this uniform from the active uniforms set
when you have you program linked the ID of the missing uniform
becomes -1
when you do glUniform4fv( -1, value ) for the removed uniform it
just exits instantly, without any update
This is pretty much less work compared to having "* ucolor" in place.
And additionally, this is one less operation per fragment in the fragment shader.
Your problem is in your fragment shader in default-2d-tex.shader. You have
uniform vec4 ucolor;
That means, that each of your color's components (RGBA) will be converted to 32-bits float value. And this drops performance heavily.
Should be:
uniform lowp vec4 ucolor;
Related
I'm trying ro render a 3d mesh on android, the positions of the vertices are provided like so:
var positionHandle = GLES20.glGetAttribLocation(program, "vPosition").also {
GLES20.glEnableVertexAttribArray(it)
GLES20.glVertexAttribPointer(
it, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer
)
}
My vertex shader:
uniform mat4 uMVPMatrix;
attribute vec4 vPosition;
varying vec4 positionOut;
void main() {
gl_Position = uMVPMatrix * vPosition;
positionOut = vPosition;
}
Fragment shader:
varying vec4 positionOut;
precision mediump float;
void main() {
gl_FragColor = vec4(1, 0,1,1);
}
This code works fine on one device (Android 9 on OnePlus device) but it doesn't work on (Android 10 Samsung Galaxy device) and no errors are thrown.
If I remove the line varying vec4 positionOut; from the fragment shader the render works.
The question is why only the definition of a varying variable breaks the render? I don't use the variable now but I want to use it in the next step.
Your code is invalid as per the spec:
The fragment language has no default precision qualifier for floating point types. Hence for float, floating
point vector and matrix variable declarations, either the declaration must include a precision qualifier or
the default float precision must have been previously declared.
So you either have to declare it as for example varying mediump ec4 positionOut;, or you put into some scope of a precision statement.
I'm rendering a geometry in WebGL and am getting different results on Chrome for Android (unwanted artifacts, left) and Chrome for Windows (right):
I've tried:
using WebGL2 and WebGL contexts
using gl.UNSIGNED_INT and gl.UNSIGNED_SHORT when passing the index buffer
rounding all attribute values to 4 decimals after the comma
Here's some of the code:
I've "dumbed down" my vertex shader to narrow down the issue:
#version 100
precision mediump float;
attribute vec3 aPosition;
attribute vec3 aColor;
attribute vec3 aNormal;
varying vec3 vColor;
varying vec3 vNormal;
varying vec3 vPosition;
varying mat4 vView;
uniform mat4 uWorld;
uniform mat4 uView;
uniform mat4 uProjection;
uniform mat3 uNormal;
uniform float uTime;
void main() {
vColor = aColor;
vNormal = uNormal * aNormal;
vPosition = (uWorld * vec4(aPosition, 1.0)).xyz;
gl_Position = uProjection * uView * uWorld * vec4(aPosition, 1.0);
}
I'm passing the attributes via an interleaved buffer (all values rounded to four decimals after the comma):
gl.bindBuffer(gl.ARRAY_BUFFER, this.interleaved.buffer)
const bytesPerElement = 4
gl.vertexAttribPointer(this.interleaved.attribLocation.position, 3, gl.FLOAT, gl.FALSE, bytesPerElement * 9, bytesPerElement * 0)
gl.vertexAttribPointer(this.interleaved.attribLocation.normal, 3, gl.FLOAT, gl.FALSE, bytesPerElement * 9, bytesPerElement * 3)
gl.vertexAttribPointer(this.interleaved.attribLocation.color, 3, gl.FLOAT, gl.FALSE, bytesPerElement * 9, bytesPerElement * 6)
I'm using an index buffer to draw the geometry:
gl.drawElements(gl.TRIANGLES, this.indices.length, gl.UNSIGNED_INT, 0)
The indices range from 0..3599, hence gl.UNSIGNED_INT should be large enough.
I'm not getting any error messages. On Windows everything renders fine, just Chrome on Android has artifacts.
The artifacts are caused by the lack of precision in shaders on different devices. Using precision highp float; fixes the issue.
lowp, mediump and highp correspond to different values on different hardware and only have an effect on OpenGL ES platforms. Desktop platforms tap into a full implementation of OpenGL or Direct X (as opposed to OpenGL ES), hence, on desktop machines these qualifiers all correspond to the same values. Mobile WebGL typically taps into OpenGL ES, hence on mobile these qualifiers correspond to different values.
In this example lowp and mediump cause glitches on Android and need to replaced with highp.
Generally, if performance is important, using the lowest possible precision is recommended to reduce shader execution time.
WebGLRenderingContext.getShaderPrecisionFormat() returns the precision for the shader data types, for vertex and fragment shaders, respectively (MDN). To use the lowest possible precision, the shaders used can be prefixed with the required precision qualifier, based on WebGLRenderingContext.getShaderPrecisionFormat().
E.g.
const lowPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.LOW_FLOAT)
const mediumPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT)
const highPrecisionFormat = gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT)
if (lowPrecisionFormat.precision >= 23)
shaderString = "precision lowp float;" + shaderString
else if (mediumPrecisionFormat.precision >= 23)
shaderString = "precision mediump float;" + shaderString
else
shaderString = "precision highp float;" + shaderString
On the Android hardware I've tested gl.getShaderPrecisionFormat() on, lowp and mediump returned the same results (which are lower than on Windows), while highp returned as high a precision as on my Windows machine.
I would like to be able to pass more per-vertex-data to my own custom shaders in kivy than the usual vertex coords + texture coords. Specifically, I would like to pass a value that says which animation frame should be used in selecting the texture coords.
I found an example (http://shadowmint.blogspot.com/2013/10/kivy-textured-quad-easy-right-no.html), and succeeded in changing the format of the vertices passed to a mesh using an argument to the constructor of the Mesh, like this:
Mesh(mode = 'triangles', fmt=[('v_pos', 2, 'float'),
('v_tex0', 2, 'float'),
('v_frame_i', 1, 'float')]
I can then set the vertices to be drawn to something like this:
vertices = [x-r,y-r, uvpos[0],uvpos[1],animationFrame,
x-r,y+r, uvpos[0],uvpos[1]+uvsize[1],animationFrame,
x+r,y-r, uvpos[0]+uvsize[0],uvpos[1],animationFrame,
x+r,y+r, uvpos[0]+uvsize[0],uvpos[1]+uvsize[1],animationFrame,
x+r,y-r, uvpos[0]+uvsize[0],uvpos[1],animationFrame,
x-r,y+r, uvpos[0],uvpos[1]+uvsize[1],animationFrame,
]
..this works well when I run in Ubuntu, but when I run on my android device the drawn texture either doesn't draw, or it looks like the vertex or texture coordinate data is corrupt / not aligned or something.
Here is my shader code in case that is relevant. Again, this all behaves as I want it to when I run in ubuntu, but not when I run on android device.
---VERTEX SHADER---
#ifdef GL_ES
precision highp float;
#endif
/* vertex attributes */
attribute vec2 v_pos;
attribute vec2 v_tex0;
attribute float v_frame_i; // for animation
/* uniform variables */
uniform mat4 modelview_mat;
uniform mat4 projection_mat;
uniform vec4 color;
uniform float opacity;
uniform float sqrtNumFrames; // the width/height of the sprite-sheet
uniform float frameWidth;
/* Outputs to the fragment shader */
varying vec4 frag_color;
varying vec2 tc;
void main() {
frag_color = color * vec4(1.0, 1.0, 1.0, opacity);
gl_Position = projection_mat * modelview_mat * vec4(v_pos.xy, 0.0, 1.0);
float f = round(v_frame_i);
tc = v_tex0;
float w = (1.0/sqrtNumFrames);
tc *= w;
tc.x += w*mod(f,sqrtNumFrames); //////////// I think that the problem might
tc.y += w*round(f / sqrtNumFrames); ///////////// be related to this code, here?
}
---FRAGMENT SHADER---
#ifdef GL_ES
precision highp float;
#endif
/* Outputs from the vertex shader */
varying vec4 frag_color;
varying vec2 tc;
/* uniform texture samplers */
uniform sampler2D texture0;
uniform vec2 player_pos;
uniform vec2 window_size; // in pixels
void main (void){
gl_FragColor = frag_color * texture2D(texture0, tc);
}
I wonder if it may have to do with a version of GLSL and int / float math (in particular in identifying which image from the sprite sheet to draw, see the comments in the glsl code. One version is running on my desktop and another on the device?
Any suggestions for things to experiment with would be much appreciated!
After looking at the log from the running version on the android device (a Moto X phone), I saw that the custom shader was not linking. This appeared to be due to the use of the function round(x), which I replaced with floor(x+0.5) in both cases, and the shader now works on the phone and my desktop properly.
I think the problem is that the version of GLSL supported on the phone and on my PC are different..but I am not 100% certain about this.
I try to make an app with OpenGLES 2. There are 2 devices for testing.
Unfortunately there seems to be a difference between both.
I have the following "basic" shader code for testing:
// vertex shader
uniform mat4 uVMatrix; // View Matrix
uniform mat4 uPMatrix; // perspective Matrix
uniform vec3 uLight1Pos; // LightPos
attribute vec4 aPosition;
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
mat4 MVPMatrix = uPMatrix * uVMatrix;
gl_Position = MVPMatrix * aPosition;
}
// fragment shader
uniform mat4 uVMatrix;
uniform vec3 uLight1Pos;
varying vec4 vColor;
varying vec3 vPosition;
void main() {
vec3 light2Pos = uLight1Pos;
gl_FragColor = vColor;
}
The problem is, some uniforms can't be found.
I attach and link the shader as usual but checking the IDs like:
uVMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uVMatrix");
uPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uPMatrix");
uLight1PosHandle = GLES20.glGetUniformLocation(mProgram, "uLight1Pos");
I get different values.
On my Galaxy S1 it is 2, 1, 3 (so all valid values - even though a weird order).
On my Galaxy S3 it is 0, 1, -1 (so the last one can not be found).
What am I doing wrong? Do I have to declare uniform differently on a S3 (Mali GPU)?
I realized that I have to "use" the uniforms in order to get a reference. That is the reason why I wrote vec3 light2Pos = uLight1Pos; If I dont do this, I also get no reference on the S1.
Thank you for your help!
Tobias
- EDIT -
Weird enough, I tried to change the uniform of the vertex shader from vec3 to a mat4:
uniform mat4 uLight1Pos;
mat4 lPos = uLight1Pos;
It appears that matrices work fine and I can get a handle for it when using a matrix. How comes?
Your "use" doesn't have a contribution to the output fragcolor. You just proved the S3 has a better compiler than the S1 for removing dead code.
GLSL compilers tend to eliminate unused uniforms which don't contribute to the result. This behavior is in compliance with OpenGL specification.
In your case, the compiler is allowed to eliminate the dead line vec3 light2Pos = uLight1Pos; rendering the uLight1Pos uniform useless.
I'm using a fragment shader that uses dFdy dFdx functions to calculate the normal of the
face to view in a flat appearance. This shader has been running ok in gles 2.0 and 3.0. Inexplicably, shader don't work in Android 4.4 ( KitKat - gles3.0 ).
(Solved!!.. individual derivatives for each component, solve the problem).
In order check error, i prepared these shaders :
//Vertex Shader
#version 300 es
precision highp float;
precision highp int;
uniform mat4 PMatrix; //Projection Matrix (varies according to camera)
uniform mat4 MVMatrix; //Model View Matrix (no change)
in vec3 vPosition;
out vec3 vPos;
main()
{
gl_Position=PMatrix * MVMatrix * vec4(vPosition.xyz,1.0);
vPos = (MVMatrix * vec4(vPosition.xyz,1.0)).xyz;
}
// Fragment shader
#version 300 es
#extension GL_OES_standard_derivatives : enable
precision highp float;
precision highp int;
in vec3 vPos;
main()
{
// don't run correctly
// vec3 fdx = dFdx(vPos);
// vec3 fdy = dFdy(vPos);
// ***Solved!*** this run correctly in KitKat
vec3 fdx = vec3(dFdx(vPos.x),dFdx(vPos.y),dFdx(vPos.z));
vec3 fdy = vec3(dFdy(vPos.x),dFdy(vPos.y),dFdy(vPos.z));
vec3 N = normalize(cross(fdx,fdy));
fragColor = vec4(N,1.0);
}
Drawing a cube, in Android<4.4 colors remain fixed for each side, independently of camera position (correct, the color identifies each normal-face). In Android 4.4 , colors vary if you move the camera.
Analyzing each derived separately :
1.- fragColor=vec4(normalize( fdx ), 1.0); colors are constantly changing (wrong)
2.- fragColor=vec4(normalize( fdy ), 1.0); the colors remain quasi-stable (quasi-ok)
A bug in the implementation of these features in Android 4.4?
We are doing something wrong?
You said that this was originally a GLES2 shader, and you are using highp in a fragment shader unconditionally? That is a disaster waiting to happen because GLES2 implementations are not required to support highp in fragment shaders. Likewise, support for dFdx (...), dFdy (...) and fwidth (...) is optional.
You need to check for GL_OES_standard_derivatives and GL_FRAGMENT_PRECISION_HIGH in the GLES2 implementation of this fragment shader.
To that end, you might consider the accuracy hint for derivatives:
GL_FRAGMENT_SHADER_DERIVATIVE_HINT_OES (GLES2, if the extension is supported)
GL_FRAGMENT_SHADER_DERIVATIVE_HINT (GLES3)
Individual derivatives in each component solve the problem in KitKat:
// Replace vec3 fdx = dFdx(vPos) by:
vec3 fdx = vec3(dFdx(vPos.x),dFdx(vPos.y),dFdx(vPos.z));
// Replace vec3 fdy = dFdy(vPos) by:
vec3 fdy = vec3(dFdy(vPos.x),dFdy(vPos.y),dFdy(vPos.z));
I've seen similar things on some AMD desktop setups.
Where dFdy( .xyz ) worked fine on NVIDIA/intel I had to do the derivate per-component to get it correctly for some AMD cards.