Better backend for hardware layers.
With this new backend, a hardware layer is only recreated when
its associated view is udpated. This offers fast composition
in GL and fast update of the layer in GL as well.
Change-Id: I97c43a612f5955c6bf1c192c8ca4af10fdf1d076
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 04a521e..8c4e654 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -73,9 +73,21 @@
// Constructors
///////////////////////////////////////////////////////////////////////////
+ /**
+ * Creates a canvas to render directly on screen.
+ */
GLES20Canvas(boolean translucent) {
this(false, translucent);
}
+
+ /**
+ * Creates a canvas to render into an FBO.
+ */
+ GLES20Canvas(int fbo, boolean translucent) {
+ mOpaque = !translucent;
+ mRenderer = nCreateLayerRenderer(fbo);
+ setupFinalizer();
+ }
protected GLES20Canvas(boolean record, boolean translucent) {
mOpaque = !translucent;
@@ -89,7 +101,11 @@
} else {
mRenderer = nCreateRenderer();
}
-
+
+ setupFinalizer();
+ }
+
+ private void setupFinalizer() {
if (mRenderer == 0) {
throw new IllegalStateException("Could not create GLES20Canvas renderer");
} else {
@@ -97,7 +113,8 @@
}
}
- private native int nCreateRenderer();
+ private static native int nCreateRenderer();
+ private static native int nCreateLayerRenderer(int fbo);
private static native int nGetDisplayListRenderer(int renderer);
private static native void nDestroyRenderer(int renderer);
@@ -136,6 +153,15 @@
}
///////////////////////////////////////////////////////////////////////////
+ // Hardware layers
+ ///////////////////////////////////////////////////////////////////////////
+
+ static native int nCreateLayer(int width, int height, int[] layerInfo);
+ static native void nResizeLayer(int layerId, int layerTextureId, int width, int height,
+ int[] layerInfo);
+ static native void nDestroyLayer(int layerId, int layerTextureId);
+
+ ///////////////////////////////////////////////////////////////////////////
// Canvas management
///////////////////////////////////////////////////////////////////////////
@@ -227,6 +253,34 @@
private native void nDrawDisplayList(int renderer, int displayList);
///////////////////////////////////////////////////////////////////////////
+ // Hardware layer
+ ///////////////////////////////////////////////////////////////////////////
+
+ void drawHardwareLayer(float left, float top, float right, float bottom,
+ HardwareLayer layer, Paint paint) {
+ final GLES20Layer glLayer = (GLES20Layer) layer;
+ boolean hasColorFilter = paint != null && setupColorFilter(paint);
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawLayer(mRenderer, left, top, right, bottom, glLayer.mLayerTextureId,
+ glLayer.getU(), glLayer.getV(), nativePaint);
+ if (hasColorFilter) nResetModifiers(mRenderer);
+ }
+
+ private native void nDrawLayer(int renderer, float left, float top, float right, float bottom,
+ int layerTexture, float u, float v, int paint);
+
+ void interrupt() {
+ nInterrupt(mRenderer);
+ }
+
+ void resume() {
+ nResume(mRenderer);
+ }
+
+ private native void nInterrupt(int renderer);
+ private native void nResume(int renderer);
+
+ ///////////////////////////////////////////////////////////////////////////
// Clipping
///////////////////////////////////////////////////////////////////////////
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index c548659..e813bc9 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -106,7 +106,11 @@
@Override
protected void finalize() throws Throwable {
- replaceNativeObject(0);
+ try {
+ replaceNativeObject(0);
+ } finally {
+ super.finalize();
+ }
}
}
}
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
new file mode 100644
index 0000000..336e07a
--- /dev/null
+++ b/core/java/android/view/GLES20Layer.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Canvas;
+
+/**
+ * An OpenGL ES 2.0 implementation of {@link HardwareLayer}.
+ */
+class GLES20Layer extends HardwareLayer {
+ private int mLayerId;
+ int mLayerTextureId;
+
+ private int mLayerWidth;
+ private int mLayerHeight;
+
+ private final GLES20Canvas mCanvas;
+
+ private float mU;
+ private float mV;
+
+ @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+ private final Finalizer mFinalizer;
+
+ GLES20Layer(int width, int height, boolean isOpaque) {
+ super(width, height, isOpaque);
+
+ int[] layerInfo = new int[3];
+ mLayerId = GLES20Canvas.nCreateLayer(width, height, layerInfo);
+ if (mLayerId != 0) {
+ mLayerWidth = layerInfo[0];
+ mLayerHeight = layerInfo[1];
+ mLayerTextureId = layerInfo[2];
+
+ mCanvas = new GLES20Canvas(mLayerId, !isOpaque);
+ mFinalizer = new Finalizer(mLayerId, mLayerTextureId);
+
+ mU = mWidth / (float) mLayerWidth;
+ mV = mHeight/ (float) mLayerHeight;
+ } else {
+ mCanvas = null;
+ mFinalizer = null;
+ }
+ }
+
+ float getU() {
+ return mU;
+ }
+
+ float getV() {
+ return mV;
+ }
+
+ @Override
+ boolean isValid() {
+ return mLayerId != 0 && mLayerWidth > 0 && mLayerHeight > 0;
+ }
+
+ @Override
+ void resize(int width, int height) {
+ if (!isValid() || width <= 0 || height <= 0) return;
+ if (width > mLayerWidth || height > mLayerHeight) {
+ mWidth = width;
+ mHeight = height;
+
+ int[] layerInfo = new int[3];
+
+ GLES20Canvas.nResizeLayer(mLayerId, mLayerTextureId, width, height, layerInfo);
+
+ mLayerWidth = layerInfo[0];
+ mLayerHeight = layerInfo[1];
+
+ mU = mWidth / (float) mLayerWidth;
+ mV = mHeight/ (float) mLayerHeight;
+ }
+ }
+
+ @Override
+ HardwareCanvas getCanvas() {
+ return mCanvas;
+ }
+
+ @Override
+ void end(Canvas currentCanvas) {
+ if (currentCanvas instanceof GLES20Canvas) {
+ ((GLES20Canvas) currentCanvas).resume();
+ }
+ }
+
+ @Override
+ HardwareCanvas start(Canvas currentCanvas) {
+ if (currentCanvas instanceof GLES20Canvas) {
+ ((GLES20Canvas) currentCanvas).interrupt();
+ }
+ return getCanvas();
+ }
+
+ @Override
+ void destroy() {
+ mFinalizer.destroy();
+ mLayerId = mLayerTextureId = 0;
+ }
+
+ private static class Finalizer {
+ private int mLayerId;
+ private int mLayerTextureId;
+
+ public Finalizer(int layerId, int layerTextureId) {
+ mLayerId = layerId;
+ mLayerTextureId = layerTextureId;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mLayerId != 0 || mLayerTextureId != 0) {
+ GLES20Canvas.nDestroyLayer(mLayerId, mLayerTextureId);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ void destroy() {
+ GLES20Canvas.nDestroyLayer(mLayerId, mLayerTextureId);
+ mLayerId = mLayerTextureId = 0;
+ }
+ }
+}
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index 7f86d99..2603281 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -36,7 +36,8 @@
class GLES20RecordingCanvas extends GLES20Canvas {
// These lists ensure that any Bitmaps recorded by a DisplayList are kept alive as long
// as the DisplayList is alive.
- private HashSet<Bitmap> mBitmaps = new HashSet<Bitmap>();
+ @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
+ private final HashSet<Bitmap> mBitmaps = new HashSet<Bitmap>();
GLES20RecordingCanvas(boolean translucent) {
super(true, translucent);
@@ -57,12 +58,6 @@
}
@Override
- public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
- Paint paint) {
- super.drawArc(oval, startAngle, sweepAngle, useCenter, paint);
- }
-
- @Override
public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) {
super.drawPatch(bitmap, chunks, dst, paint);
mBitmaps.add(bitmap);
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 8b8d15e..1a5df98 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -18,6 +18,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Paint;
/**
* Hardware accelerated canvas.
@@ -48,5 +49,18 @@
*
* @param displayList The display list to replay.
*/
- public abstract void drawDisplayList(DisplayList displayList);
+ abstract void drawDisplayList(DisplayList displayList);
+
+ /**
+ * Draws the specified layer onto this canvas.
+ *
+ * @param left The left coordinate of the layer
+ * @param top The top coordinate of the layer
+ * @param right The right coordinate of the layer
+ * @param bottom The bottom coordinate of the layer
+ * @param layer The layer to composite on this canvas
+ * @param paint The paint used to draw the layer
+ */
+ abstract void drawHardwareLayer(float left, float top, float right, float bottom,
+ HardwareLayer layer, Paint paint);
}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
new file mode 100644
index 0000000..d01b8ce
--- /dev/null
+++ b/core/java/android/view/HardwareLayer.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Canvas;
+
+/**
+ * A hardware layer can be used to render graphics operations into a hardware
+ * friendly buffer. For instance, with an OpenGL backend, a hardware layer
+ * would use a Frame Buffer Object (FBO.) The hardware layer can be used as
+ * a drawing cache when a complex set of graphics operations needs to be
+ * drawn several times.
+ */
+abstract class HardwareLayer {
+ int mWidth;
+ int mHeight;
+
+ final boolean mOpaque;
+
+ /**
+ * Creates a new hardware layer at least as large as the supplied
+ * dimensions.
+ *
+ * @param width The minimum width of the layer
+ * @param height The minimum height of the layer
+ * @param isOpaque Whether the layer should be opaque or not
+ */
+ HardwareLayer(int width, int height, boolean isOpaque) {
+ mWidth = width;
+ mHeight = height;
+ mOpaque = isOpaque;
+ }
+
+ /**
+ * Returns the minimum width of the layer.
+ *
+ * @return The minimum desired width of the hardware layer
+ */
+ int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * Returns the minimum height of the layer.
+ *
+ * @return The minimum desired height of the hardware layer
+ */
+ int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * Returns whether or not this layer is opaque.
+ *
+ * @return True if the layer is opaque, false otherwise
+ */
+ boolean isOpaque() {
+ return mOpaque;
+ }
+
+ /**
+ * Indicates whether this layer can be rendered.
+ *
+ * @return True if the layer can be rendered into, false otherwise
+ */
+ abstract boolean isValid();
+
+ /**
+ * Resizes the layer, if necessary, to be at least as large
+ * as the supplied dimensions.
+ *
+ * @param width The new desired minimum width for this layer
+ * @param height The new desired minimum height for this layer
+ */
+ abstract void resize(int width, int height);
+
+ /**
+ * Returns a hardware canvas that can be used to render onto
+ * this layer.
+ *
+ * @return A hardware canvas, or null if a canvas cannot be created
+ */
+ abstract HardwareCanvas getCanvas();
+
+ /**
+ * Destroys resources without waiting for a GC.
+ */
+ abstract void destroy();
+
+ /**
+ * This must be invoked before drawing onto this layer.
+ * @param currentCanvas
+ */
+ abstract HardwareCanvas start(Canvas currentCanvas);
+
+ /**
+ * This must be invoked after drawing onto this layer.
+ * @param currentCanvas
+ */
+ abstract void end(Canvas currentCanvas);
+}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 63490ee..05a9ff8 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -116,6 +116,17 @@
abstract DisplayList createDisplayList();
/**
+ * Creates a new hardware layer.
+ *
+ * @param width The minimum width of the layer
+ * @param height The minimum height of the layer
+ * @param isOpaque Whether the layer should be opaque or not
+ *
+ * @return A hardware layer
+ */
+ abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque);
+
+ /**
* Initializes the hardware renderer for the specified surface and setup the
* renderer for drawing, if needed. This is invoked when the ViewRoot has
* potentially lost the hardware renderer. The hardware renderer should be
@@ -682,6 +693,11 @@
DisplayList createDisplayList() {
return new GLES20DisplayList();
}
+
+ @Override
+ HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
+ return new GLES20Layer(width, height, isOpaque);
+ }
static HardwareRenderer create(boolean translucent) {
if (GLES20Canvas.isAvailable()) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 32f0b14..703084f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2049,6 +2049,7 @@
private Bitmap mDrawingCache;
private Bitmap mUnscaledDrawingCache;
private DisplayList mDisplayList;
+ private HardwareLayer mHardwareLayer;
/**
* When this view has focus and the next focus is {@link #FOCUS_LEFT},
@@ -2204,6 +2205,10 @@
*
* <p>A hardware layer is useful to apply a specific color filter and/or
* blending mode and/or translucency to a view and all its children.</p>
+ * <p>A hardware layer can be used to cache a complex view tree into a
+ * texture and reduce the complexity of drawing operations. For instance,
+ * when animating a complex view tree with a translation, a hardware layer can
+ * be used to render the view tree only once.</p>
* <p>A hardware layer can also be used to increase the rendering quality when
* rotation transformations are applied on a view. It can also be used to
* prevent potential clipping issues when applying 3D transforms on a view.</p>
@@ -7551,9 +7556,16 @@
*/
protected void onDetachedFromWindow() {
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+
removeUnsetPressCallback();
removeLongPressCallback();
+
destroyDrawingCache();
+
+ if (mHardwareLayer != null) {
+ mHardwareLayer.destroy();
+ mHardwareLayer = null;
+ }
}
/**
@@ -7868,23 +7880,36 @@
throw new IllegalArgumentException("Layer type can only be one of: LAYER_TYPE_NONE, "
+ "LAYER_TYPE_SOFTWARE or LAYER_TYPE_HARDWARE");
}
+
+ if (layerType == mLayerType) return;
// Destroy any previous software drawing cache if needed
- if (mLayerType == LAYER_TYPE_SOFTWARE && layerType != LAYER_TYPE_SOFTWARE) {
- if (mDrawingCache != null) {
- mDrawingCache.recycle();
- mDrawingCache = null;
- }
-
- if (mUnscaledDrawingCache != null) {
- mUnscaledDrawingCache.recycle();
- mUnscaledDrawingCache = null;
- }
+ switch (mLayerType) {
+ case LAYER_TYPE_SOFTWARE:
+ if (mDrawingCache != null) {
+ mDrawingCache.recycle();
+ mDrawingCache = null;
+ }
+
+ if (mUnscaledDrawingCache != null) {
+ mUnscaledDrawingCache.recycle();
+ mUnscaledDrawingCache = null;
+ }
+ break;
+ case LAYER_TYPE_HARDWARE:
+ if (mHardwareLayer != null) {
+ mHardwareLayer.destroy();
+ mHardwareLayer = null;
+ }
+ break;
+ default:
+ break;
}
mLayerType = layerType;
mLayerPaint = mLayerType == LAYER_TYPE_NONE ? null : paint;
+ // TODO: Make sure we invalidate the parent's display list
invalidate();
}
@@ -7905,6 +7930,62 @@
public int getLayerType() {
return mLayerType;
}
+
+ /**
+ * <p>Returns a hardware layer that can be used to draw this view again
+ * without executing its draw method.</p>
+ *
+ * @return A HardwareLayer ready to render, or null if an error occurred.
+ */
+ HardwareLayer getHardwareLayer(Canvas currentCanvas) {
+ if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+ return null;
+ }
+
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+
+ if (width == 0 || height == 0) {
+ return null;
+ }
+
+ if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) {
+ if (mHardwareLayer == null) {
+ mHardwareLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
+ width, height, isOpaque());
+ } else if (mHardwareLayer.getWidth() != width || mHardwareLayer.getHeight() != height) {
+ mHardwareLayer.resize(width, height);
+ }
+
+ final HardwareCanvas canvas = mHardwareLayer.start(currentCanvas);
+ try {
+ canvas.setViewport(width, height);
+ canvas.onPreDraw();
+
+ computeScroll();
+ canvas.translate(-mScrollX, -mScrollY);
+
+ final int restoreCount = canvas.save();
+
+ mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
+
+ // Fast path for layouts with no backgrounds
+ if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+ mPrivateFlags &= ~DIRTY_MASK;
+ dispatchDraw(canvas);
+ } else {
+ draw(canvas);
+ }
+
+ canvas.restoreToCount(restoreCount);
+ } finally {
+ canvas.onPostDraw();
+ mHardwareLayer.end(currentCanvas);
+ }
+ }
+
+ return mHardwareLayer;
+ }
/**
* <p>Enables or disables the drawing cache. When the drawing cache is enabled, the next call
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7af611e..ed5eed1 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2179,7 +2179,7 @@
boolean scalingRequired = false;
boolean caching;
- final int layerType = child.getLayerType();
+ int layerType = child.getLayerType();
if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE ||
(flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) {
@@ -2278,6 +2278,7 @@
if (caching) {
if (!canvas.isHardwareAccelerated()) {
if (layerType != LAYER_TYPE_NONE) {
+ layerType = LAYER_TYPE_SOFTWARE;
child.buildDrawingCache(true);
}
cache = child.getDrawingCache(true);
@@ -2354,13 +2355,11 @@
}
if (layerType != LAYER_TYPE_NONE && child.mLayerPaint != null) {
child.mLayerPaint.setAlpha(multipliedAlpha);
- canvas.saveLayer(sx, sy, sx + cr - cl, sy + cb - ct,
- child.mLayerPaint, layerFlags);
} else {
canvas.saveLayerAlpha(sx, sy, sx + cr - cl, sy + cb - ct,
multipliedAlpha, layerFlags);
+ layerSaved = true;
}
- layerSaved = true;
} else {
// Alpha is handled by the child directly, clobber the layer's alpha
if (layerType != LAYER_TYPE_NONE && child.mLayerPaint != null) {
@@ -2388,24 +2387,35 @@
}
if (hasNoCache) {
+ boolean layerRendered = false;
if (!layerSaved && layerType == LAYER_TYPE_HARDWARE) {
- canvas.saveLayer(sx, sy, sx + cr - cl, sy + cb - ct, child.mLayerPaint,
- Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
- }
- if (!hasDisplayList) {
- // Fast path for layouts with no backgrounds
- if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
- if (ViewDebug.TRACE_HIERARCHY) {
- ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
- }
- child.mPrivateFlags &= ~DIRTY_MASK;
- child.dispatchDraw(canvas);
+ final HardwareLayer layer = child.getHardwareLayer(canvas);
+ if (layer != null && layer.isValid()) {
+ ((HardwareCanvas) canvas).drawHardwareLayer(0, 0, cr - cl, cb - ct,
+ layer, child.mLayerPaint);
+ layerRendered = true;
} else {
- child.draw(canvas);
+ canvas.saveLayer(sx, sy, sx + cr - cl, sy + cb - ct, child.mLayerPaint,
+ Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
}
- } else {
- child.mPrivateFlags &= ~DIRTY_MASK;
- ((HardwareCanvas) canvas).drawDisplayList(displayList);
+ }
+
+ if (!layerRendered) {
+ if (!hasDisplayList) {
+ // Fast path for layouts with no backgrounds
+ if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+ if (ViewDebug.TRACE_HIERARCHY) {
+ ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
+ }
+ child.mPrivateFlags &= ~DIRTY_MASK;
+ child.dispatchDraw(canvas);
+ } else {
+ child.draw(canvas);
+ }
+ } else {
+ child.mPrivateFlags &= ~DIRTY_MASK;
+ ((HardwareCanvas) canvas).drawDisplayList(displayList);
+ }
}
} else if (cache != null) {
child.mPrivateFlags &= ~DIRTY_MASK;
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index b4c868f..d6991e6 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -32,6 +32,7 @@
#include <SkXfermode.h>
#include <DisplayListRenderer.h>
+#include <LayerRenderer.h>
#include <OpenGLDebugRenderer.h>
#include <OpenGLRenderer.h>
#include <SkiaShader.h>
@@ -77,7 +78,7 @@
// Constructors
// ----------------------------------------------------------------------------
-static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject canvas) {
+static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject clazz) {
RENDERER_LOGD("Create OpenGLRenderer");
#if PROFILE_RENDERER
return new OpenGLDebugRenderer;
@@ -442,6 +443,71 @@
renderer->drawDisplayList(displayList);
}
+// ----------------------------------------------------------------------------
+// Layers
+// ----------------------------------------------------------------------------
+
+static void android_view_GLES20Canvas_interrupt(JNIEnv* env, jobject canvas,
+ OpenGLRenderer* renderer) {
+ renderer->interrupt();
+}
+
+static void android_view_GLES20Canvas_resume(JNIEnv* env, jobject canvas,
+ OpenGLRenderer* renderer) {
+ renderer->resume();
+}
+
+static OpenGLRenderer* android_view_GLES20Canvas_createLayerRenderer(JNIEnv* env,
+ jobject clazz, jint fbo) {
+ return new LayerRenderer(fbo);
+}
+
+static jint android_view_GLES20Canvas_createLayer(JNIEnv* env,
+ jobject clazz, jint width, jint height, jintArray layerInfo) {
+ uint32_t layerWidth = 0;
+ uint32_t layerHeight = 0;
+ GLuint textureId = 0;
+
+ jint layerId = LayerRenderer::createLayer(width, height,
+ &layerWidth, &layerHeight, &textureId);
+
+ if (layerId) {
+ jint* storage = env->GetIntArrayElements(layerInfo, NULL);
+ storage[0] = layerWidth;
+ storage[1] = layerHeight;
+ storage[2] = textureId;
+ env->ReleaseIntArrayElements(layerInfo, storage, 0);
+ }
+
+ return layerId;
+}
+
+static void android_view_GLES20Canvas_resizeLayer(JNIEnv* env,
+ jobject clazz, jint layerId, jint layerTextureId, jint width, jint height,
+ jintArray layerInfo) {
+ uint32_t layerWidth = 0;
+ uint32_t layerHeight = 0;
+
+ LayerRenderer::resizeLayer(layerId, layerTextureId, width, height, &layerWidth, &layerHeight);
+
+ jint* storage = env->GetIntArrayElements(layerInfo, NULL);
+ storage[0] = layerWidth;
+ storage[1] = layerHeight;
+ env->ReleaseIntArrayElements(layerInfo, storage, 0);
+}
+
+static void android_view_GLES20Canvas_destroyLayer(JNIEnv* env,
+ jobject clazz, jint layerId, jint layerTextureId) {
+ LayerRenderer::destroyLayer(layerId, layerTextureId);
+}
+
+static void android_view_GLES20Canvas_drawLayer(JNIEnv* env,
+ jobject canvas, OpenGLRenderer* renderer,
+ jfloat left, jfloat top, jfloat right, jfloat bottom,
+ jint layerTexture, jfloat u, jfloat v, SkPaint* paint) {
+ renderer->drawLayer(layerTexture, left, top, right, bottom, u, v, paint);
+}
+
#endif // USE_OPENGL_RENDERER
// ----------------------------------------------------------------------------
@@ -522,10 +588,20 @@
{ "nGetClipBounds", "(ILandroid/graphics/Rect;)Z",
(void*) android_view_GLES20Canvas_getClipBounds },
- { "nGetDisplayList", "(I)I", (void*) android_view_GLES20Canvas_getDisplayList },
- { "nDestroyDisplayList", "(I)V", (void*) android_view_GLES20Canvas_destroyDisplayList },
- { "nGetDisplayListRenderer", "(I)I", (void*) android_view_GLES20Canvas_getDisplayListRenderer },
- { "nDrawDisplayList", "(II)V", (void*) android_view_GLES20Canvas_drawDisplayList },
+ { "nGetDisplayList", "(I)I", (void*) android_view_GLES20Canvas_getDisplayList },
+ { "nDestroyDisplayList", "(I)V", (void*) android_view_GLES20Canvas_destroyDisplayList },
+ { "nGetDisplayListRenderer", "(I)I", (void*) android_view_GLES20Canvas_getDisplayListRenderer },
+ { "nDrawDisplayList", "(II)V", (void*) android_view_GLES20Canvas_drawDisplayList },
+
+ { "nInterrupt", "(I)V", (void*) android_view_GLES20Canvas_interrupt },
+ { "nResume", "(I)V", (void*) android_view_GLES20Canvas_resume },
+
+ { "nCreateLayerRenderer", "(I)I", (void*) android_view_GLES20Canvas_createLayerRenderer },
+ { "nCreateLayer", "(II[I)I", (void*) android_view_GLES20Canvas_createLayer },
+ { "nResizeLayer", "(IIII[I)V", (void*) android_view_GLES20Canvas_resizeLayer },
+ { "nDestroyLayer", "(II)V", (void*) android_view_GLES20Canvas_destroyLayer },
+ { "nDrawLayer", "(IFFFFIFFI)V",
+ (void*) android_view_GLES20Canvas_drawLayer },
#endif
};
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 29158e5..c49be93 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -13,6 +13,7 @@
FboCache.cpp \
GradientCache.cpp \
LayerCache.cpp \
+ LayerRenderer.cpp \
Matrix.cpp \
OpenGLDebugRenderer.cpp \
OpenGLRenderer.cpp \
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index afb82bf..fdb4e8c 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -241,6 +241,11 @@
renderer.drawDisplayList(getDisplayList());
}
break;
+ case DrawLayer: {
+ renderer.drawLayer(getInt(), getFloat(), getFloat(), getFloat(), getFloat(),
+ getFloat(), getFloat(), getPaint());
+ }
+ break;
case DrawBitmap: {
renderer.drawBitmap(getBitmap(), getFloat(), getFloat(), getPaint());
}
@@ -483,6 +488,16 @@
addDisplayList(displayList);
}
+void DisplayListRenderer::drawLayer(int texture, float left, float top, float right, float bottom,
+ float u, float v, SkPaint* paint) {
+ addOp(DisplayList::DrawLayer);
+ addInt(texture);
+ addBounds(left, top, right, bottom);
+ addFloat(u);
+ addFloat(v);
+ addPaint(paint);
+}
+
void DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float left, float top,
SkPaint* paint) {
addOp(DisplayList::DrawBitmap);
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index fedb174..62cb0e8 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -93,6 +93,7 @@
ConcatMatrix,
ClipRect,
DrawDisplayList,
+ DrawLayer,
DrawBitmap,
DrawBitmapMatrix,
DrawBitmapRect,
@@ -245,6 +246,8 @@
bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
void drawDisplayList(DisplayList* displayList);
+ void drawLayer(int texture, float left, float top, float right, float bottom,
+ float u, float v, SkPaint* paint);
void drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
void drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
new file mode 100644
index 0000000..3583b86
--- /dev/null
+++ b/libs/hwui/LayerRenderer.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "LayerRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Rendering
+///////////////////////////////////////////////////////////////////////////////
+
+void LayerRenderer::prepare(bool opaque) {
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &mPreviousFbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
+ OpenGLRenderer::prepare(opaque);
+}
+
+void LayerRenderer::finish() {
+ OpenGLRenderer::finish();
+ glBindFramebuffer(GL_FRAMEBUFFER, mPreviousFbo);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Static functions
+///////////////////////////////////////////////////////////////////////////////
+
+GLuint LayerRenderer::createLayer(uint32_t width, uint32_t height,
+ uint32_t* layerWidth, uint32_t* layerHeight, GLuint* texture) {
+ GLuint previousFbo;
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
+
+ GLuint fbo = 0;
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+ glActiveTexture(GL_TEXTURE0);
+ glGenTextures(1, texture);
+ glBindTexture(GL_TEXTURE_2D, *texture);
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+ if (glGetError() != GL_NO_ERROR) {
+ glDeleteBuffers(1, &fbo);
+ glDeleteTextures(1, texture);
+ glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+ return 0;
+ }
+
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ *texture, 0);
+
+ if (glGetError() != GL_NO_ERROR) {
+ glDeleteBuffers(1, &fbo);
+ glDeleteTextures(1, texture);
+ glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+ return 0;
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+ *layerWidth = width;
+ *layerHeight = height;
+
+ return fbo;
+}
+
+void LayerRenderer::resizeLayer(GLuint fbo, GLuint texture, uint32_t width, uint32_t height,
+ uint32_t* layerWidth, uint32_t* layerHeight) {
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, texture);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+ if (glGetError() != GL_NO_ERROR) {
+ glDeleteBuffers(1, &fbo);
+ glDeleteTextures(1, texture);
+ glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+ *layerWidth = 0;
+ *layerHeight = 0;
+
+ return;
+ }
+
+ *layerWidth = width;
+ *layerHeight = height;
+}
+
+void LayerRenderer::destroyLayer(GLuint fbo, GLuint texture) {
+ if (fbo) glDeleteFramebuffers(1, &fbo);
+ if (texture) glDeleteTextures(1, &texture);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
new file mode 100644
index 0000000..a8f1ff7
--- /dev/null
+++ b/libs/hwui/LayerRenderer.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_LAYER_RENDERER_H
+#define ANDROID_HWUI_LAYER_RENDERER_H
+
+#include "OpenGLRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Renderer
+///////////////////////////////////////////////////////////////////////////////
+
+class LayerRenderer: public OpenGLRenderer {
+public:
+ LayerRenderer(GLuint fbo): mFbo(fbo) {
+ }
+
+ ~LayerRenderer() {
+ }
+
+ void prepare(bool opaque);
+ void finish();
+
+ static GLuint createLayer(uint32_t width, uint32_t height,
+ uint32_t* layerWidth, uint32_t* layerHeight, GLuint* texture);
+ static void resizeLayer(GLuint fbo, GLuint texture, uint32_t width, uint32_t height,
+ uint32_t* layerWidth, uint32_t* layerHeight);
+ static void destroyLayer(GLuint fbo, GLuint texture);
+
+private:
+ GLuint mFbo;
+ GLuint mPreviousFbo;
+
+}; // class LayerRenderer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_LAYER_RENDERER_H
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 99bb6f0..aefefe4 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -178,7 +178,7 @@
#endif
}
-void OpenGLRenderer::acquireContext() {
+void OpenGLRenderer::interrupt() {
if (mCaches.currentProgram) {
if (mCaches.currentProgram->isInUse()) {
mCaches.currentProgram->remove();
@@ -188,7 +188,11 @@
mCaches.unbindMeshBuffer();
}
-void OpenGLRenderer::releaseContext() {
+void OpenGLRenderer::acquireContext() {
+ interrupt();
+}
+
+void OpenGLRenderer::resume() {
glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight());
glEnable(GL_SCISSOR_TEST);
@@ -205,6 +209,10 @@
glBlendEquation(GL_FUNC_ADD);
}
+void OpenGLRenderer::releaseContext() {
+ resume();
+}
+
///////////////////////////////////////////////////////////////////////////////
// State management
///////////////////////////////////////////////////////////////////////////////
@@ -1477,6 +1485,30 @@
finishDrawTexture();
}
+void OpenGLRenderer::drawLayer(int texture, float left, float top, float right, float bottom,
+ float u, float v, SkPaint* paint) {
+ if (quickReject(left, top, right, bottom)) {
+ return;
+ }
+
+ glActiveTexture(gTextureUnits[0]);
+ if (!texture) return;
+
+ mCaches.unbindMeshBuffer();
+ resetDrawTextureTexCoords(0.0f, v, u, 0.0f);
+
+ int alpha;
+ SkXfermode::Mode mode;
+ getAlphaAndMode(paint, &alpha, &mode);
+
+ // TODO: Should get the blend info from the caller
+ drawTextureMesh(left, top, right, bottom, texture, alpha / 255.0f, mode, true,
+ &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+ GL_TRIANGLE_STRIP, gMeshCount);
+
+ resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+}
+
///////////////////////////////////////////////////////////////////////////////
// Shaders
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 8f93f5b..5f45915 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -64,6 +64,10 @@
virtual void prepare(bool opaque);
virtual void finish();
+ // These two calls must not be recorded in display lists
+ void interrupt();
+ void resume();
+
virtual void acquireContext();
virtual void releaseContext();
@@ -91,6 +95,8 @@
virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
virtual void drawDisplayList(DisplayList* displayList);
+ virtual void drawLayer(int texture, float left, float top, float right, float bottom,
+ float u, float v, SkPaint* paint);
virtual void drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
virtual void drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
virtual void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 0fdc030..e73afa0 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -34,6 +34,15 @@
</activity>
<activity
+ android:name="ViewLayersActivity2"
+ android:label="_ViewLayers2">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
android:name="AlphaLayersActivity"
android:label="_αLayers">
<intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java
index aa1eb59..5ddcd20 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java
@@ -69,7 +69,7 @@
mMediumPaint = new Paint();
mMediumPaint.setAntiAlias(true);
- mMediumPaint.setColor(0xff0000ff);
+ mMediumPaint.setColor(0xe00000ff);
mMediumPaint.setStrokeWidth(10.0f);
mMediumPaint.setStyle(Paint.Style.STROKE);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity2.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity2.java
new file mode 100644
index 0000000..a0b1d78
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity2.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ViewLayersActivity2 extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.view_layers);
+
+ setupList(R.id.list1);
+ setupList(R.id.list2);
+ setupList(R.id.list3);
+ }
+
+ private void setupList(int listId) {
+ final ListView list = (ListView) findViewById(listId);
+ list.setAdapter(new SimpleListAdapter(this));
+ list.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ }
+
+ private static class SimpleListAdapter extends ArrayAdapter<String> {
+ public SimpleListAdapter(Context context) {
+ super(context, android.R.layout.simple_list_item_1, DATA_LIST);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ TextView v = (TextView) super.getView(position, convertView, parent);
+ final Resources r = getContext().getResources();
+ final DisplayMetrics metrics = r.getDisplayMetrics();
+ v.setCompoundDrawablePadding((int) (6 * metrics.density + 0.5f));
+ v.setCompoundDrawablesWithIntrinsicBounds(r.getDrawable(R.drawable.icon),
+ null, null, null);
+ return v;
+ }
+ }
+
+ private static final String[] DATA_LIST = {
+ "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
+ "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina",
+ "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan",
+ "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium",
+ "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia",
+ "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil",
+ "British Indian Ocean Territory", "British Virgin Islands", "Brunei", "Bulgaria",
+ "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
+ "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+ "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+ "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+ "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
+ "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
+ "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
+ "Former Yugoslav Republic of Macedonia", "France", "French Guiana", "French Polynesia",
+ "French Southern Territories", "Gabon", "Georgia", "Germany", "Ghana", "Gibraltar",
+ "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau",
+ "Guyana", "Haiti", "Heard Island and McDonald Islands", "Honduras", "Hong Kong", "Hungary",
+ "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica",
+ "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos",
+ "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
+ "Macau", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
+ "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova",
+ "Monaco", "Mongolia", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia",
+ "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand",
+ "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "North Korea", "Northern Marianas",
+ "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paraguay", "Peru",
+ "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar",
+ "Reunion", "Romania", "Russia", "Rwanda", "Sqo Tome and Principe", "Saint Helena",
+ "Saint Kitts and Nevis", "Saint Lucia", "Saint Pierre and Miquelon",
+ "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Saudi Arabia", "Senegal",
+ "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands",
+ "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Korea",
+ "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen", "Swaziland", "Sweden",
+ "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas",
+ "The Gambia", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey",
+ "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Virgin Islands", "Uganda",
+ "Ukraine", "United Arab Emirates", "United Kingdom",
+ "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan",
+ "Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara",
+ "Yemen", "Yugoslavia", "Zambia", "Zimbabwe"
+ };
+}