[go: nahoru, domu]

1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui;
18
19import static android.opengl.GLES20.*;
20
21import static javax.microedition.khronos.egl.EGL10.*;
22
23import android.app.ActivityManager;
24import android.app.WallpaperManager;
25import android.content.ComponentCallbacks2;
26import android.graphics.Bitmap;
27import android.graphics.Canvas;
28import android.graphics.Rect;
29import android.graphics.RectF;
30import android.graphics.Region.Op;
31import android.opengl.GLUtils;
32import android.os.AsyncTask;
33import android.os.SystemProperties;
34import android.os.Trace;
35import android.renderscript.Matrix4f;
36import android.service.wallpaper.WallpaperService;
37import android.util.Log;
38import android.view.Display;
39import android.view.DisplayInfo;
40import android.view.MotionEvent;
41import android.view.SurfaceHolder;
42import android.view.WindowManager;
43
44import java.io.FileDescriptor;
45import java.io.IOException;
46import java.io.PrintWriter;
47import java.nio.ByteBuffer;
48import java.nio.ByteOrder;
49import java.nio.FloatBuffer;
50
51import javax.microedition.khronos.egl.EGL10;
52import javax.microedition.khronos.egl.EGLConfig;
53import javax.microedition.khronos.egl.EGLContext;
54import javax.microedition.khronos.egl.EGLDisplay;
55import javax.microedition.khronos.egl.EGLSurface;
56
57/**
58 * Default built-in wallpaper that simply shows a static image.
59 */
60@SuppressWarnings({"UnusedDeclaration"})
61public class ImageWallpaper extends WallpaperService {
62    private static final String TAG = "ImageWallpaper";
63    private static final String GL_LOG_TAG = "ImageWallpaperGL";
64    private static final boolean DEBUG = false;
65    private static final String PROPERTY_KERNEL_QEMU = "ro.kernel.qemu";
66
67    static final boolean FIXED_SIZED_SURFACE = true;
68    static final boolean USE_OPENGL = true;
69
70    WallpaperManager mWallpaperManager;
71
72    DrawableEngine mEngine;
73
74    boolean mIsHwAccelerated;
75
76    @Override
77    public void onCreate() {
78        super.onCreate();
79        mWallpaperManager = (WallpaperManager) getSystemService(WALLPAPER_SERVICE);
80
81        //noinspection PointlessBooleanExpression,ConstantConditions
82        if (FIXED_SIZED_SURFACE && USE_OPENGL) {
83            if (!isEmulator()) {
84                mIsHwAccelerated = ActivityManager.isHighEndGfx();
85            }
86        }
87    }
88
89    @Override
90    public void onTrimMemory(int level) {
91        if (mEngine != null) {
92            mEngine.trimMemory(level);
93        }
94    }
95
96    private static boolean isEmulator() {
97        return "1".equals(SystemProperties.get(PROPERTY_KERNEL_QEMU, "0"));
98    }
99
100    @Override
101    public Engine onCreateEngine() {
102        mEngine = new DrawableEngine();
103        return mEngine;
104    }
105
106    class DrawableEngine extends Engine {
107        static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
108        static final int EGL_OPENGL_ES2_BIT = 4;
109
110        Bitmap mBackground;
111        int mBackgroundWidth = -1, mBackgroundHeight = -1;
112        int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
113        int mLastRotation = -1;
114        float mXOffset = 0.5f;
115        float mYOffset = 0.5f;
116        float mScale = 1f;
117
118        private Display mDefaultDisplay;
119        private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
120
121        boolean mVisible = true;
122        boolean mOffsetsChanged;
123        int mLastXTranslation;
124        int mLastYTranslation;
125
126        private EGL10 mEgl;
127        private EGLDisplay mEglDisplay;
128        private EGLConfig mEglConfig;
129        private EGLContext mEglContext;
130        private EGLSurface mEglSurface;
131
132        private static final String sSimpleVS =
133                "attribute vec4 position;\n" +
134                "attribute vec2 texCoords;\n" +
135                "varying vec2 outTexCoords;\n" +
136                "uniform mat4 projection;\n" +
137                "\nvoid main(void) {\n" +
138                "    outTexCoords = texCoords;\n" +
139                "    gl_Position = projection * position;\n" +
140                "}\n\n";
141        private static final String sSimpleFS =
142                "precision mediump float;\n\n" +
143                "varying vec2 outTexCoords;\n" +
144                "uniform sampler2D texture;\n" +
145                "\nvoid main(void) {\n" +
146                "    gl_FragColor = texture2D(texture, outTexCoords);\n" +
147                "}\n\n";
148
149        private static final int FLOAT_SIZE_BYTES = 4;
150        private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
151        private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
152        private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
153
154        private int mRotationAtLastSurfaceSizeUpdate = -1;
155        private int mDisplayWidthAtLastSurfaceSizeUpdate = -1;
156        private int mDisplayHeightAtLastSurfaceSizeUpdate = -1;
157
158        private int mLastRequestedWidth = -1;
159        private int mLastRequestedHeight = -1;
160        private AsyncTask<Void, Void, Bitmap> mLoader;
161        private boolean mNeedsDrawAfterLoadingWallpaper;
162        private boolean mSurfaceValid;
163
164        public DrawableEngine() {
165            super();
166            setFixedSizeAllowed(true);
167        }
168
169        public void trimMemory(int level) {
170            if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW
171                    && level <= ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL
172                    && mBackground != null) {
173                if (DEBUG) {
174                    Log.d(TAG, "trimMemory");
175                }
176                mBackground.recycle();
177                mBackground = null;
178                mBackgroundWidth = -1;
179                mBackgroundHeight = -1;
180                mWallpaperManager.forgetLoadedWallpaper();
181            }
182        }
183
184        @Override
185        public void onCreate(SurfaceHolder surfaceHolder) {
186            if (DEBUG) {
187                Log.d(TAG, "onCreate");
188            }
189
190            super.onCreate(surfaceHolder);
191
192            mDefaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay();
193            setOffsetNotificationsEnabled(false);
194
195            updateSurfaceSize(surfaceHolder, getDefaultDisplayInfo(), false /* forDraw */);
196        }
197
198        @Override
199        public void onDestroy() {
200            super.onDestroy();
201            mBackground = null;
202            mWallpaperManager.forgetLoadedWallpaper();
203        }
204
205        boolean updateSurfaceSize(SurfaceHolder surfaceHolder, DisplayInfo displayInfo,
206                boolean forDraw) {
207            boolean hasWallpaper = true;
208
209            // Load background image dimensions, if we haven't saved them yet
210            if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
211                // Need to load the image to get dimensions
212                mWallpaperManager.forgetLoadedWallpaper();
213                loadWallpaper(forDraw);
214                if (DEBUG) {
215                    Log.d(TAG, "Reloading, redoing updateSurfaceSize later.");
216                }
217                hasWallpaper = false;
218            }
219
220            // Force the wallpaper to cover the screen in both dimensions
221            int surfaceWidth = Math.max(displayInfo.logicalWidth, mBackgroundWidth);
222            int surfaceHeight = Math.max(displayInfo.logicalHeight, mBackgroundHeight);
223
224            if (FIXED_SIZED_SURFACE) {
225                // Used a fixed size surface, because we are special.  We can do
226                // this because we know the current design of window animations doesn't
227                // cause this to break.
228                surfaceHolder.setFixedSize(surfaceWidth, surfaceHeight);
229                mLastRequestedWidth = surfaceWidth;
230                mLastRequestedHeight = surfaceHeight;
231            } else {
232                surfaceHolder.setSizeFromLayout();
233            }
234            return hasWallpaper;
235        }
236
237        @Override
238        public void onVisibilityChanged(boolean visible) {
239            if (DEBUG) {
240                Log.d(TAG, "onVisibilityChanged: mVisible, visible=" + mVisible + ", " + visible);
241            }
242
243            if (mVisible != visible) {
244                if (DEBUG) {
245                    Log.d(TAG, "Visibility changed to visible=" + visible);
246                }
247                mVisible = visible;
248                if (visible) {
249                    drawFrame();
250                }
251            }
252        }
253
254        @Override
255        public void onOffsetsChanged(float xOffset, float yOffset,
256                float xOffsetStep, float yOffsetStep,
257                int xPixels, int yPixels) {
258            if (DEBUG) {
259                Log.d(TAG, "onOffsetsChanged: xOffset=" + xOffset + ", yOffset=" + yOffset
260                        + ", xOffsetStep=" + xOffsetStep + ", yOffsetStep=" + yOffsetStep
261                        + ", xPixels=" + xPixels + ", yPixels=" + yPixels);
262            }
263
264            if (mXOffset != xOffset || mYOffset != yOffset) {
265                if (DEBUG) {
266                    Log.d(TAG, "Offsets changed to (" + xOffset + "," + yOffset + ").");
267                }
268                mXOffset = xOffset;
269                mYOffset = yOffset;
270                mOffsetsChanged = true;
271            }
272            drawFrame();
273        }
274
275        @Override
276        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
277            if (DEBUG) {
278                Log.d(TAG, "onSurfaceChanged: width=" + width + ", height=" + height);
279            }
280
281            super.onSurfaceChanged(holder, format, width, height);
282
283            drawFrame();
284        }
285
286        @Override
287        public void onSurfaceDestroyed(SurfaceHolder holder) {
288            super.onSurfaceDestroyed(holder);
289            if (DEBUG) {
290                Log.i(TAG, "onSurfaceDestroyed");
291            }
292
293            mLastSurfaceWidth = mLastSurfaceHeight = -1;
294            mSurfaceValid = false;
295        }
296
297        @Override
298        public void onSurfaceCreated(SurfaceHolder holder) {
299            super.onSurfaceCreated(holder);
300            if (DEBUG) {
301                Log.i(TAG, "onSurfaceCreated");
302            }
303
304            mLastSurfaceWidth = mLastSurfaceHeight = -1;
305            mSurfaceValid = true;
306        }
307
308        @Override
309        public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
310            if (DEBUG) {
311                Log.d(TAG, "onSurfaceRedrawNeeded");
312            }
313            super.onSurfaceRedrawNeeded(holder);
314            drawFrame();
315        }
316
317        private DisplayInfo getDefaultDisplayInfo() {
318            mDefaultDisplay.getDisplayInfo(mTmpDisplayInfo);
319            return mTmpDisplayInfo;
320        }
321
322        void drawFrame() {
323            if (!mSurfaceValid) {
324                return;
325            }
326            try {
327                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawWallpaper");
328                DisplayInfo displayInfo = getDefaultDisplayInfo();
329                int newRotation = displayInfo.rotation;
330
331                // Sometimes a wallpaper is not large enough to cover the screen in one dimension.
332                // Call updateSurfaceSize -- it will only actually do the update if the dimensions
333                // should change
334                if (newRotation != mLastRotation) {
335                    // Update surface size (if necessary)
336                    if (!updateSurfaceSize(getSurfaceHolder(), displayInfo, true /* forDraw */)) {
337                        return; // had to reload wallpaper, will retry later
338                    }
339                    mRotationAtLastSurfaceSizeUpdate = newRotation;
340                    mDisplayWidthAtLastSurfaceSizeUpdate = displayInfo.logicalWidth;
341                    mDisplayHeightAtLastSurfaceSizeUpdate = displayInfo.logicalHeight;
342                }
343                SurfaceHolder sh = getSurfaceHolder();
344                final Rect frame = sh.getSurfaceFrame();
345                final int dw = frame.width();
346                final int dh = frame.height();
347                boolean surfaceDimensionsChanged = dw != mLastSurfaceWidth
348                        || dh != mLastSurfaceHeight;
349
350                boolean redrawNeeded = surfaceDimensionsChanged || newRotation != mLastRotation;
351                if (!redrawNeeded && !mOffsetsChanged) {
352                    if (DEBUG) {
353                        Log.d(TAG, "Suppressed drawFrame since redraw is not needed "
354                                + "and offsets have not changed.");
355                    }
356                    return;
357                }
358                mLastRotation = newRotation;
359
360                // Load bitmap if it is not yet loaded
361                if (mBackground == null) {
362                    if (DEBUG) {
363                        Log.d(TAG, "Reloading bitmap: mBackground, bgw, bgh, dw, dh = " +
364                                mBackground + ", " +
365                                ((mBackground == null) ? 0 : mBackground.getWidth()) + ", " +
366                                ((mBackground == null) ? 0 : mBackground.getHeight()) + ", " +
367                                dw + ", " + dh);
368                    }
369                    mWallpaperManager.forgetLoadedWallpaper();
370                    loadWallpaper(true /* needDraw */);
371                    if (DEBUG) {
372                        Log.d(TAG, "Reloading, resuming draw later");
373                    }
374                    return;
375                }
376
377                // Center the scaled image
378                mScale = Math.max(1f, Math.max(dw / (float) mBackground.getWidth(),
379                        dh / (float) mBackground.getHeight()));
380                final int availw = dw - (int) (mBackground.getWidth() * mScale);
381                final int availh = dh - (int) (mBackground.getHeight() * mScale);
382                int xPixels = availw / 2;
383                int yPixels = availh / 2;
384
385                // Adjust the image for xOffset/yOffset values. If window manager is handling offsets,
386                // mXOffset and mYOffset are set to 0.5f by default and therefore xPixels and yPixels
387                // will remain unchanged
388                final int availwUnscaled = dw - mBackground.getWidth();
389                final int availhUnscaled = dh - mBackground.getHeight();
390                if (availwUnscaled < 0)
391                    xPixels += (int) (availwUnscaled * (mXOffset - .5f) + .5f);
392                if (availhUnscaled < 0)
393                    yPixels += (int) (availhUnscaled * (mYOffset - .5f) + .5f);
394
395                mOffsetsChanged = false;
396                if (surfaceDimensionsChanged) {
397                    mLastSurfaceWidth = dw;
398                    mLastSurfaceHeight = dh;
399                }
400                if (!redrawNeeded && xPixels == mLastXTranslation && yPixels == mLastYTranslation) {
401                    if (DEBUG) {
402                        Log.d(TAG, "Suppressed drawFrame since the image has not "
403                                + "actually moved an integral number of pixels.");
404                    }
405                    return;
406                }
407                mLastXTranslation = xPixels;
408                mLastYTranslation = yPixels;
409
410                if (DEBUG) {
411                    Log.d(TAG, "Redrawing wallpaper");
412                }
413
414                if (mIsHwAccelerated) {
415                    if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
416                        drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
417                    }
418                } else {
419                    drawWallpaperWithCanvas(sh, availw, availh, xPixels, yPixels);
420                }
421            } finally {
422                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
423                if (FIXED_SIZED_SURFACE && !mIsHwAccelerated) {
424                    // If the surface is fixed-size, we should only need to
425                    // draw it once and then we'll let the window manager
426                    // position it appropriately.  As such, we no longer needed
427                    // the loaded bitmap.  Yay!
428                    // hw-accelerated renderer retains bitmap for faster rotation
429                    mBackground = null;
430                    mWallpaperManager.forgetLoadedWallpaper();
431                }
432            }
433        }
434
435        /**
436         * Loads the wallpaper on background thread and schedules updating the surface frame,
437         * and if {@param needsDraw} is set also draws a frame.
438         *
439         * If loading is already in-flight, subsequent loads are ignored (but needDraw is or-ed to
440         * the active request).
441         */
442        private void loadWallpaper(boolean needsDraw) {
443            mNeedsDrawAfterLoadingWallpaper |= needsDraw;
444            if (mLoader != null) {
445                if (DEBUG) {
446                    Log.d(TAG, "Skipping loadWallpaper, already in flight ");
447                }
448                return;
449            }
450            mLoader = new AsyncTask<Void, Void, Bitmap>() {
451                @Override
452                protected Bitmap doInBackground(Void... params) {
453                    Throwable exception;
454                    try {
455                        return mWallpaperManager.getBitmap();
456                    } catch (RuntimeException | OutOfMemoryError e) {
457                        exception = e;
458                    }
459
460                    if (exception != null) {
461                        // Note that if we do fail at this, and the default wallpaper can't
462                        // be loaded, we will go into a cycle.  Don't do a build where the
463                        // default wallpaper can't be loaded.
464                        Log.w(TAG, "Unable to load wallpaper!", exception);
465                        try {
466                            mWallpaperManager.clear();
467                        } catch (IOException ex) {
468                            // now we're really screwed.
469                            Log.w(TAG, "Unable reset to default wallpaper!", ex);
470                        }
471
472                        try {
473                            return mWallpaperManager.getBitmap();
474                        } catch (RuntimeException | OutOfMemoryError e) {
475                            Log.w(TAG, "Unable to load default wallpaper!", e);
476                        }
477                    }
478                    return null;
479                }
480
481                @Override
482                protected void onPostExecute(Bitmap b) {
483                    mBackground = null;
484                    mBackgroundWidth = -1;
485                    mBackgroundHeight = -1;
486
487                    if (b != null) {
488                        mBackground = b;
489                        mBackgroundWidth = mBackground.getWidth();
490                        mBackgroundHeight = mBackground.getHeight();
491                    }
492
493                    if (DEBUG) {
494                        Log.d(TAG, "Wallpaper loaded: " + mBackground);
495                    }
496                    updateSurfaceSize(getSurfaceHolder(), getDefaultDisplayInfo(),
497                            false /* forDraw */);
498                    if (mNeedsDrawAfterLoadingWallpaper) {
499                        drawFrame();
500                    }
501
502                    mLoader = null;
503                    mNeedsDrawAfterLoadingWallpaper = false;
504                }
505            }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
506        }
507
508        @Override
509        protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
510            super.dump(prefix, fd, out, args);
511
512            out.print(prefix); out.println("ImageWallpaper.DrawableEngine:");
513            out.print(prefix); out.print(" mBackground="); out.print(mBackground);
514            out.print(" mBackgroundWidth="); out.print(mBackgroundWidth);
515            out.print(" mBackgroundHeight="); out.println(mBackgroundHeight);
516
517            out.print(prefix); out.print(" mLastRotation="); out.print(mLastRotation);
518            out.print(" mLastSurfaceWidth="); out.print(mLastSurfaceWidth);
519            out.print(" mLastSurfaceHeight="); out.println(mLastSurfaceHeight);
520
521            out.print(prefix); out.print(" mXOffset="); out.print(mXOffset);
522            out.print(" mYOffset="); out.println(mYOffset);
523
524            out.print(prefix); out.print(" mVisible="); out.print(mVisible);
525            out.print(" mOffsetsChanged="); out.println(mOffsetsChanged);
526
527            out.print(prefix); out.print(" mLastXTranslation="); out.print(mLastXTranslation);
528            out.print(" mLastYTranslation="); out.print(mLastYTranslation);
529            out.print(" mScale="); out.println(mScale);
530
531            out.print(prefix); out.print(" mLastRequestedWidth="); out.print(mLastRequestedWidth);
532            out.print(" mLastRequestedHeight="); out.println(mLastRequestedHeight);
533
534            out.print(prefix); out.println(" DisplayInfo at last updateSurfaceSize:");
535            out.print(prefix);
536            out.print("  rotation="); out.print(mRotationAtLastSurfaceSizeUpdate);
537            out.print("  width="); out.print(mDisplayWidthAtLastSurfaceSizeUpdate);
538            out.print("  height="); out.println(mDisplayHeightAtLastSurfaceSizeUpdate);
539        }
540
541        private void drawWallpaperWithCanvas(SurfaceHolder sh, int w, int h, int left, int top) {
542            Canvas c = sh.lockCanvas();
543            if (c != null) {
544                try {
545                    if (DEBUG) {
546                        Log.d(TAG, "Redrawing: left=" + left + ", top=" + top);
547                    }
548
549                    final float right = left + mBackground.getWidth() * mScale;
550                    final float bottom = top + mBackground.getHeight() * mScale;
551                    if (w < 0 || h < 0) {
552                        c.save(Canvas.CLIP_SAVE_FLAG);
553                        c.clipRect(left, top, right, bottom,
554                                Op.DIFFERENCE);
555                        c.drawColor(0xff000000);
556                        c.restore();
557                    }
558                    if (mBackground != null) {
559                        RectF dest = new RectF(left, top, right, bottom);
560                        // add a filter bitmap?
561                        c.drawBitmap(mBackground, null, dest, null);
562                    }
563                } finally {
564                    sh.unlockCanvasAndPost(c);
565                }
566            }
567        }
568
569        private boolean drawWallpaperWithOpenGL(SurfaceHolder sh, int w, int h, int left, int top) {
570            if (!initGL(sh)) return false;
571
572            final float right = left + mBackground.getWidth() * mScale;
573            final float bottom = top + mBackground.getHeight() * mScale;
574
575            final Rect frame = sh.getSurfaceFrame();
576            final Matrix4f ortho = new Matrix4f();
577            ortho.loadOrtho(0.0f, frame.width(), frame.height(), 0.0f, -1.0f, 1.0f);
578
579            final FloatBuffer triangleVertices = createMesh(left, top, right, bottom);
580
581            final int texture = loadTexture(mBackground);
582            final int program = buildProgram(sSimpleVS, sSimpleFS);
583
584            final int attribPosition = glGetAttribLocation(program, "position");
585            final int attribTexCoords = glGetAttribLocation(program, "texCoords");
586            final int uniformTexture = glGetUniformLocation(program, "texture");
587            final int uniformProjection = glGetUniformLocation(program, "projection");
588
589            checkGlError();
590
591            glViewport(0, 0, frame.width(), frame.height());
592            glBindTexture(GL_TEXTURE_2D, texture);
593
594            glUseProgram(program);
595            glEnableVertexAttribArray(attribPosition);
596            glEnableVertexAttribArray(attribTexCoords);
597            glUniform1i(uniformTexture, 0);
598            glUniformMatrix4fv(uniformProjection, 1, false, ortho.getArray(), 0);
599
600            checkGlError();
601
602            if (w > 0 || h > 0) {
603                glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
604                glClear(GL_COLOR_BUFFER_BIT);
605            }
606
607            // drawQuad
608            triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
609            glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
610                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
611
612            triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
613            glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
614                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
615
616            glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
617
618            boolean status = mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
619            checkEglError();
620
621            finishGL(texture, program);
622
623            return status;
624        }
625
626        private FloatBuffer createMesh(int left, int top, float right, float bottom) {
627            final float[] verticesData = {
628                    // X, Y, Z, U, V
629                     left,  bottom, 0.0f, 0.0f, 1.0f,
630                     right, bottom, 0.0f, 1.0f, 1.0f,
631                     left,  top,    0.0f, 0.0f, 0.0f,
632                     right, top,    0.0f, 1.0f, 0.0f,
633            };
634
635            final int bytes = verticesData.length * FLOAT_SIZE_BYTES;
636            final FloatBuffer triangleVertices = ByteBuffer.allocateDirect(bytes).order(
637                    ByteOrder.nativeOrder()).asFloatBuffer();
638            triangleVertices.put(verticesData).position(0);
639            return triangleVertices;
640        }
641
642        private int loadTexture(Bitmap bitmap) {
643            int[] textures = new int[1];
644
645            glActiveTexture(GL_TEXTURE0);
646            glGenTextures(1, textures, 0);
647            checkGlError();
648
649            int texture = textures[0];
650            glBindTexture(GL_TEXTURE_2D, texture);
651            checkGlError();
652
653            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
654            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
655
656            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
657            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
658
659            GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
660            checkGlError();
661
662            return texture;
663        }
664
665        private int buildProgram(String vertex, String fragment) {
666            int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
667            if (vertexShader == 0) return 0;
668
669            int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
670            if (fragmentShader == 0) return 0;
671
672            int program = glCreateProgram();
673            glAttachShader(program, vertexShader);
674            glAttachShader(program, fragmentShader);
675            glLinkProgram(program);
676            checkGlError();
677
678            glDeleteShader(vertexShader);
679            glDeleteShader(fragmentShader);
680
681            int[] status = new int[1];
682            glGetProgramiv(program, GL_LINK_STATUS, status, 0);
683            if (status[0] != GL_TRUE) {
684                String error = glGetProgramInfoLog(program);
685                Log.d(GL_LOG_TAG, "Error while linking program:\n" + error);
686                glDeleteProgram(program);
687                return 0;
688            }
689
690            return program;
691        }
692
693        private int buildShader(String source, int type) {
694            int shader = glCreateShader(type);
695
696            glShaderSource(shader, source);
697            checkGlError();
698
699            glCompileShader(shader);
700            checkGlError();
701
702            int[] status = new int[1];
703            glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
704            if (status[0] != GL_TRUE) {
705                String error = glGetShaderInfoLog(shader);
706                Log.d(GL_LOG_TAG, "Error while compiling shader:\n" + error);
707                glDeleteShader(shader);
708                return 0;
709            }
710
711            return shader;
712        }
713
714        private void checkEglError() {
715            int error = mEgl.eglGetError();
716            if (error != EGL_SUCCESS) {
717                Log.w(GL_LOG_TAG, "EGL error = " + GLUtils.getEGLErrorString(error));
718            }
719        }
720
721        private void checkGlError() {
722            int error = glGetError();
723            if (error != GL_NO_ERROR) {
724                Log.w(GL_LOG_TAG, "GL error = 0x" + Integer.toHexString(error), new Throwable());
725            }
726        }
727
728        private void finishGL(int texture, int program) {
729            int[] textures = new int[1];
730            textures[0] = texture;
731            glDeleteTextures(1, textures, 0);
732            glDeleteProgram(program);
733            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
734            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
735            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
736            mEgl.eglTerminate(mEglDisplay);
737        }
738
739        private boolean initGL(SurfaceHolder surfaceHolder) {
740            mEgl = (EGL10) EGLContext.getEGL();
741
742            mEglDisplay = mEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
743            if (mEglDisplay == EGL_NO_DISPLAY) {
744                throw new RuntimeException("eglGetDisplay failed " +
745                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
746            }
747
748            int[] version = new int[2];
749            if (!mEgl.eglInitialize(mEglDisplay, version)) {
750                throw new RuntimeException("eglInitialize failed " +
751                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
752            }
753
754            mEglConfig = chooseEglConfig();
755            if (mEglConfig == null) {
756                throw new RuntimeException("eglConfig not initialized");
757            }
758
759            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
760            if (mEglContext == EGL_NO_CONTEXT) {
761                throw new RuntimeException("createContext failed " +
762                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
763            }
764
765            int attribs[] = {
766                EGL_WIDTH, 1,
767                EGL_HEIGHT, 1,
768                EGL_NONE
769            };
770            EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
771            mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext);
772
773            int[] maxSize = new int[1];
774            Rect frame = surfaceHolder.getSurfaceFrame();
775            glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0);
776
777            mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
778            mEgl.eglDestroySurface(mEglDisplay, tmpSurface);
779
780            if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) {
781                mEgl.eglDestroyContext(mEglDisplay, mEglContext);
782                mEgl.eglTerminate(mEglDisplay);
783                Log.e(GL_LOG_TAG, "requested  texture size " +
784                    frame.width() + "x" + frame.height() + " exceeds the support maximum of " +
785                    maxSize[0] + "x" + maxSize[0]);
786                return false;
787            }
788
789            mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null);
790            if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
791                int error = mEgl.eglGetError();
792                if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) {
793                    Log.e(GL_LOG_TAG, "createWindowSurface returned " +
794                                         GLUtils.getEGLErrorString(error) + ".");
795                    return false;
796                }
797                throw new RuntimeException("createWindowSurface failed " +
798                        GLUtils.getEGLErrorString(error));
799            }
800
801            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
802                throw new RuntimeException("eglMakeCurrent failed " +
803                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
804            }
805
806            return true;
807        }
808
809
810        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
811            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
812            return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, attrib_list);
813        }
814
815        private EGLConfig chooseEglConfig() {
816            int[] configsCount = new int[1];
817            EGLConfig[] configs = new EGLConfig[1];
818            int[] configSpec = getConfig();
819            if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
820                throw new IllegalArgumentException("eglChooseConfig failed " +
821                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
822            } else if (configsCount[0] > 0) {
823                return configs[0];
824            }
825            return null;
826        }
827
828        private int[] getConfig() {
829            return new int[] {
830                    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
831                    EGL_RED_SIZE, 8,
832                    EGL_GREEN_SIZE, 8,
833                    EGL_BLUE_SIZE, 8,
834                    EGL_ALPHA_SIZE, 0,
835                    EGL_DEPTH_SIZE, 0,
836                    EGL_STENCIL_SIZE, 0,
837                    EGL_CONFIG_CAVEAT, EGL_NONE,
838                    EGL_NONE
839            };
840        }
841    }
842}
843