GLSurfaceView adapted for 3D Live Wallpapers

I've been using GLSurfaceView since it was introduced in Android 1.5 and I was a little let down to find that the new Live Wallpaper APIs didn't include anything like that. I like the design because it makes it very easy to quickly start working in OpenGL. Without it, there is quite a bit of tedious initialization and thread management code that isn't necessary for the vast majority of apps. Fortunately, for my first live wallpaper (Live Waterpaper), I adapted the GLSurfaceView's code and created a GLWallpaperService with a GLEngine which takes a Renderer and does the job for me.

This is very similar in design to how things work with GLSurfaceView, except the GLWallpaperService does nothing more than create Engines and I have to move around a few of the fields for the GLThread management and configurations. Any event handling code you have should go into your GLEngine subclass and can be passed directly to your Renderer, which can be an exact port of an existing Renderer, with the exception of specifying the different package name on the interface.

I submitted this code to be part of the standard Android API. Hopefully it will make it in.

There is a lot of overlap with this code and GLSurfaceView, perhaps 99%.

So how do you use this? Extend GLWallpaperService and in your subclass, declare an inner class that extends GLEngine. Override onCreateEngine() to return your GLEngine subclass. In your GLEngine subclass, instantiate your Renderer, configure it and set it using setRenderer(Renderer) and setRenderMode(int). Your engine can handle touch events if you call setTouchEventsEnabled(true) and can also handle sensor data and preference changes. Everything else works exactly like it did with GLSurfaceView.

Enjoy!

NOTE - This code is buggy and not supported here. There is an up-to-date supported version at https://github.com/GLWallpaperService/GLWallpaperService

File android.opengl.GLWallpaperService.java

package android.opengl;

import java.io.Writer;
import java.util.ArrayList;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGL11;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.BaseConfigChooser.ComponentSizeChooser;
import android.opengl.BaseConfigChooser.SimpleEGLConfigChooser;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.SurfaceHolder;

public class GLWallpaperService extends WallpaperService {
private static final String TAG = "GLWallpaperService";

@Override
public Engine onCreateEngine() {
return new GLEngine();
}

public class GLEngine extends Engine {
public final static int RENDERMODE_WHEN_DIRTY = 0;
public final static int RENDERMODE_CONTINUOUSLY = 1;

private GLThread mGLThread;
private EGLConfigChooser mEGLConfigChooser;
private EGLContextFactory mEGLContextFactory;
private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
private GLWrapper mGLWrapper;
private int mDebugFlags;

public GLEngine() {
super();
}

@Override
public void onVisibilityChanged(boolean visible) {
if (visible) {
onResume();
} else {
onPause();
}
super.onVisibilityChanged(visible);
}

@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
// Log.d(TAG, "GLEngine.onCreate()");
}

@Override
public void onDestroy() {
super.onDestroy();
// Log.d(TAG, "GLEngine.onDestroy()");
mGLThread.requestExitAndWait();
}

@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// Log.d(TAG, "onSurfaceChanged()");
mGLThread.onWindowResize(width, height);
super.onSurfaceChanged(holder, format, width, height);
}

@Override
public void onSurfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "onSurfaceCreated()");
mGLThread.surfaceCreated(holder);
super.onSurfaceCreated(holder);
}

@Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "onSurfaceDestroyed()");
mGLThread.surfaceDestroyed();
super.onSurfaceDestroyed(holder);
}

/**
* An EGL helper class.
*/
public void setGLWrapper(GLWrapper glWrapper) {
mGLWrapper = glWrapper;
}

public void setDebugFlags(int debugFlags) {
mDebugFlags = debugFlags;
}

public int getDebugFlags() {
return mDebugFlags;
}

public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mGLThread = new GLThread(renderer, mEGLConfigChooser, mEGLContextFactory, mEGLWindowSurfaceFactory, mGLWrapper);
mGLThread.start();
}

public void setEGLContextFactory(EGLContextFactory factory) {
checkRenderThreadState();
mEGLContextFactory = factory;
}

public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) {
checkRenderThreadState();
mEGLWindowSurfaceFactory = factory;
}

public void setEGLConfigChooser(EGLConfigChooser configChooser) {
checkRenderThreadState();
mEGLConfigChooser = configChooser;
}

public void setEGLConfigChooser(boolean needDepth) {
setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth));
}

public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize,
int stencilSize) {
setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, blueSize, alphaSize, depthSize,
stencilSize));
}

