0

I'm using OpenGL ES 2.0 on Android to add effects to a video. Everything works fine until I add another texture.

What I've tried:

  • Using a Texture2D instead of external (EOS) -> Impossible for an external source (video file)
  • Changed texture to a power of two after using the method that checks if the GPU supports non pow of 2 -> No change
  • Played with the chronological order of GLES20 methods -> No visible change in execution behaviour
  • Binded textures using different index logic -> No change

Not tried yet:

  • Using another bitmap format, but the GLUtils method to load bitmap is supposed to handle the format
  • Using the 'legacy' method to load a texture from a bitmap (GLES instead of GLUtils)
  • Using a array of pixels instead of a bitmap.

But at this point I would just surrender about bitmap and send an Uniform Buffer containing my pixels to the shader...

When my surface is created I do this:

var vertexShader = _videoView.Filtering.Shaders.Item1;
var fragmentShader = _videoView.Filtering.Shaders.Item2;
var progExists = glPrograms.ContainsKey (vertexShader + fragmentShader);
if (!progExists) {
    var prog = createProgram (vertexShader, fragmentShader);
    glPrograms.Add (vertexShader + fragmentShader, prog);
}
_glProgram = glPrograms [vertexShader + fragmentShader];

if (_glProgram == 0) {
    // Should compile with default shaders from GlFilter.
    throw new System.Exception ("Can't create GL Program. There is something wrong with EGL on this device.");
}

I also load my textures:

_aPositionHandle = GLES20.GlGetAttribLocation(_glProgram, "aPosition");
checkGlError("glGetAttribLocation aPosition");
if (_aPositionHandle == -1) {
    throw new RuntimeException(
        "Could not get attrib location for aPosition");
}

_aTextureCoord = GLES20.GlGetAttribLocation(_glProgram,
    "aTextureCoord");
checkGlError("glGetAttribLocation aTextureCoord");
if (_aTextureCoord == -1) {
    throw new RuntimeException (
        "Could not get attrib location for aTextureCoord");
}

_uMVPMatrixHandle = GLES20.GlGetUniformLocation(_glProgram,
    "uMVPMatrix");
checkGlError("glGetUniformLocation uMVPMatrix");
if (_uMVPMatrixHandle == -1) {
    throw new RuntimeException(
        "Could not get attrib location for uMVPMatrix");
}

_uSTMatrixHandle = GLES20.GlGetUniformLocation(_glProgram,
    "uSTMatrix");
checkGlError("glGetUniformLocation uSTMatrix");
if (_uSTMatrixHandle == -1) {
    throw new RuntimeException(
        "Could not get attrib location for uSTMatrix");
}

_texelWidthOffsetUniform = GLES20.GlGetUniformLocation(_glProgram,
    "texelWidthOffset");
checkGlError("glGetUniformLocation texelWidthOffset");
if (_texelWidthOffsetUniform == -1) {
    throw new RuntimeException(
        "Could not get attrib location for texelWidthOffset");
}


_texelHeightOffsetUniform = GLES20.GlGetUniformLocation(_glProgram,
    "texelHeightOffset");
checkGlError("glGetUniformLocation texelHeightOffset");
if (_texelHeightOffsetUniform == -1) {
    throw new RuntimeException(
        "Could not get attrib location for texelHeightOffset");
}

_sTexture2Uniform = GLES20.GlGetUniformLocation(_glProgram,
    "sTexture2");
checkGlError("glGetUniformLocation sTexture2Uniform");
if (_sTexture2Uniform == -1) {
    throw new RuntimeException(
        "Could not get attrib location for sTexture2");
}

int[] textures = new int[1];
GLES20.GlGenTextures(1, textures, 0);
_textureID = textures[0];
GLES20.GlBindTexture(GL_TEXTURE_EXTERNAL_OES, _textureID);
checkGlError("glBindTexture _textureID");
GLES20.GlTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GlTextureMinFilter, GLES20.GlNearest);
GLES20.GlTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GlTextureMagFilter, GLES20.GlLinear);

Still in Surface Created event, I add a texture by uploading a Bitmap

source = Bitmap.CreateBitmap(256, 256, Bitmap.Config.Argb8888);
source.EraseColor(Color.Pink.ToArgb());

for (int x = 0; x < 256; x++)
    for (int y = 0; y < 256; y++) {
        var cof = (int)(System.Math.Cos ((double)x / (double)256) * 255);
        var sif = (int)(System.Math.Sin ((double)y / (double)256) * 255);
        var p = (double)x / (double)256 * 360d;
        var q = (double)y / (double)256 * 360d;
        var c = (int)(((System.Math.Sin (p * q) / 2d) + 0.5d) * 255d);
        source.SetPixel (x, y, Color.Argb (255, c, (c + cof) / 2, (c + sif) / 2));
    }

