diff options
20 files changed, 905 insertions, 46 deletions
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 04a521e98ab0..8c4e65478865 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -73,9 +73,21 @@ class GLES20Canvas extends HardwareCanvas { // 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 @@ class GLES20Canvas extends HardwareCanvas { } else { mRenderer = nCreateRenderer(); } - + + setupFinalizer(); + } + + private void setupFinalizer() { if (mRenderer == 0) { throw new IllegalStateException("Could not create GLES20Canvas renderer"); } else { @@ -97,7 +113,8 @@ class GLES20Canvas extends HardwareCanvas { } } - 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 @@ class GLES20Canvas extends HardwareCanvas { } /////////////////////////////////////////////////////////////////////////// + // 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 @@ class GLES20Canvas extends HardwareCanvas { 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 c548659315de..e813bc9aebaf 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -106,7 +106,11 @@ class GLES20DisplayList extends DisplayList { @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 000000000000..336e07adfe5a --- /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 7f86d9970a65..2603281668e6 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -36,7 +36,8 @@ import java.util.HashSet; 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 @@ class GLES20RecordingCanvas extends GLES20Canvas { } @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 8b8d15ebdd33..1a5df98cc69f 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -18,6 +18,7 @@ package android.view; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; /** * Hardware accelerated canvas. @@ -48,5 +49,18 @@ abstract class HardwareCanvas extends Canvas { * * @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 000000000000..d01b8ce37854 --- /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 63490ee1dfc7..05a9ff8b5831 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -116,6 +116,17 @@ public abstract class HardwareRenderer { 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 @@ public abstract class HardwareRenderer { 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 32f0b14ba47d..703084ff1446 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2049,6 +2049,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * <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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ protected void onDetachedFromWindow() { mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; + removeUnsetPressCallback(); removeLongPressCallback(); + destroyDrawingCache(); + + if (mHardwareLayer != null) { + mHardwareLayer.destroy(); + mHardwareLayer = null; + } } /** @@ -7868,23 +7880,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility 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 class View implements Drawable.Callback, KeyEvent.Callback, Accessibility 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 7af611ea36b5..ed5eed159f0d 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2179,7 +2179,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager 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 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager 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 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } 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 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } 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); + 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 { + 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); + + 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); } - child.mPrivateFlags &= ~DIRTY_MASK; - child.dispatchDraw(canvas); } else { - child.draw(canvas); + child.mPrivateFlags &= ~DIRTY_MASK; + ((HardwareCanvas) canvas).drawDisplayList(displayList); } - } 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 b4c868f89ead..d6991e610bb9 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 @@ static struct { // 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 @@ static void android_view_GLES20Canvas_drawDisplayList(JNIEnv* env, 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 @@ static JNINativeMethod gMethods[] = { { "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 29158e512692..c49be9373135 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -13,6 +13,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) 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 afb82bf4bbd9..fdb4e8ce8532 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -241,6 +241,11 @@ void DisplayList::replay(OpenGLRenderer& renderer) { 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 @@ void DisplayListRenderer::drawDisplayList(DisplayList* displayList) { 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 fedb174dccff..62cb0e8bc874 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -93,6 +93,7 @@ public: ConcatMatrix, ClipRect, DrawDisplayList, + DrawLayer, DrawBitmap, DrawBitmapMatrix, DrawBitmapRect, @@ -245,6 +246,8 @@ public: 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 000000000000..3583b8658254 --- /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 000000000000..a8f1ff7c2349 --- /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 99bb6f01031a..aefefe4dc75f 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -178,7 +178,7 @@ void OpenGLRenderer::finish() { #endif } -void OpenGLRenderer::acquireContext() { +void OpenGLRenderer::interrupt() { if (mCaches.currentProgram) { if (mCaches.currentProgram->isInUse()) { mCaches.currentProgram->remove(); @@ -188,7 +188,11 @@ void OpenGLRenderer::acquireContext() { 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 @@ void OpenGLRenderer::releaseContext() { glBlendEquation(GL_FUNC_ADD); } +void OpenGLRenderer::releaseContext() { + resume(); +} + /////////////////////////////////////////////////////////////////////////////// // State management /////////////////////////////////////////////////////////////////////////////// @@ -1477,6 +1485,30 @@ void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) { 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 8f93f5bc60f5..5f45915ca940 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -64,6 +64,10 @@ public: 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 @@ public: 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 0fdc030b8899..e73afa0f4eb4 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 aa1eb5955585..5ddcd200b478 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 @@ public class PathsActivity extends Activity { 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 000000000000..a0b1d782d723 --- /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" + }; +} |