public void setRenderMode(int renderMode) {
mGLThread.setRenderMode(renderMode);
}

public int getRenderMode() {
return mGLThread.getRenderMode();
}

public void requestRender() {
mGLThread.requestRender();
}

public void onPause() {
mGLThread.onPause();
}

public void onResume() {
mGLThread.onResume();
}

public void queueEvent(Runnable r) {
mGLThread.queueEvent(r);
}

private void checkRenderThreadState() {
if (mGLThread != null) {
throw new IllegalStateException("setRenderer has already been called for this instance.");
}
}
}

public interface Renderer {

public void onSurfaceCreated(GL10 gl, EGLConfig config);

public void onSurfaceChanged(GL10 gl, int width, int height);

public void onDrawFrame(GL10 gl);
}
}

class LogWriter extends Writer {
private StringBuilder mBuilder = new StringBuilder();

@Override
public void close() {
flushBuilder();
}

@Override
public void flush() {
flushBuilder();
}

@Override
public void write(char[] buf, int offset, int count) {
for (int i = 0; i < count; i++) {
char c = buf[offset + i];
if (c == '\n') {
flushBuilder();
} else {
mBuilder.append(c);
}
}
}

private void flushBuilder() {
if (mBuilder.length() > 0) {
Log.v("GLSurfaceView", mBuilder.toString());
mBuilder.delete(0, mBuilder.length());
}
}
}

// ----------------------------------------------------------------------

/**
* An interface for customizing the eglCreateContext and eglDestroyContext calls.
*

* This interface must be implemented by clients wishing to call
* {@link GLWallpaperService#setEGLContextFactory(EGLContextFactory)}
*/
interface EGLContextFactory {
EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig);

void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context);
}

class DefaultContextFactory implements EGLContextFactory {

public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, null);
}

public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
egl.eglDestroyContext(display, context);
}
}

/**
* An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls.
*

* This interface must be implemented by clients wishing to call
* {@link GLWallpaperService#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)}
*/
interface EGLWindowSurfaceFactory {
EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow);

void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface);
}

class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory {

public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay
display, EGLConfig config, Object nativeWindow) {
// this is a bit of a hack to work around Droid init problems - if you don't have this, it'll get hung up on orientation changes
EGLSurface eglSurface = null;
while (eglSurface == null) {
try {
eglSurface = egl.eglCreateWindowSurface(display,
config, nativeWindow, null);
} catch (Throwable t) {
} finally {
if (eglSurface == null) {
try {
Thread.sleep(10);
} catch (InterruptedException t) {
}
}
}
}
return eglSurface;
}

public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {
egl.eglDestroySurface(display, surface);
}
}

interface GLWrapper {
/**
* Wraps a gl interface in another gl interface.
*
* @param gl
* a GL interface that is to be wrapped.
* @return either the input argument or another GL object that wraps the input argument.
*/
GL wrap(GL gl);
}

class EglHelper {

private EGL10 mEgl;
private EGLDisplay mEglDisplay;
private EGLSurface mEglSurface;
private EGLContext mEglContext;
EGLConfig mEglConfig;

private EGLConfigChooser mEGLConfigChooser;
private EGLContextFactory mEGLContextFactory;
private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
private GLWrapper mGLWrapper;

public EglHelper(EGLConfigChooser chooser, EGLContextFactory contextFactory,
EGLWindowSurfaceFactory surfaceFactory, GLWrapper wrapper) {
this.mEGLConfigChooser = chooser;
this.mEGLContextFactory = contextFactory;
this.mEGLWindowSurfaceFactory = surfaceFactory;
this.mGLWrapper = wrapper;
}

/**
* Initialize EGL for a given configuration spec.
*
* @param configSpec
*/
public void start() {
Log.d("EglHelper" + instanceId, "start()");
if (mEgl == null) {
Log.d("EglHelper" + instanceId, "getting new EGL");
/*
* Get an EGL instance
*/
mEgl = (EGL10) EGLContext.getEGL();
} else {
Log.d("EglHelper" + instanceId, "reusing EGL");
}

if (mEglDisplay == null) {
Log.d("EglHelper" + instanceId, "getting new display");
/*
* Get to the default display.
*/
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
} else {
Log.d("EglHelper" + instanceId, "reusing display");
}

if (mEglConfig == null) {
Log.d("EglHelper" + instanceId, "getting new config");
/*
* We can now initialize EGL for that display
*/
int[] version = new int[2];
mEgl.eglInitialize(mEglDisplay, version);
mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
} else {
Log.d("EglHelper" + instanceId, "reusing config");
}

if (mEglContext == null) {
Log.d("EglHelper" + instanceId, "creating new context");
/*
* Create an OpenGL ES context. This must be done only once, an OpenGL context is a somewhat heavy object.
*/
mEglContext = mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
throw new RuntimeException("createContext failed");
}
} else {
Log.d("EglHelper" + instanceId, "reusing context");
}

mEglSurface = null;
}