int[] textures = new int[1];

GLES20.GlGenTextures(1, textures, 0);

_texture2Id = textures[0];

GLUtils.TexImage2D(GLES20.GlTexture2d, 0, source, 0);
source.Recycle ();

GLES20.GlBindTexture(GLES20.GlTexture2d, _texture2Id);

GLES20.GlTexParameterf(GLES20.GlTexture2d, GLES20.GlTextureWrapS, GLES20.GlClampToEdge);
GLES20.GlTexParameterf(GLES20.GlTexture2d, GLES20.GlTextureWrapT, GLES20.GlClampToEdge);
GLES20.GlTexParameterf(GLES20.GlTexture2d, GLES20.GlTextureMagFilter, GLES20.GlLinear);
GLES20.GlTexParameterf(GLES20.GlTexture2d, GLES20.GlTextureMinFilter, GLES20.GlLinear);

_surfaceTexture = new SurfaceTexture(_textureID);
_surfaceTexture.FrameAvailable += _surfaceTexture_FrameAvailable;
_mediaPlayer = new MediaPlayer();

Then in OnDraw:

var fragment = _videoView.Filtering.Shaders.Item2;
var vertex = _videoView.Filtering.Shaders.Item1;

var progExists = glPrograms.ContainsKey (vertex + fragment);
if (!progExists) {
    var prog = createProgram (vertex, fragment);
    glPrograms.Add (vertex + fragment, prog);
}
var progToUse = glPrograms [vertex + fragment];

GLES20.GlClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLES20.GlClear(GLES20.GlColorBufferBit);

lock (syncLock) {
    _surfaceTexture.UpdateTexImage ();
    _surfaceTexture.GetTransformMatrix (_STMatrix);
    updateSurface = false;
}

GLES20.GlViewport(0, 0, _surfaceWidth, _surfaceHeight);

if (progToUse != _glProgram) {
    GLES20.GlUseProgram (progToUse);
}

_glProgram = progToUse;

GLES20.GlUniform1i(_sTexture2Uniform, 1);

GLES20.GlActiveTexture (GLES20.GlTexture0);
GLES20.GlBindTexture (GL_TEXTURE_EXTERNAL_OES, _textureID);

GLES20.GlActiveTexture (GLES20.GlTexture1);
GLES20.GlBindTexture (GLES20.GlTexture2d, _texture2Id);

_triangleVertices.Position (TRIANGLE_VERTICES_DATA_POS_OFFSET);
GLES20.GlVertexAttribPointer (_aPositionHandle, 3, GLES20.GlFloat, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, _triangleVertices);
GLES20.GlEnableVertexAttribArray (_aPositionHandle);

_textureVertices.Position (TRIANGLE_VERTICES_DATA_UV_OFFSET);
GLES20.GlVertexAttribPointer (_aTextureCoord, 2, GLES20.GlFloat, false, TEXTURE_VERTICES_DATA_STRIDE_BYTES, _textureVertices);
GLES20.GlEnableVertexAttribArray (_aTextureCoord);

Android.Opengl.Matrix.SetIdentityM (_MVPMatrix, 0);

GLES20.GlUniformMatrix4fv (_uMVPMatrixHandle, 1, false, _MVPMatrix, 0);
GLES20.GlUniformMatrix4fv (_uSTMatrixHandle, 1, false, _STMatrix, 0);

GLES20.GlDrawArrays(GLES20.GlTriangleStrip, 0, 4);  


The problem is when I hit this line `GLES20.GlUniform1i(_sTexture2Uniform, 1);`

I keep getting this error then setting my bitmap texture uniform in OnDraw.

[Adreno200-ES20] <__load_uniform_float:539>: GL_INVALID_OPERATION

The two errors I get are:

[SurfaceTexture] [unnamed-22096-0] updateTexImage: clearing GL error: 0x502
[Adreno200-ES20] <__load_uniform_int:305>: GL_INVALID_OPERATION

Vertex looks like it:

uniform mat4 uMVPMatrix;
uniform mat4 uSTMatrix;
uniform highp float texelWidthOffset;
uniform highp float texelHeightOffset;
attribute vec4 aPosition;
attribute vec4 aTextureCoord;
varying vec2 vTextureCoord;
void main() {
    highp float useTX = texelWidthOffset;
    highp float useTY = texelHeightOffset;
    gl_Position = uMVPMatrix * aPosition;
    vTextureCoord = (uSTMatrix * aTextureCoord).xy;
}

Fragment looks like it:

