5

I've been working on a project in which a user can interact with a GLSurfaceView to draw shapes on a screen. This all works fine and dandy, but now I'm attempting to do two things: 1) create a thumbnail of their drawing, and 2) save their drawing. It's important to note that the user can draw an image which is larger than the screen.

As far as my research shows, this is best achieved using a Bitmap (which can be 1) rendered to a Canvas for a thumbnail, and 2) be saved to the file system, which achieves both of my goals).

Initially, I tried to read the Renderer from the GLSurfaceView via glReadPixels, but it turns out I couldn't get the off-screen data from this. Instead, I opted to do an off-screen buffer to produce a render of the image, which could be converted to a Bitmap.

I found a lovely post which provided a code for a class called PixelBuffer, which I am now using. (I've made a few tweaks, but all issues I've had occur with or without said tweaking.)

Now, when using the getBitmap() of the code (which I will post below in the event the PixelBuffer forum thread is unreadable), I get a bunch of called unimplemented OpenGL ES API. This struck me as odd at first, so I did some investigating. It turns out that, for some reason, the PixelBuffer class is using OpenGL ES 2.0, while the GLSurfaceView is using OpenGL ES 1.1. The device I'm using (Galaxy Nexus) doesn't support 2.0. (And moreover, I'd like to support the largest range of devices possible.)

So, here is my question: How can I force my PixelBuffer class to use the OpenGL ES 1.1 API? I have already added the following in my Manifest:

<uses-feature android:glEsVersion="0x00010001" />

Additionally, I have attempted to set the version using int[] version = new int[] { 1, 1 };, to no avail.

The PixelBuffer code I'm using: Link. The line in question that calls the Renderer is on line 91. The version code is on line 39-ish onward.

This is the code I use to create the PixelBuffer, called from the GLSurfaceView object:

    setEGLConfigChooser(8, 8, 8, 8, 0, 0);
    mRenderer = new MyRenderer(this);
    setRenderer(mRenderer);
    mPixelBuffer = new CPPixelBuffer(1440, 1280); // TODO Temporary hardcode for Galaxy Nexus wallpaper size
    mPixelBuffer.setRenderer(mRenderer);

If it is not possible to use version 1.1 for an offscreen render, what is the best way to convert my GL surface to a Bitmap using version 1.1 or lower?

EDIT: Through some testing, I discovered that using the below code causes these errors:

// int[] version = new int[2];
int[] version = new int[] { // When using this line, as well as the other marked lines, instead of the line above, the errors are produced.
    1, 1 // Marked
}; // Marked
int[] attribList = new int[] {
    EGL_WIDTH, mWidth,
    EGL_HEIGHT, mHeight,
    EGL_VERSION, 1, // Marked
    EGL_NONE
};

06-11 15:41:52.316: E/libEGL(5618): eglMakeCurrent:674 error 3009 (EGL_BAD_MATCH)
06-11 15:41:52.316: E/libEGL(5618): call to OpenGL ES API with no current context (logged once per thread)

However, neither of these setups alleviate the issue at hand. They are just both present in my PasteBin'd code, so I thought it would be good to point it out.

Cat
  • 66,919
  • 24
  • 133
  • 141
  • Actually Galaxy Nexus supports OpenGL ES 2.0 just fine. Which function call exactly gives you "unimplemented OpenGL ES API"? If that is PBufferSurface creation, then try to use different attributes for it. Or better just skip it, and use glGreadPixels from framebuffer. – Mārtiņš Možeiko Jun 11 '12 at 03:21
  • All methods (in the `onDrawFrame` call) give it to me. Even `gl.glLoadIdentity()`; my window is literally spammed by them. It's nothing 2.0-specific, which is why this is so odd to me. _(However, this doesn't resolve the issue for older 1.1-compatible devices...)_ – Cat Jun 11 '12 at 03:25
  • Do you have GL context created for the thread where you are invoking gl... functions? – Mārtiņš Možeiko Jun 11 '12 at 03:34
  • Hmm... good question. I create the EGL context and get the GL from that (as seen in the Pastebin link). I think that's the only separate context created, though. – Cat Jun 11 '12 at 03:37
  • Shot in the dark: There's a `setEGLContextClientVersion` method on `GLSurfaceView` that internally sets the `mEGLContextClientVersion` field. [Have a look](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.4_r1.2/android/opengl/GLSurfaceView.java/) at how this field is interpreted. – Stefan Hanke Jun 11 '12 at 07:18
  • @StefanHanke Thanks for the idea... however, I have tried setting this (both to `1` and `2`) to no avail. The `GL_VERSION` string prints out "OpenGL ES API 2.0-build@" regardless of the EGL Client Version parameter. – Cat Jun 11 '12 at 07:29