/*
* React to the creation of a new surface by creating and returning an OpenGL interface that renders to that
* surface.
*/
public GL createSurface(SurfaceHolder holder) {
/*
* The window size has changed, so we need to create a new surface.
*/
if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {

/*
* Unbind and destroy the old EGL surface, if there is one.
*/
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
}

/*
* Create an EGL surface we can render into.
*/
mEglSurface = mEGLWindowSurfaceFactory.createWindowSurface(mEgl, mEglDisplay, mEglConfig, holder);

if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
throw new RuntimeException("createWindowSurface failed");
}

/*
* Before we can issue GL commands, we need to make sure the context is current and bound to a surface.
*/
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
throw new RuntimeException("eglMakeCurrent failed.");
}

GL gl = mEglContext.getGL();
if (mGLWrapper != null) {
gl = mGLWrapper.wrap(gl);
}

/*
* if ((mDebugFlags & (DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS))!= 0) { int configFlags = 0; Writer log =
* null; if ((mDebugFlags & DEBUG_CHECK_GL_ERROR) != 0) { configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR; }
* if ((mDebugFlags & DEBUG_LOG_GL_CALLS) != 0) { log = new LogWriter(); } gl = GLDebugHelper.wrap(gl,
* configFlags, log); }
*/
return gl;
}

/**
* Display the current render surface.
*
* @return false if the context has been lost.
*/
public boolean swap() {
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);

/*
* Always check for EGL_CONTEXT_LOST, which means the context and all associated data were lost (For instance
* because the device went to sleep). We need to sleep until we get a new surface.
*/
return mEgl.eglGetError() != EGL11.EGL_CONTEXT_LOST;
}

public void destroySurface() {
if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface);
mEglSurface = null;
}
}

public void finish() {
if (mEglContext != null) {
mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext);
mEglContext = null;
}
if (mEglDisplay != null) {
mEgl.eglTerminate(mEglDisplay);
mEglDisplay = null;
}
}
}