#version 150
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform sampler2D sTexture2;
uniform samplerExternalOES sTexture;
varying vec2 vTextureCoord;
varying float texelWidthOffset;
varying float texelHeightOffset;
void main() {
    highp float useTX = texelWidthOffset;
    highp float useTY = texelHeightOffset;
    lowp vec4 inputColor0 = texture2D(sTexture,vTextureCoord);
    lowp vec4 someColor = mix(inputColor0, texture2D(sTexture2,vTextureCoord), useTY);
    lowp vec4 outputColor0 = mix(inputColor0, texture2D(sTexture,vTextureCoord), useTX);
    gl_FragColor=outputColor0;
}

I don't know what to say except "Help!", since I've maybe tried to follow 100+ tutorials to fix this GL_INVALID_OPERATION error. None of the attempts I've made ever result in any colored pixel when mixing OES external source (a movie file) with a bitmap texture.

[EDIT]

Ok. In the code above I was setting _glProgram to the proper created program id, then before using the program, I was saying: "Is _glProgram equals to the proper created program id? If yes, then don't come here to call useProgram." So I was never using the proper program. So there is not a single OpenGL error now, but the screen is black like in zero. This is a good news. I know the pixels from my bitmap texture are correct. (Coded an a trigo algo randomly, happened to form cute polka four holes buttons.) So I'm revising my OpenGL ES code.

[EDIT 2]

I feel like I'm stuck with this problem.

Community
  • 1
  • 1
Léon Pelletier
  • 2,701
  • 2
  • 40
  • 67

1 Answers1

1

There's one very clear problem in your code. In this call sequence:

GLES20.GlGenTextures(1, textures, 0);
_texture2Id = textures[0];

GLUtils.TexImage2D(GLES20.GlTexture2d, 0, source, 0);
source.Recycle ();

GLES20.GlBindTexture(GLES20.GlTexture2d, _texture2Id);

You need to bind the texture before calling GLUtils.texImage2D(). That method will load the texture data into the currently bound texture. The correct order is:

GLES20.GlGenTextures(1, textures, 0);
_texture2Id = textures[0];
GLES20.GlBindTexture(GLES20.GlTexture2d, _texture2Id);

GLUtils.TexImage2D(GLES20.GlTexture2d, 0, source, 0);
source.Recycle ();

This doesn't explain the errors from the glUniform1i() call, though. It can give a GL_INVALID_OPERATION error for the following reasons:

  1. No program is bound.
  2. The location is not valid for the currently bound program.
  3. The type/size used for the call does not match the type/size of the uniform in the shader.

We can probably exclude options 1, or at the very least you should be able to check quickly that the program you just bound is valid.

This leaves the options that the location is invalid, or the location of the wrong uniform. Unless I missed something in the relatively lengthy code you posted, the only logical explanation is that the program used to retrieve the shader locations is not the same as the one you bind for setting the uniforms in onDraw().

I notice that you're using some kind of shader cache to reuse shaders. While that looks fairly straightforward, I would double (and triple, and quadruple) check that it really works as expected. Compare the program id used during setup where you get the uniform locations with the one used when preparing to draw. And make sure that no shaders were destroyed and recreated. And that everything happened in the same thread/context.

Reto Koradi
  • 53,228
  • 8
  • 93
  • 133
  • Thanks. Yes, the program `id` stays the first from the beginning until the ondraw moment. Unfortunately the GlUniform1i was moved above and under in trials and errors without success. Based on your tips, I'll log the id of each element during runtime to quintuple-sextuple check that nothing is disposed and replaced during the whole process. If I have no chance, I'll switch to Buffer objects. I don't do real intensive texture blending. I just need to pass Lookup textures for some filter algo, and that may fit in one or four 64kb blocks. – Léon Pelletier Jul 03 '15 at 02:56
  • 1
    Just for diagnostics, I would also try to put a `glGetUniformLocation()` immediately before the problematic `glUniform()` call, and see if you still get the same uniform location. Plus a generous helping of `glGetError()`, and maybe try a `glValidateProgram()`. – Reto Koradi Jul 03 '15 at 03:32
  • I fixed the errors. (See the Edit - bad assignations and condition preventing useProgram to be called.) Renamed it to LoadAndUseProgram, and setting _glProgram - in - the condition now. :) There is now no errors, but the screen is black, which is not the case for the version with only the OES texture (source is MediaPlayer). – Léon Pelletier Jul 03 '15 at 03:38
  • What is strange is that when integrating just that code to add a second texture coming from a bitmap, my two textures are not usable in the shader code. Hardcoding value, like using texel offsets, do the proper image (ex. a gradient). So either I overwrite the two textures with an invalid value or something is crashed, but not the rendering itself. By the way `GlValidateProgram` works. – Léon Pelletier Jul 03 '15 at 07:53