1 Answers1

2

I didnt find for some reason the link to post a comment, so sorry about posting this as an answer. About the GL thread, normally you are in GL thread only in

public void onDrawFrame(GL10 gl)

Which is a method of the Renderer. Everything must be done inside this method. You can just put a boolean, lets say, into the renderer, and set it to true when you want something to be done in the renderer, and that's it. If you do any call outside this method, you will get erorrs.

Also, look at this example: Android OpenGL Screenshot I have used it and it worked for me, but I didn't need to save off-screen area, so I don't know if it will work, maybe not :)

The method must be called inside onDrawFrame

Community
  • 1
  • 1
XMight
  • 1,991
  • 2
  • 20
  • 36
  • Even with a valid `GL10` object passed into it? It should just piggy-back on the Renderer's coding (most of which is by me, not from the `Renderer` class), and render onto its own GL object. – Cat Jun 14 '12 at 06:57
  • Do you mean another method in different thread? If so, then yes, you will not be able to use even if you passed a valid GL10 object. You must use it only there. Imagine how all games are written: there is a while loop in which everything is done. Math calculations for example can be done in other threads, but you handle the results in the thread where you render, and GL objects are valid in the rendering thread, as GL objects are too. Android is smarter and points you to this, in C++ you'll get only crash or access violation :) – XMight Jun 14 '12 at 07:04
  • They both share the same GL thread. It's basically a second object (the `PixelBuffer`) that has the `Renderer` as a member variable; they share a thread and a context. It basically calls `mRenderer.onDrawFrame()`. – Cat Jun 14 '12 at 17:09
  • First, if you receive the error you specified, then there are different threads anyway. Even if PixelBuffer has the renderer set as a variable, then it was created in another thread, and calling the onDrawFrame method from another thread is incorrect. This is because the GLThread is created inside the renderer. Do I miss something? – XMight Jun 15 '12 at 12:01
  • The GL thread is created from inside the renderer? Then all calls to that renderer should use that same thread, since it is associated with only that renderer... I feel it is I that is missing something, heh. I assume you've taken a look at the `PixelBuffer` code in the original post? It has some thread checking methods, and the original constructor is called from the `GLSurfaceView`, which should be the same thread as the renderer, right? – Cat Jun 15 '12 at 16:30
  • Hi, I looked into the code of PixelBuffer. As I can see, there is a check on Thread.getName, but I'm not sure how this works. If you didn't find the solution already, and if you want, you can make a simple test project, and I can try to find why you get the error with OpenGL context. Obviously if you get the error with the context, then you call the method from the thread that doesn't own the GL context, as Android points you – XMight Jun 18 '12 at 11:05
  • Because of the project's complexity, it will take a while to implement this in an isolated project... so, before I do, I'll ask a quick thing of you: Is it possible to force the `PixelBuffer` class to use a `Thread` that is passed to it? – Cat Jun 20 '12 at 02:43
  • Hi, you can use android.os.Handler. Usually you will do: 1. Create a new Handler (or impl Callback) in the Thread that owns the GL context. Pass the Handler to the PixelBuffer, and create a new Runnable and pass it to the Handler.post(Runnable). It will be put in the queue of the thread in which Handler was created. Theoretically this must work, in practice dunno. I advice you anyway to do everything in onDrawFrame though, but it's your choice. Also, check this on pixel format: (http://stackoverflow.com/questions/7523597/egl-bad-match-with-droid-droid-2) – XMight Jun 20 '12 at 06:54
  • Ahhhhhhh, alrighty, I finally got them running in the same thread. I realized it was being called by the UI thread, but the UI thread was also the one that constructed the object, so it thought it was the same thread. I called it instead from the `Renderer` and all is well. Sadly, this has not solved my problem of why I switched to this method to begin with... but that's for another SO question. Thank you so much for your help, and enjoy your +50 bounty, good sir! – Cat Jun 20 '12 at 18:18