class GLThread extends Thread {
private final static boolean LOG_THREADS = false;
public final static int DEBUG_CHECK_GL_ERROR = 1;
public final static int DEBUG_LOG_GL_CALLS = 2;

private final GLThreadManager sGLThreadManager = new GLThreadManager();
private GLThread mEglOwner;

private EGLConfigChooser mEGLConfigChooser;
private EGLContextFactory mEGLContextFactory;
private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
private GLWrapper mGLWrapper;

public SurfaceHolder mHolder;
private boolean mSizeChanged = true;

// Once the thread is started, all accesses to the following member
// variables are protected by the sGLThreadManager monitor
public boolean mDone;
private boolean mPaused;
private boolean mHasSurface;
private boolean mWaitingForSurface;
private boolean mHaveEgl;
private int mWidth;
private int mHeight;
private int mRenderMode;
private boolean mRequestRender;
private boolean mEventsWaiting;
// End of member variables protected by the sGLThreadManager monitor.

private GLWallpaperService.Renderer mRenderer;
private ArrayList mEventQueue = new ArrayList();
private EglHelper mEglHelper;

GLThread(GLWallpaperService.Renderer renderer, EGLConfigChooser chooser, EGLContextFactory contextFactory,
EGLWindowSurfaceFactory surfaceFactory, GLWrapper wrapper) {
super();
mDone = false;
mWidth = 0;
mHeight = 0;
mRequestRender = true;
mRenderMode = GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY;
mRenderer = renderer;
this.mEGLConfigChooser = chooser;
this.mEGLContextFactory = contextFactory;
this.mEGLWindowSurfaceFactory = surfaceFactory;
this.mGLWrapper = wrapper;
}

@Override
public void run() {
setName("GLThread " + getId());
if (LOG_THREADS) {
Log.i("GLThread", "starting tid=" + getId());
}

try {
guardedRun();
} catch (InterruptedException e) {
// fall thru and exit normally
} finally {
sGLThreadManager.threadExiting(this);
}
}

/*
* This private method should only be called inside a synchronized(sGLThreadManager) block.
*/
private void stopEglLocked() {
if (mHaveEgl) {
mHaveEgl = false;
mEglHelper.destroySurface();
sGLThreadManager.releaseEglSurface(this);
}
}

private void guardedRun() throws InterruptedException {
mEglHelper = new EglHelper(mEGLConfigChooser, mEGLContextFactory, mEGLWindowSurfaceFactory, mGLWrapper);
try {
GL10 gl = null;
boolean tellRendererSurfaceCreated = true;
boolean tellRendererSurfaceChanged = true;

/*
* This is our main activity thread's loop, we go until asked to quit.
*/
while (!isDone()) {
/*
* Update the asynchronous state (window size)
*/
int w = 0;
int h = 0;
boolean changed = false;
boolean needStart = false;
boolean eventsWaiting = false;

synchronized (sGLThreadManager) {
while (true) {
// Manage acquiring and releasing the SurfaceView
// surface and the EGL surface.
if (mPaused) {
stopEglLocked();
}
if (!mHasSurface) {
if (!mWaitingForSurface) {
stopEglLocked();
mWaitingForSurface = true;
sGLThreadManager.notifyAll();
}
} else {
if (!mHaveEgl) {
if (sGLThreadManager.tryAcquireEglSurface(this)) {
mHaveEgl = true;
mEglHelper.start();
mRequestRender = true;
needStart = true;
}
}
}

// Check if we need to wait. If not, update any state
// that needs to be updated, copy any state that
// needs to be copied, and use "break" to exit the
// wait loop.

if (mDone) {
return;
}

if (mEventsWaiting) {
eventsWaiting = true;
mEventsWaiting = false;
break;
}

if ((!mPaused) && mHasSurface && mHaveEgl && (mWidth > 0) && (mHeight > 0)
&& (mRequestRender || (mRenderMode == GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY))) {
changed = mSizeChanged;
w = mWidth;
h = mHeight;
mSizeChanged = false;
mRequestRender = false;
if (mHasSurface && mWaitingForSurface) {
changed = true;
mWaitingForSurface = false;
sGLThreadManager.notifyAll();
}
break;
}

// By design, this is the only place where we wait().

if (LOG_THREADS) {
Log.i("GLThread", "waiting tid=" + getId());
}
sGLThreadManager.wait();
}
} // end of synchronized(sGLThreadManager)

/*
* Handle queued events
*/
if (eventsWaiting) {
Runnable r;
while ((r = getEvent()) != null) {
r.run();
if (isDone()) {
return;
}
}
// Go back and see if we need to wait to render.
continue;
}

if (needStart) {
tellRendererSurfaceCreated = true;
changed = true;
}
if (changed) {
gl = (GL10) mEglHelper.createSurface(mHolder);
tellRendererSurfaceChanged = true;
}
if (tellRendererSurfaceCreated) {
mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig);
tellRendererSurfaceCreated = false;
}
if (tellRendererSurfaceChanged) {
mRenderer.onSurfaceChanged(gl, w, h);
tellRendererSurfaceChanged = false;
}
if ((w > 0) && (h > 0)) {
/* draw a frame here */
mRenderer.onDrawFrame(gl);

/*
* Once we're done with GL, we need to call swapBuffers() to instruct the system to display the
* rendered frame
*/
mEglHelper.swap();
}
}
} finally {
/*
* clean-up everything...
*/
synchronized (sGLThreadManager) {
stopEglLocked();
mEglHelper.finish();
}
}
}

private boolean isDone() {
synchronized (sGLThreadManager) {
return mDone;
}
}

public void setRenderMode(int renderMode) {
if (!((GLWallpaperService.GLEngine.RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY))) {
throw new IllegalArgumentException("renderMode");
}
synchronized (sGLThreadManager) {
mRenderMode = renderMode;
if (renderMode == GLWallpaperService.GLEngine.RENDERMODE_CONTINUOUSLY) {
sGLThreadManager.notifyAll();
}
}
}

public int getRenderMode() {
synchronized (sGLThreadManager) {
return mRenderMode;
}
}

public void requestRender() {
synchronized (sGLThreadManager) {
mRequestRender = true;
sGLThreadManager.notifyAll();
}
}

