Related
I want to make a android application which draws open gl graphics completely from native code, in other words I'm using native_app_glue in my c/c++ code.
However I cant find a single resource on the internet that points to using open gl completely from native code !
All the links I found show how can I use the GLSurfaceView in java and then make a JNI call to C/C++ for each onDraw() call. THIS IS EXACTLY WHAT I DON'T WANT TO DO!!
Even the hello-gl2 sample in the ndk uses the same approach!
I do understand that the native app glue does includes some java code behind the scenes to start up the activity, but I dont want any more frequent JNI calls after that.
Instead I want to setup initialisation and call backs for the screen in native code, by that I want to setup the display surface and its corresponding call backs in my native code and not through java.
1.what are the data structures representing a surface (or window display) in NDK?
2.How do I get access to them and get attributes like width and height of the display (in native code)?
3.How do I draw onto that display (since there is no on draw callback I assume I need to make a loop to draw call manually but in that case how do I know that last frame has finished rendering?)
Below is a simple C program (without Manifest/Java etc...) that spins a triangle for 20 seconds. This is just for information purpose (not the recommended way for creating an Android app). It requires access rights to the 'graphics' group (which is the case if you run it through ADB).
How to compile it:
1) get android NDK from http://developer.android.com/tools/sdk/ndk/index.html
2) install toolchain:
./android-ndk-r9b/build/tools/make-standalone-toolchain.sh \
--platform=android-17 --toolchain=arm-linux-androideabi-4.7 \
--system=linux-x86_64 --install-dir=where_you_want_to_install
3) get libui.so from device:
adb pull /system/lib/libui.so
4) compilation:
arm-linux-androideabi-gcc main.c -lGLESv1_CM -lEGL -landroid -L. -lui -o gles_test
To run the program, you will need access to the 'graphics' group, which is the case if you run it through ADB (but not if you run it from a terminal, e.g., TerminalIDE)
/* Program largely inspired from: */
/* http://jiggawatt.org/badc0de/android/index.html */
/* http://software.intel.com/en-us/articles/setting-up-native-opengl-es-on-android-platforms */
/* http://www.brucesutherland.co.uk/android-ndk/opengl-es-2-0-android-ndk-game-programming/ */
#include <stdio.h>
#include <stdlib.h>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <android/native_window.h>
#include <sys/types.h>
#include <sys/times.h>
/* returns current time in seconds */
double now() {
struct tms now_tms ;
return (double)(times(&now_tms)) / 100.0 ;
}
/* from libui.so (get it from the device using adb pull /system/lib/libui.so) */
extern NativeWindowType android_createDisplaySurface();
NativeWindowType displayWindow;
const EGLint config16bpp[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5,
EGL_NONE
};
const EGLint config24bpp[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_NONE
};
const EGLint config32bpp[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_NONE
};
GLfloat colors[3][4] = {
{1.0f, 0.0f, 0.0f, 1.0f},
{0.0f, 1.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 1.0f, 1.0f}
};
GLfloat vertices[3][3] = {
{0.0f, 0.7f, 0.0f},
{-0.7f, -0.7f, 0.0f},
{0.7f, -0.7f, 0.0f}
};
void draw_tri() {
glViewport(
0,
0,
ANativeWindow_getWidth(displayWindow),
ANativeWindow_getHeight(displayWindow)
);
glRotatef(0.5, 0, 0, 1) ;
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glColorPointer(4, GL_FLOAT, 0, colors);
glVertexPointer(3, GL_FLOAT, 0, vertices);
/* Draw the triangle (3 vertices) */
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
int main(int argc, char** argv) {
EGLint majorVersion, minorVersion;
EGLContext eglContext;
EGLSurface eglSurface;
EGLConfig eglConfig;
EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint format;
const EGLint* config = NULL ;
int numConfigs;
int windowFormat;
double start_time = now() ;
/*
* create a window surface that covers the entire screen.
* This function is from libui.
*/
displayWindow = android_createDisplaySurface();
eglInitialize(eglDisplay, &majorVersion, &minorVersion);
printf("GL version: %d.%d\n",majorVersion,minorVersion);
if(displayWindow == 0) {
printf("Could not create window\n") ;
printf("Started from an on-device shell ?\n") ;
printf("use: adb shell <path_to_exe>/%s\n",argv[0]) ;
exit(-1) ;
}
/* get the format of the window. */
windowFormat = ANativeWindow_getFormat(displayWindow) ;
printf("Window specs: %d*%d format=%d\n",
ANativeWindow_getWidth(displayWindow),
ANativeWindow_getHeight(displayWindow),
windowFormat
) ;
/* choose the config according to the format of the window. */
switch(windowFormat) {
case WINDOW_FORMAT_RGBA_8888:
config = config32bpp ;
break ;
case WINDOW_FORMAT_RGBX_8888:
config = config24bpp ;
break ;
case WINDOW_FORMAT_RGB_565:
config = config16bpp ;
break ;
default:
printf("Unknown window format\n") ;
exit(-1) ;
}
if (!eglChooseConfig(eglDisplay, config32bpp, &eglConfig, 1, &numConfigs)) {
printf("eglChooseConfig failed\n");
if (eglContext==0) printf("Error code: %x\n", eglGetError());
exit(-1) ;
}
eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, NULL);
printf("GL context: %x\n", eglContext);
if (eglContext==0) {
printf("Error code: %x\n", eglGetError());
exit(-1) ;
}
eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, displayWindow, NULL);
printf("GL surface: %x\n", eglSurface);
if (eglSurface==0) {
printf("Error code: %x\n", eglGetError());
exit(-1);
}
eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
printf(
"Vendor: %s, Renderer: %s, Version: %s\n",
glGetString(GL_VENDOR),
glGetString(GL_RENDERER),
glGetString(GL_VERSION)
) ;
printf("Extensions: %s\n", glGetString(GL_EXTENSIONS)) ;
printf("Spinning triangle for 20s\n") ;
while (now() - start_time < 20.0) {
draw_tri();
eglSwapBuffers(eglDisplay, eglSurface);
}
printf("End (tap the screen of the phone to continue).\n") ;
return 0;
}
See the NDK Samples. There is one here:
\android-ndk\samples\native-activity
On-line link:
http://developer.android.com/reference/android/app/NativeActivity.html
I'm trying to make an NDK based OpenGL application. At some point in my code, I want to check the OpenGL version available on the device.
I'm using the following code :
const char *version = (const char *) glGetString(GL_VERSION);
if (strstr(version, "OpenGL ES 2.")) {
// do something
} else {
__android_log_print(ANDROID_LOG_ERROR, "NativeGL", "Open GL 2 not available (%s)", version=;
}
THe problem is that the version string is always equals to "OpenGL ES-CM 1.1".
I'm testing on both a Moto G (Android 4.4.4) and Samsung Galaxy Nexus (Android 4.3), both of which are OpenGL ES 2.0 compliant (the moto G is also OpenGL ES 3.0 compliant).
I tried to force the EGL_CONTEXT_CLIENT_VERSION when I initialise my display, but then eglChooseConfig returns 0 configurations. And when I test the context client version value in the default configuration, it's always 0 :
const EGLint attrib_list[] = {
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_NONE
};
// get the number of configs matching the attrib_list
EGLint num_configs;
eglChooseConfig(display, attrib_list, NULL, 0, &num_configs);
LOG_D(TAG, " • %d EGL configurations found", num_configs);
// find matching configurations
EGLConfig configs[num_configs];
EGLint client_version = 0, depth_size = 0, stencil_size = 0, surface_type = 0;
eglChooseConfig(display, requirements, configs, num_configs, &num_configs);
for(int i = 0; i < num_configs; ++i){
eglGetConfigAttrib(display, configs[i], EGL_CONTEXT_CLIENT_VERSION, &client_version);
LOG_D(TAG, " client version %d = 0x%08x", i, client_version);
}
// Update the window format from the configuration
EGLint format;
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(window, 0, 0, format);
// create the surface and context
EGLSurface surface = eglCreateWindowSurface(display, config, window, NULL);
EGLContext context = eglCreateContext(display, config, NULL, NULL);
I'm linking against the Open GL ES 2.0 library : here's the excerpt from my Android.mk
LOCAL_LDLIBS := -landroid -llog -lEGL -lGLESv2
Thanks to the hints given by mstorsjo, I managed to have the correct initialisation code, shown here if other people struggle with this.
const EGLint attrib_list[] = {
// this specifically requests an Open GL ES 2 renderer
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
// (ommiting other configs regarding the color channels etc...
EGL_NONE
};
EGLConfig config;
EGLint num_configs;
eglChooseConfig(display, attrib_list, &config, 1, &num_configs);
// ommiting other codes
const EGLint context_attrib_list[] = {
// request a context using Open GL ES 2.0
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLContext context = eglCreateContext(display, config, NULL, context_attrib_list);
What version you get from glGetString(GL_VERSION) depends on which library you've linked the code against, either libGLESv1_CM.so or libGLESv2.so. Similarly for all the other common GL functions. This means that in practice, you need to build two separate .so files for your GL ES 1 and 2 versions of your rendering, and only load the right one once you know which one of them you can use (or load the function pointers dynamically). (This apparently is different when having compatibility between GL ES 2 and 3, where you can check using glGetString(GL_VERSION).)
You didn't say where you tried using EGL_CONTEXT_CLIENT_VERSION - it should be used in the parameter array to eglCreateContext (which you only call once you actually have chosen a config). The attribute array given to eglChooseConfig should have the pair EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT to get a suitable config.
I am developing for Android using opengl/egl. My app requires a second context for loading textures from a second thread.
My code works fine on android 2.3, but when I try the code on a 4.0.3 android device or emulator, eglMakeCurrent() fails with EGL_BAD_MATCH.
The initialization of the second context and it's pixel buffer all works fine too, so I am not sure where to begin looking for this error.
This is the initialization code:
ANativeWindow *window = (ANativeWindow*)displaySurface;
EGLint dummy, format;
display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
EGLint contextAttribs[] =
{
EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE
};
const EGLint configAttribs[] =
{
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_BUFFER_SIZE, 32,
EGL_DEPTH_SIZE, 24,
EGL_NONE
};
EGLint numConfigs;
EGLConfig config;
eglChooseConfig(display, configAttribs, &config, 1, &numConfigs);
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(window, 0, 0, format);
surface = eglCreateWindowSurface(display, config, window, NULL);
if(surface == NULL)
Trace("error creating window surface: " + GetEglError());
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
if(context == NULL)
Trace("error creating main context: " + GetEglError());
const EGLint auxConfigAttribs[] =
{
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_NONE
};
EGLint pbufferAttribs[] =
{
EGL_WIDTH, 1,
EGL_HEIGHT, 1,
EGL_TEXTURE_TARGET, EGL_NO_TEXTURE,
EGL_TEXTURE_FORMAT, EGL_NO_TEXTURE,
EGL_NONE
};
EGLint auxNumConfigs;
EGLConfig auxConfig;
eglChooseConfig(display, auxConfigAttribs, &auxConfig, 1, &auxNumConfigs);
auxSurface = eglCreatePbufferSurface(display, auxConfig, pbufferAttribs);
if(auxSurface == NULL)
Trace("error creating pbuffer surface: " + GetEglError());
auxContext = eglCreateContext(display, auxConfig, context, contextAttribs);
if(auxSurface == NULL)
Trace("error creating auxilliary context: " + GetEglError());
if(!eglMakeCurrent(display, surface, surface, context))
Trace("could not make main context current: " + GetEglError());
On my Android 2.3 device(HTC Desire), the above initialization code works perfectly, and I can make the auxContext current, and load textures just fine.
BUT, on my android 4.0.3 device(Samsung Nexus S) and my Android 4.1 device (Galaxy Note 2) eglMakeCurrent() fails with EGL_BAD_MATCH after a successful initialization.
Does anyone know why I may be getting this error?
Ah, something I actually know something about. ;) [Having spent best part of 5 years working on various EGL implementations].
I'm pretty certain your surface is a different format to the actual display surface. I'm not sure exactly WHAT the difference would be, or what you need to change. EGL_DEPTH_SIZE perhaps? You could try enumerating the modes that are available and see if any look "likely". I know, it's a bit of a pain, but I've been there done that a few times in the past - with the difference that I could usually look through the EGL source code and figure out what I'd done wrong... ;)
If your getting this error but not dealing with this surface or texture stuff, go to run and type .android
go to AVD and your current Emulator delete the user-date file usually on .img file, restart your emulator then test. This works for me, if it happens on while testing on your device, clear the data and restart your app. Cheers for those who find this helpful.
Ensure you have set EGL_PBUFFER_BIT for the EGL_SURFACE_TYPE in the attributes passed into eglChooseConfig() call. It's work for me
I have EGL/GLES 2.0 code, which I try to run on Linux (via Mesa) and Android (iOS to come). On Linux it works fine and renders like expected.
Running on Android (Phone, tablet and emulator - all 4.01) it passes fine but displays nothing (screen stays black).
The code is 99% the same for all 3 - with some special handling for Android.
Following my EGL attributes:
EGLint attribList[] =
{
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
//EGL_ALPHA_SIZE, (flags & ES_WINDOW_ALPHA) ? 8 : EGL_DONT_CARE,
//EGL_DEPTH_SIZE, (flags & ES_WINDOW_DEPTH) ? 8 : EGL_DONT_CARE,
//EGL_STENCIL_SIZE, (flags & ES_WINDOW_STENCIL) ? 8 : EGL_DONT_CARE,
EGL_SAMPLE_BUFFERS, (flags & ES_WINDOW_MULTISAMPLE) ? 1 : 0,
// For Android this is extremely important - eglCreateContext will fail without it
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE, EGL_NONE
};
Following the EGL creation code:
EGLint numConfigs;
EGLint majorVersion;
EGLint minorVersion;
EGLDisplay display;
EGLContext context;
EGLSurface surface;
EGLConfig config;
EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE };
// Get Display
display = eglGetDisplay((EGLNativeDisplayType)x_display);
if ( display == EGL_NO_DISPLAY )
{
esLogMessage("eglGetDisplay failed %d\n", eglGetError());
return EGL_FALSE;
}
// Initialize EGL
if ( !eglInitialize(display, &majorVersion, &minorVersion) )
{
esLogMessage("eglInitialize failed %d\n", eglGetError());
return EGL_FALSE;
}
static const size_t CONFIG_COUNT = 128;
EGLConfig configs[CONFIG_COUNT];
// Get configs
if ( !eglGetConfigs(display, configs, CONFIG_COUNT, &numConfigs) )
{
esLogMessage("eglGetConfigs failed %d\n", eglGetError());
return EGL_FALSE;
}
else if( numConfigs == 0 )
{
esLogMessage("eglGetConfigs found no configs for the display\n");
return EGL_FALSE;
}
EGLint chosenConfigCount = 0;
// Choose config
if ( !eglChooseConfig(display, attribList, &config, 1, &chosenConfigCount) )
{
esLogMessage("eglChooseConfig failed %d\n", eglGetError());
return EGL_FALSE;
}
else if( chosenConfigCount == 0 )
{
esLogMessage("eglChooseConfig found no matching configs (%d available)\n", numConfigs);
return EGL_FALSE;
}
EGLint format;
/* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
* guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
* As soon as we picked a EGLConfig, we can safely reconfigure the
* ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */
if( !eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format) )
{
esLogMessage("eglGetConfigAttrib failed %d\n", eglGetError());
return EGL_FALSE;
}
#ifdef ANDROID
if( ANativeWindow_setBuffersGeometry(hWnd, 0, 0, format) )
{
esLogMessage("ANativeWindow_setBuffersGeometry failed\n");
return EGL_FALSE;
}
#endif
// Create a surface
surface = eglCreateWindowSurface(display, config, (EGLNativeWindowType)hWnd, NULL);
if ( surface == EGL_NO_SURFACE )
{
esLogMessage("eglCreateWindowSurface failed %d\n", eglGetError());
return EGL_FALSE;
}
// Create a GL context
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs );
if ( context == EGL_NO_CONTEXT )
{
esLogMessage("eglCreateContext failed %d\n", eglGetError());
return EGL_FALSE;
}
// Make the context current
if ( !eglMakeCurrent(display, surface, surface, context) )
{
esLogMessage("eglMakeCurrent failed %d\n", eglGetError());
return EGL_FALSE;
}
Could someone shed a light, what to test or how to find the problem?
EDIT:
I fixed some other bugs and it now works fine in the Android Emulator and HP Touchpad (Cyanogenmod 9 alpha) but still leads to a black screen on my Samsung Galaxy S1 with Cyanogenmod 9 *sigh*.
First, just a remark: you don't need two EGL_NONE to end your attribute definitions.
Second, your issue comes from the fact that eglGetConfigs() returns the available configs, but eglChooseConfig(...,1,..) will not necessarily return the config that exactly matches the one you requested.
Thus, you have to scan the configs to find the closest to your needs.
You have to call eglGetConfigAttrib() to compare, for instance, EGL_RED_SIZE, EGL_GREEN_SIZE,EGL_BLUE_SIZE, EGL_ALPHA_SIZE, EGL_RENDERABLE_TYPE, EGL_DEPTH_SIZE, EGL_STENCIL_SIZE.
See eglChooseConfig() for further details.
I'm trying to create the EGL context to draw everything with OpenglES within a native function call. The problem is that I need access to the NativeWindowType instance, but I could only find a function to create one (which I can't find out how to link, anyway). However, even if I create one, I suspect that would be wrong, since what I really need is the one created by the SurfaceView instance from which I'm calling this native function.
Here is the code:
static int egl_init() {
const EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_NONE
};
EGLint w, h, dummy, format;
EGLint egl_major_version, egl_minor_version;
EGLint numConfigs;
EGLConfig egl_config;
EGLSurface egl_surface;
EGLContext egl_context;
EGLDisplay egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
// v--------- This is where I should get the display window
NativeWindowType display_window;
display_window = android_createDisplaySurface();
eglInitialize(egl_display, &egl_major_version, &egl_minor_version);
printf("GL Version: %d.%d\n", egl_major_version, egl_minor_version);
if (!eglChooseConfig(egl_display, attribs, &egl_config, 1, &numConfigs))
{
printf("eglChooseConfig failed\n");
if (egl_context == 0) printf("Error code: %x\n", eglGetError());
}
eglGetConfigAttrib(egl_display, egl_config, EGL_NATIVE_VISUAL_ID, &format);
// v---------- This requires that I link libandroid, it is found in android/native_window.h
ANativeWindow_setBuffersGeometry(display_window, 0, 0, format);
egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, NULL);
if (egl_context == 0) LOGE("Error code: %x\n", eglGetError());
egl_surface = eglCreateWindowSurface(egl_display, egl_config, display_window, NULL);
if (egl_surface == 0) LOGE("Error code: %x\n", eglGetError());
if (eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context) == EGL_FALSE) {
LOGE("Unable to eglMakeCurrent");
return -1;
}
return 0;
}
Thanks for your help
The surface can not support the the requested egl config (red,green and blue being at least 8 bits).
const EGLint attribs[] = {
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_NONE
};
Some phones, makes all surface buffers RGB_565 by default.
In Java to get more detailed colors or alpha you can getWindow and setFormat(). Like so:
getWindow().setFormat(PixelFormat.TRANSLUCENT);
To do something equivalent in a native-activity you must do something like the following.
ANativeWindow_setBuffersGeometry(display_window, 0, 0, 1);
as defined in android/native_window.h
/*
* Pixel formats that a window can use.
*/
enum {
WINDOW_FORMAT_RGBA_8888 = 1,
WINDOW_FORMAT_RGBX_8888 = 2,
WINDOW_FORMAT_RGB_565 = 4,
};
Hope this helps. I've seen this question around and I just figured it out.