public void surfaceCreated(SurfaceHolder holder) {
mHolder = holder;
synchronized (sGLThreadManager) {
if (LOG_THREADS) {
Log.i("GLThread", "surfaceCreated tid=" + getId());
}
mHasSurface = true;
sGLThreadManager.notifyAll();
}
}

public void surfaceDestroyed() {
synchronized (sGLThreadManager) {
if (LOG_THREADS) {
Log.i("GLThread", "surfaceDestroyed tid=" + getId());
}
mHasSurface = false;
sGLThreadManager.notifyAll();
while (!mWaitingForSurface && isAlive() && !mDone) {
try {
sGLThreadManager.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}

public void onPause() {
synchronized (sGLThreadManager) {
mPaused = true;
sGLThreadManager.notifyAll();
}
}

public void onResume() {
synchronized (sGLThreadManager) {
mPaused = false;
mRequestRender = true;
sGLThreadManager.notifyAll();
}
}

public void onWindowResize(int w, int h) {
synchronized (sGLThreadManager) {
mWidth = w;
mHeight = h;
mSizeChanged = true;
sGLThreadManager.notifyAll();
}
}

public void requestExitAndWait() {
// don't call this from GLThread thread or it is a guaranteed
// deadlock!
synchronized (sGLThreadManager) {
mDone = true;
sGLThreadManager.notifyAll();
}
try {
join();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}

/**
* Queue an "event" to be run on the GL rendering thread.
*
* @param r
* the runnable to be run on the GL rendering thread.
*/
public void queueEvent(Runnable r) {
synchronized (this) {
mEventQueue.add(r);
synchronized (sGLThreadManager) {
mEventsWaiting = true;
sGLThreadManager.notifyAll();
}
}
}

private Runnable getEvent() {
synchronized (this) {
if (mEventQueue.size() > 0) {
return mEventQueue.remove(0);
}

}
return null;
}

private class GLThreadManager {

public synchronized void threadExiting(GLThread thread) {
if (LOG_THREADS) {
Log.i("GLThread", "exiting tid=" + thread.getId());
}
thread.mDone = true;
if (mEglOwner == thread) {
mEglOwner = null;
}
notifyAll();
}

/*
* Tries once to acquire the right to use an EGL surface. Does not block.
*
* @return true if the right to use an EGL surface was acquired.
*/
public synchronized boolean tryAcquireEglSurface(GLThread thread) {
if (mEglOwner == thread || mEglOwner == null) {
mEglOwner = thread;
notifyAll();
return true;
}
return false;
}

public synchronized void releaseEglSurface(GLThread thread) {
if (mEglOwner == thread) {
mEglOwner = null;
}
notifyAll();
}
}
}

interface EGLConfigChooser {
EGLConfig chooseConfig(EGL10 egl, EGLDisplay display);
}

abstract class BaseConfigChooser implements EGLConfigChooser {
public BaseConfigChooser(int[] configSpec) {
mConfigSpec = configSpec;
}

public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] num_config = new int[1];
egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config);

int numConfigs = num_config[0];

if (numConfigs <= 0) {
throw new IllegalArgumentException("No configs match configSpec");
}

EGLConfig[] configs = new EGLConfig[numConfigs];
egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, num_config);
EGLConfig config = chooseConfig(egl, display, configs);
if (config == null) {
throw new IllegalArgumentException("No config chosen");
}
return config;
}

abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs);

protected int[] mConfigSpec;
public static class ComponentSizeChooser extends BaseConfigChooser {
public ComponentSizeChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize,
int stencilSize) {
super(new int[] { EGL10.EGL_RED_SIZE, redSize, EGL10.EGL_GREEN_SIZE, greenSize, EGL10.EGL_BLUE_SIZE,
blueSize, EGL10.EGL_ALPHA_SIZE, alphaSize, EGL10.EGL_DEPTH_SIZE, depthSize, EGL10.EGL_STENCIL_SIZE,
stencilSize, EGL10.EGL_NONE });
mValue = new int[1];
mRedSize = redSize;
mGreenSize = greenSize;
mBlueSize = blueSize;
mAlphaSize = alphaSize;
mDepthSize = depthSize;
mStencilSize = stencilSize;
}

@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
EGLConfig closestConfig = null;
int closestDistance = 1000;
for (EGLConfig config : configs) {
int d = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, 0);
int s = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, 0);
if (d >= mDepthSize && s >= mStencilSize) {
int r = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, 0);
int g = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, 0);
int b = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, 0);
int a = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, 0);
int distance = Math.abs(r - mRedSize) + Math.abs(g - mGreenSize) + Math.abs(b - mBlueSize)
+ Math.abs(a - mAlphaSize);
if (distance < closestDistance) {
closestDistance = distance;
closestConfig = config;
}
}
}
return closestConfig;
}

private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, int defaultValue) {

if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
return mValue[0];
}
return defaultValue;
}

private int[] mValue;
// Subclasses can adjust these values:
protected int mRedSize;
protected int mGreenSize;
protected int mBlueSize;
protected int mAlphaSize;
protected int mDepthSize;
protected int mStencilSize;
}

/**
* This class will choose a supported surface as close to RGB565 as possible, with or without a depth buffer.
*
*/
public static class SimpleEGLConfigChooser extends ComponentSizeChooser {
public SimpleEGLConfigChooser(boolean withDepthBuffer) {
super(4, 4, 4, 0, withDepthBuffer ? 16 : 0, 0);
// Adjust target values. This way we'll accept a 4444 or
// 555 buffer if there's no 565 buffer available.
mRedSize = 5;
mGreenSize = 6;
mBlueSize = 5;
}
}
}

And here's the basic template for implementing your wallpaper service:

public class MyWallpaperService extends GLWallpaperService {
public MyWallpaperService() {
super();
}

public Engine onCreateEngine() {
MyEngine engine = new MyEngine();
return engine;
}

// prefs and sensor interface is optional, just showing that this is where you do all of that - everything that would normally be in an activity is in here.
class MyEngine extends GLEngine implements SharedPreferences.OnSharedPreferenceChangeListener, SensorEventListener {
MyRenderer renderer;
public MyEngine() {
super();
// handle prefs, other initialization
renderer = new MyRenderer();
setRenderer(renderer);
setRenderMode(RENDERMODE_WHEN_DIRTY);
}

public void onDestroy() {
super.onDestroy();
if (renderer != null) {
renderer.release(); // assuming yours has this method - it should!
}
renderer = null;
}

Update 3/19/2013 - There is a github repository that is active and maintained for this project at https://github.com/GLWallpaperService/GLWallpaperService

155 Comments

Post a comment here or discuss this and other topics in the forums

support openGL2.x

Hi
I just want to know ? how to let GLWallPaperService support openGL2.x 。 how to copfigure .

wallpaper engine

thanks man, plain and simple, im going to use it

The service itself is a

The service itself is a context - just let the engine access it.

it disappeared

i posted a query and it disappeared

I am using context Renderer

I am using Renderer(context) , so what should i change in MyWallpaperservice.java file so that it runs properly. Because its showing add arguments to match Renderer(context).
Help me

Thanks

Hi, I think I just replied to

Hi, I think I just replied to this in another thread. The short answer is - create and use your own local matrix, then just use glLoadMatrix with it.

setGLWrapper

First of all I want to thank you for this library - it's saved me a ton of work!

But, I want to use setGLWarpper to use a matrix tracking wrapper but I can't see how to do it.

Your override of the setGLWrapper method uses your own GLWrapper interface which isn't visible (i.e. no protection modifier). So, I guess I'm not supposed to use that method?

Any help would be appreciated.

Feel free to sell it

Feel free to sell it commercially. Mentioning me, my site, Battery Powered Games or anything like that would be fun for us too :)

Thank You!

Robert, I just wanted to leave a big thank you here, your code served really well as a basis for my first wallpaper. It ran straight away and it's really easy to use. One question: can I sell my wallpaper commercially for millions of $ or is the code under some sort of license?

Regards,

Sebastian

loading resources

Firstly, thanks for the code it's been a real help!

I'm curious, what is the best way to load bitmaps and create textures? I have been doing it in onSurfaceCreated. However, the images are decoded each time the wallpaper becomes visible. Seems like a waste

RGBA_888?

How can I change this value for a LWP and where?

getHolder().setFormat(PixelFormat.RGBA_8888);

thanks,
Leslie

Multisampling / FSAA

Anyone got an example of how to implement FSAA with a config chooser with this?

This is a bug in Eclipse when

This is a bug in Eclipse when resources can't be found. Just clean Eclipse project. "Project - Clean..."

PRoblems with textures

Hi I have same problem and i didnt find any solution. Have you some feedback from anybody who slove tihs ?

BR

Hi, nice job for the

Hi, nice job for the GLWallpaperService... I have a little problem with shaders, everything I tried doesn't allow me to load them. The compiler always failed to compile the shader. I even copy/paste an example available in ApiDemo, but it still block on the compilation.

Do you know how to integrate them ?

Are you using libgdx for a

Are you using libgdx for a live wallpaper, or to run the code straight-up? I've seen phones that limit wallpaper FPS to 30. The code I posted does not limit your FPS in any way, nor does it do anything with GL so you'll want to investigate it with some profiling.

Performance, optimizing

Hello!

I am using opengl in my applications, but its very strange, when i am using my game code/your live wallpaper code, the FPS about 30 frame/sec on a samsung galaxy s, with only ~ 80 textured square.
on a HTC Legend I get a slower/lagger FPS, only 15-20.

But with Libgdx i get a bigger FPS, on these.. why?

I use VBO's, and I perfomancing all the structures, (culling, etc), but it is very poor/slow at the moment.
If I use Native openGL ES codes, then it would be faster?

How I can optimize this better?

Please help me,
thank u, Lacroix

I figured it out

Finally I figured it out.

I forgot to load the surface in the "onSurfaceCreated".
I was doing it only in the "onDrawFrame".

Here are my first two lines of code of the onSurfaceCreated.

------------------------------------------
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
------------------------------------------

Hope it helps.

Wallpaper disappear when going to sleep.

Hi Robert,

first, thank you for all your stuff. You are doing great great things.

My Problem:

I have followed your guide to build my LWP, an it works properly, until I puss the on/off button to put the phone
on sleep mode. When I push it again the LWP has disappear.

Should I manage the new surface, and/or keep the old one? I have supposed that it was made automatically by the library. Am I correct?

One thing more. If I try to load it another time (after the first disappear), it does not load. But, if load other one, and after that I load mine it come alive again. (I think I losing the control of the surface...)

Thank you very much in advance.

Hugo.

Freeze Wallpaers

why, why this cool code freeze :( ... many users send me mails, coments LW 1star, its frustrating :( ... why any one override freeze problem ? :( is any , any solutions ? HTC phones a lot freeze ... please for any sugestions

You need to create and track

You need to create and track your own matrices in GLES since the get functions (as you've found) are unreliable. It's not that hard once you learn the way. Look at the angle project for examples of the math.

Get the matrix

Thanks for the code, it works great!
I have a question though:
Is there a way to retrieve the modelview and the projection matrices from GL?
The glGetFloatv functions keep returning matrices full of 0's...

I tried to use the GLWrapper from the SpriteText API demo but I just couldn't get it to work.
I'd really appreciate some help.

Thanks!

It is working now! This code

It is working now!
This code fix made it work:

in wallpaper service class:
-------------------
renderer = new GlRenderer(this);

in renderer class:
----------------
private Context context;

public GlRenderer(Context context) {
this.context = context;
((GL11) (gl)).glEnable(GL10.GL_BLEND);
bitmap1 = BitmapFactory.decodeResource(context.getResources(),
R.drawable.l11);

I think that you should write in your tutorial that it is not possible to get the resources directly in the
wallpaper service class, because that results in a nullpoint exception. The resources have to be fetched in the renderer class, to avoid the nullpoint exception. I have linked you now and I mailed you the link.

It was not possible to post

It was not possible to post the XML content here or post the link to it, so I mailed it to you. Hope you can help me out with this. I have put up a link to your GL wallpaper information page from our company's link page,but I can't post the link here, since it gets stopped by the spam filter.

It seems impossible to post

It seems impossible to post the code of the manifest file,and I can't post the link to it on my website,because it is flagged as spam. I try to write the manifest file step by step

1:

2:

4:

(No subject)

package="net.markguerra.andro

package="net.markguerra.android.glwallpaperexample"
android:versionCode="1"
android:versionName="1.0">

Manifest

Yes, I am registering it in

Yes, I am registering it in the manifest, as you can see below. The code is the same as in your sample project, I have just changed the package name of the classes in the JAR file. I have also tried with your original JAR file included, but with the same result.

<?xml version="1.0" encoding="utf-8"?>

I don't use

I don't use super.getResources() but just getResources(), but either should work. That's strange.. You're registering "MyWallpaperService" as a service in the manifest, not instantiating it yourself, right? You need to do that with an Intent Action of android.service.wallpaper.WallpaperService

It looks to me like it's not registered and invoked by android but instead by other code.