summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Reck <jreck@google.com>2013-11-05 13:27:50 -0800
committerJohn Reck <jreck@google.com>2013-12-09 15:57:09 -0800
commitcec24ae16e9a0a7c3075f1a8d9149bb7fb3813fc (patch)
tree9b2287a705b0634197262c13433f5c32aa848bdc
parent4598ea4e5e6b2accce5165a76f5e2d04ce46c74c (diff)
downloadframeworks_base-cec24ae16e9a0a7c3075f1a8d9149bb7fb3813fc.zip
frameworks_base-cec24ae16e9a0a7c3075f1a8d9149bb7fb3813fc.tar.gz
frameworks_base-cec24ae16e9a0a7c3075f1a8d9149bb7fb3813fc.tar.bz2
RenderThread work
Hacky prototype needs a private API to enable Change-Id: I21e0ddf3cdbd38a4036354b5d6012449e1a34849
-rw-r--r--core/java/android/view/GLRenderer.java70
-rw-r--r--core/java/android/view/HardwareRenderer.java32
-rw-r--r--core/java/android/view/ThreadedRenderer.java317
-rw-r--r--core/java/android/view/View.java9
-rw-r--r--core/java/android/view/ViewRootImpl.java224
-rw-r--r--core/jni/Android.mk1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_view_ThreadedRenderer.cpp91
-rw-r--r--libs/hwui/Android.mk5
-rw-r--r--libs/hwui/renderthread/RenderTask.cpp35
-rw-r--r--libs/hwui/renderthread/RenderTask.h39
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp86
-rw-r--r--libs/hwui/renderthread/RenderThread.h58
-rw-r--r--tests/RenderThreadTest/Android.mk18
-rw-r--r--tests/RenderThreadTest/AndroidManifest.xml29
-rw-r--r--tests/RenderThreadTest/res/drawable-hdpi/ic_launcher.pngbin0 -> 9397 bytes
-rw-r--r--tests/RenderThreadTest/res/drawable-mdpi/ic_launcher.pngbin0 -> 5237 bytes
-rw-r--r--tests/RenderThreadTest/res/drawable-xhdpi/ic_launcher.pngbin0 -> 14383 bytes
-rw-r--r--tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpgbin0 -> 561519 bytes
-rw-r--r--tests/RenderThreadTest/res/layout/activity_main.xml12
-rw-r--r--tests/RenderThreadTest/res/layout/activity_sub.xml39
-rw-r--r--tests/RenderThreadTest/res/layout/item_layout.xml12
-rw-r--r--tests/RenderThreadTest/res/values/strings.xml8
-rw-r--r--tests/RenderThreadTest/res/values/styles.xml11
-rw-r--r--tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java158
-rw-r--r--tests/RenderThreadTest/src/com/example/renderthread/SubActivity.java60
26 files changed, 1187 insertions, 129 deletions
diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
index a195231..70428bc 100644
--- a/core/java/android/view/GLRenderer.java
+++ b/core/java/android/view/GLRenderer.java
@@ -635,11 +635,11 @@ public class GLRenderer extends HardwareRenderer {
GLRenderer(boolean translucent) {
mTranslucent = translucent;
- loadSystemProperties(null);
+ loadSystemProperties();
}
@Override
- boolean loadSystemProperties(Surface surface) {
+ boolean loadSystemProperties() {
boolean value;
boolean changed = false;
@@ -1102,11 +1102,6 @@ public class GLRenderer extends HardwareRenderer {
}
@Override
- HardwareCanvas getCanvas() {
- return mCanvas;
- }
-
- @Override
void setName(String name) {
mName = name;
}
@@ -1129,6 +1124,66 @@ public class GLRenderer extends HardwareRenderer {
}
@Override
+ void drawDisplayList(DisplayList displayList, View.AttachInfo attachInfo,
+ HardwareDrawCallbacks callbacks, Rect dirty) {
+ if (canDraw()) {
+ if (!hasDirtyRegions()) {
+ dirty = null;
+ }
+
+ // We are already on the correct thread
+ final int surfaceState = checkRenderContextUnsafe();
+ if (surfaceState != SURFACE_STATE_ERROR) {
+ HardwareCanvas canvas = mCanvas;
+
+ if (mProfileEnabled) {
+ mProfileLock.lock();
+ }
+
+ dirty = beginFrame(canvas, dirty, surfaceState);
+
+ int saveCount = 0;
+ int status = DisplayList.STATUS_DONE;
+
+ long start = getSystemTime();
+ try {
+ status = prepareFrame(dirty);
+
+ saveCount = canvas.save();
+ callbacks.onHardwarePreDraw(canvas);
+
+ status |= drawDisplayList(attachInfo, canvas, displayList, status);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "An error has occurred while drawing:", e);
+ } finally {
+ callbacks.onHardwarePostDraw(canvas);
+ canvas.restoreToCount(saveCount);
+
+ mDrawDelta = getSystemTime() - start;
+
+ if (mDrawDelta > 0) {
+ mFrameCount++;
+
+ debugOverdraw(attachInfo, dirty, canvas, displayList);
+ debugDirtyRegions(dirty, canvas);
+ drawProfileData(attachInfo);
+ }
+ }
+
+ onPostDraw();
+
+ swapBuffers(status);
+
+ if (mProfileEnabled) {
+ mProfileLock.unlock();
+ }
+
+ attachInfo.mIgnoreDirtyState = false;
+ }
+ }
+ }
+
+ @Override
void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
Rect dirty) {
if (canDraw()) {
@@ -1144,7 +1199,6 @@ public class GLRenderer extends HardwareRenderer {
final int surfaceState = checkRenderContextUnsafe();
if (surfaceState != SURFACE_STATE_ERROR) {
HardwareCanvas canvas = mCanvas;
- attachInfo.mHardwareCanvas = canvas;
if (mProfileEnabled) {
mProfileLock.lock();
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 5c0be4a..434d473 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -188,6 +188,9 @@ public abstract class HardwareRenderer {
*/
public static boolean sSystemRendererDisabled = false;
+ /** @hide */
+ public static boolean sUseRenderThread = false;
+
private boolean mEnabled;
private boolean mRequested = true;
@@ -306,13 +309,6 @@ public abstract class HardwareRenderer {
abstract int getHeight();
/**
- * Gets the current canvas associated with this HardwareRenderer.
- *
- * @return the current HardwareCanvas
- */
- abstract HardwareCanvas getCanvas();
-
- /**
* Outputs extra debugging information in the specified file descriptor.
* @param pw
*/
@@ -335,7 +331,7 @@ public abstract class HardwareRenderer {
*
* @return True if a property has changed.
*/
- abstract boolean loadSystemProperties(Surface surface);
+ abstract boolean loadSystemProperties();
/**
* Sets the directory to use as a persistent storage for hardware rendering
@@ -412,6 +408,18 @@ public abstract class HardwareRenderer {
Rect dirty);
/**
+ * Temporary hook to draw a display list directly, only used if sUseRenderThread
+ * is true.
+ *
+ * @param displayList The display list to draw
+ * @param attachInfo AttachInfo tied to the specified view.
+ * @param callbacks Callbacks invoked when drawing happens.
+ * @param dirty The dirty rectangle to update, can be null.
+ */
+ abstract void drawDisplayList(DisplayList displayList, View.AttachInfo attachInfo,
+ HardwareDrawCallbacks callbacks, Rect dirty);
+
+ /**
* Creates a new hardware layer. A hardware layer built by calling this
* method will be treated as a texture layer, instead of as a render target.
*
@@ -517,10 +525,14 @@ public abstract class HardwareRenderer {
* @return A hardware renderer backed by OpenGL.
*/
static HardwareRenderer create(boolean translucent) {
+ HardwareRenderer renderer = null;
if (GLES20Canvas.isAvailable()) {
- return new GLRenderer(translucent);
+ renderer = new GLRenderer(translucent);
+ }
+ if (renderer != null && sUseRenderThread) {
+ renderer = new ThreadedRenderer((GLRenderer)renderer);
}
- return null;
+ return renderer;
}
/**
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
new file mode 100644
index 0000000..4087313
--- /dev/null
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2013 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.Rect;
+import android.graphics.SurfaceTexture;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.view.Surface.OutOfResourcesException;
+import android.view.View.AttachInfo;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+
+/**
+ * Hardware renderer that proxies the rendering to a render thread. Most calls
+ * are synchronous, however a few such as draw() are posted async. The display list
+ * is shared between the two threads and is guarded by a top level lock.
+ *
+ * The UI thread can block on the RenderThread, but RenderThread must never
+ * block on the UI thread.
+ *
+ * Note that although currently the EGL context & surfaces are created & managed
+ * by the render thread, the goal is to move that into a shared structure that can
+ * be managed by both threads. EGLSurface creation & deletion should ideally be
+ * done on the UI thread and not the RenderThread to avoid stalling the
+ * RenderThread with surface buffer allocation.
+ *
+ * @hide
+ */
+public class ThreadedRenderer extends HardwareRenderer {
+ private static final String LOGTAG = "ThreadedRenderer";
+
+ @SuppressWarnings("serial")
+ static HashMap<String, Method> sMethodLut = new HashMap<String, Method>() {{
+ Method[] methods = HardwareRenderer.class.getDeclaredMethods();
+ for (Method m : methods) {
+ put(m.getName(), m);
+ }
+ }};
+ static boolean sNeedsInit = true;
+
+ private HardwareRenderer mRemoteRenderer;
+ private int mWidth, mHeight;
+ private RTJob mPreviousDraw;
+
+ ThreadedRenderer(GLRenderer backingRenderer) {
+ mRemoteRenderer = backingRenderer;
+ setEnabled(true);
+ if (sNeedsInit) {
+ sNeedsInit = false;
+ postToRenderThread(new Runnable() {
+ @Override
+ public void run() {
+ // Hack to allow GLRenderer to create a handler to post the EGL
+ // destruction to, although it'll never run
+ Looper.prepare();
+ }
+ });
+ }
+ }
+
+ @Override
+ void destroy(boolean full) {
+ run("destroy", full);
+ }
+
+ @Override
+ boolean initialize(Surface surface) throws OutOfResourcesException {
+ return (Boolean) run("initialize", surface);
+ }
+
+ @Override
+ void updateSurface(Surface surface) throws OutOfResourcesException {
+ post("updateSurface", surface);
+ }
+
+ @Override
+ void destroyLayers(View view) {
+ throw new NoSuchMethodError();
+ }
+
+ @Override
+ void destroyHardwareResources(View view) {
+ run("destroyHardwareResources", view);
+ }
+
+ @Override
+ void invalidate(Surface surface) {
+ post("invalidate", surface);
+ }
+
+ @Override
+ boolean validate() {
+ // TODO Remove users of this API
+ return false;
+ }
+
+ @Override
+ boolean safelyRun(Runnable action) {
+ return (Boolean) run("safelyRun", action);
+ }
+
+ @Override
+ void setup(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ post("setup", width, height);
+ }
+
+ @Override
+ int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ int getHeight() {
+ return mHeight;
+ }
+
+ @Override
+ void dumpGfxInfo(PrintWriter pw) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ long getFrameCount() {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ boolean loadSystemProperties() {
+ return (Boolean) run("loadSystemProperties");
+ }
+
+ @Override
+ void pushLayerUpdate(HardwareLayer layer) {
+ throw new NoSuchMethodError();
+ }
+
+ @Override
+ void cancelLayerUpdate(HardwareLayer layer) {
+ throw new NoSuchMethodError();
+ }
+
+ @Override
+ void flushLayerUpdates() {
+ throw new NoSuchMethodError();
+ }
+
+ @Override
+ void drawDisplayList(DisplayList displayList, AttachInfo attachInfo,
+ HardwareDrawCallbacks callbacks, Rect dirty) {
+ throw new NoSuchMethodError();
+ }
+
+ /**
+ * TODO: Remove
+ * Temporary hack to allow RenderThreadTest prototype app to trigger
+ * replaying a DisplayList after modifying the displaylist properties
+ *
+ * @hide */
+ public void repeatLastDraw() {
+ if (mPreviousDraw == null) {
+ throw new IllegalStateException("There isn't a previous draw");
+ }
+ synchronized (mPreviousDraw) {
+ mPreviousDraw.completed = false;
+ }
+ mPreviousDraw.args[3] = null;
+ postToRenderThread(mPreviousDraw);
+ }
+
+ @Override
+ void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) {
+ requireCompletion(mPreviousDraw);
+
+ attachInfo.mIgnoreDirtyState = true;
+ attachInfo.mDrawingTime = SystemClock.uptimeMillis();
+ view.mPrivateFlags |= View.PFLAG_DRAWN;
+
+ view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
+ == View.PFLAG_INVALIDATED;
+ view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
+ DisplayList displayList = view.getDisplayList();
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+
+ view.mRecreateDisplayList = false;
+
+ mPreviousDraw = post("drawDisplayList", displayList, attachInfo,
+ callbacks, dirty);
+ }
+
+ @Override
+ HardwareLayer createHardwareLayer(boolean isOpaque) {
+ throw new NoSuchMethodError();
+ }
+
+ @Override
+ HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
+ throw new NoSuchMethodError();
+ }
+
+ @Override
+ SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
+ throw new NoSuchMethodError();
+ }
+
+ @Override
+ void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture) {
+ throw new NoSuchMethodError();
+ }
+
+ @Override
+ void detachFunctor(int functor) {
+ throw new NoSuchMethodError();
+ }
+
+ @Override
+ boolean attachFunctor(AttachInfo attachInfo, int functor) {
+ throw new NoSuchMethodError();
+ }
+
+ @Override
+ void setName(String name) {
+ post("setName", name);
+ }
+
+ private static void requireCompletion(RTJob job) {
+ if (job != null) {
+ synchronized (job) {
+ if (!job.completed) {
+ try {
+ job.wait();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+
+ private RTJob post(String method, Object... args) {
+ RTJob job = new RTJob();
+ job.method = sMethodLut.get(method);
+ job.args = args;
+ job.target = mRemoteRenderer;
+ if (job.method == null) {
+ throw new NullPointerException("Couldn't find method: " + method);
+ }
+ postToRenderThread(job);
+ return job;
+ }
+
+ private Object run(String method, Object... args) {
+ RTJob job = new RTJob();
+ job.method = sMethodLut.get(method);
+ job.args = args;
+ job.target = mRemoteRenderer;
+ if (job.method == null) {
+ throw new NullPointerException("Couldn't find method: " + method);
+ }
+ synchronized (job) {
+ postToRenderThread(job);
+ try {
+ job.wait();
+ return job.ret;
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ static class RTJob implements Runnable {
+ Method method;
+ Object[] args;
+ Object target;
+ Object ret;
+ boolean completed = false;
+
+ @Override
+ public void run() {
+ try {
+ ret = method.invoke(target, args);
+ synchronized (this) {
+ completed = true;
+ notify();
+ }
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Failed to invoke: " + method.getName(), e);
+ }
+ }
+ }
+
+ /** @hide */
+ public static native void postToRenderThread(Runnable runnable);
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5d264b6..05366a7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11209,6 +11209,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * @hide
+ */
+ public HardwareRenderer getHardwareRenderer() {
+ return mAttachInfo != null ? mAttachInfo.mHardwareRenderer : null;
+ }
+
+ /**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
@@ -18843,8 +18850,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final Callbacks mRootCallbacks;
- HardwareCanvas mHardwareCanvas;
-
IWindowId mIWindowId;
WindowId mWindowId;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ff74f9d..cbaf921 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -266,10 +266,10 @@ public final class ViewRootImpl implements ViewParent,
int mScrollY;
int mCurScrollY;
Scroller mScroller;
- HardwareLayer mResizeBuffer;
- long mResizeBufferStartTime;
- int mResizeBufferDuration;
- static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
+// HardwareLayer mResizeBuffer;
+// long mResizeBufferStartTime;
+// int mResizeBufferDuration;
+// static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
private ArrayList<LayoutTransition> mPendingTransitions;
final ViewConfiguration mViewConfiguration;
@@ -933,17 +933,17 @@ public final class ViewRootImpl implements ViewParent,
return mAppVisible ? mView.getVisibility() : View.GONE;
}
- void disposeResizeBuffer() {
- if (mResizeBuffer != null && mAttachInfo.mHardwareRenderer != null) {
- mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() {
- @Override
- public void run() {
- mResizeBuffer.destroy();
- mResizeBuffer = null;
- }
- });
- }
- }
+// void disposeResizeBuffer() {
+// if (mResizeBuffer != null && mAttachInfo.mHardwareRenderer != null) {
+// mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() {
+// @Override
+// public void run() {
+// mResizeBuffer.destroy();
+// mResizeBuffer = null;
+// }
+// });
+// }
+// }
/**
* Add LayoutTransition to the list of transitions to be started in the next traversal.
@@ -1454,75 +1454,76 @@ public final class ViewRootImpl implements ViewParent,
final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
mAttachInfo.mVisibleInsets);
if (contentInsetsChanged) {
- if (mWidth > 0 && mHeight > 0 && lp != null &&
- ((lp.systemUiVisibility|lp.subtreeSystemUiVisibility)
- & View.SYSTEM_UI_LAYOUT_FLAGS) == 0 &&
- mSurface != null && mSurface.isValid() &&
- !mAttachInfo.mTurnOffWindowResizeAnim &&
- mAttachInfo.mHardwareRenderer != null &&
- mAttachInfo.mHardwareRenderer.isEnabled() &&
- mAttachInfo.mHardwareRenderer.validate() &&
- lp != null && !PixelFormat.formatHasAlpha(lp.format)) {
-
- disposeResizeBuffer();
-
- boolean completed = false;
- HardwareCanvas hwRendererCanvas = mAttachInfo.mHardwareRenderer.getCanvas();
- HardwareCanvas layerCanvas = null;
- try {
- if (mResizeBuffer == null) {
- mResizeBuffer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
- mWidth, mHeight, false);
- } else if (mResizeBuffer.getWidth() != mWidth ||
- mResizeBuffer.getHeight() != mHeight) {
- mResizeBuffer.resize(mWidth, mHeight);
- }
- // TODO: should handle create/resize failure
- layerCanvas = mResizeBuffer.start(hwRendererCanvas);
- final int restoreCount = layerCanvas.save();
-
- int yoff;
- final boolean scrolling = mScroller != null
- && mScroller.computeScrollOffset();
- if (scrolling) {
- yoff = mScroller.getCurrY();
- mScroller.abortAnimation();
- } else {
- yoff = mScrollY;
- }
-
- layerCanvas.translate(0, -yoff);
- if (mTranslator != null) {
- mTranslator.translateCanvas(layerCanvas);
- }
-
- DisplayList displayList = mView.mDisplayList;
- if (displayList != null && displayList.isValid()) {
- layerCanvas.drawDisplayList(displayList, null,
- DisplayList.FLAG_CLIP_CHILDREN);
- } else {
- mView.draw(layerCanvas);
- }
-
- drawAccessibilityFocusedDrawableIfNeeded(layerCanvas);
-
- mResizeBufferStartTime = SystemClock.uptimeMillis();
- mResizeBufferDuration = mView.getResources().getInteger(
- com.android.internal.R.integer.config_mediumAnimTime);
- completed = true;
-
- layerCanvas.restoreToCount(restoreCount);
- } catch (OutOfMemoryError e) {
- Log.w(TAG, "Not enough memory for content change anim buffer", e);
- } finally {
- if (mResizeBuffer != null) {
- mResizeBuffer.end(hwRendererCanvas);
- if (!completed) {
- disposeResizeBuffer();
- }
- }
- }
- }
+// TODO: Do something with this...
+// if (mWidth > 0 && mHeight > 0 && lp != null &&
+// ((lp.systemUiVisibility|lp.subtreeSystemUiVisibility)
+// & View.SYSTEM_UI_LAYOUT_FLAGS) == 0 &&
+// mSurface != null && mSurface.isValid() &&
+// !mAttachInfo.mTurnOffWindowResizeAnim &&
+// mAttachInfo.mHardwareRenderer != null &&
+// mAttachInfo.mHardwareRenderer.isEnabled() &&
+// mAttachInfo.mHardwareRenderer.validate() &&
+// lp != null && !PixelFormat.formatHasAlpha(lp.format)) {
+//
+// disposeResizeBuffer();
+//
+// boolean completed = false;
+// HardwareCanvas hwRendererCanvas = mAttachInfo.mHardwareRenderer.getCanvas();
+// HardwareCanvas layerCanvas = null;
+// try {
+// if (mResizeBuffer == null) {
+// mResizeBuffer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
+// mWidth, mHeight, false);
+// } else if (mResizeBuffer.getWidth() != mWidth ||
+// mResizeBuffer.getHeight() != mHeight) {
+// mResizeBuffer.resize(mWidth, mHeight);
+// }
+// // TODO: should handle create/resize failure
+// layerCanvas = mResizeBuffer.start(hwRendererCanvas);
+// final int restoreCount = layerCanvas.save();
+//
+// int yoff;
+// final boolean scrolling = mScroller != null
+// && mScroller.computeScrollOffset();
+// if (scrolling) {
+// yoff = mScroller.getCurrY();
+// mScroller.abortAnimation();
+// } else {
+// yoff = mScrollY;
+// }
+//
+// layerCanvas.translate(0, -yoff);
+// if (mTranslator != null) {
+// mTranslator.translateCanvas(layerCanvas);
+// }
+//
+// DisplayList displayList = mView.mDisplayList;
+// if (displayList != null && displayList.isValid()) {
+// layerCanvas.drawDisplayList(displayList, null,
+// DisplayList.FLAG_CLIP_CHILDREN);
+// } else {
+// mView.draw(layerCanvas);
+// }
+//
+// drawAccessibilityFocusedDrawableIfNeeded(layerCanvas);
+//
+// mResizeBufferStartTime = SystemClock.uptimeMillis();
+// mResizeBufferDuration = mView.getResources().getInteger(
+// com.android.internal.R.integer.config_mediumAnimTime);
+// completed = true;
+//
+// layerCanvas.restoreToCount(restoreCount);
+// } catch (OutOfMemoryError e) {
+// Log.w(TAG, "Not enough memory for content change anim buffer", e);
+// } finally {
+// if (mResizeBuffer != null) {
+// mResizeBuffer.end(hwRendererCanvas);
+// if (!completed) {
+// disposeResizeBuffer();
+// }
+// }
+// }
+// }
mAttachInfo.mContentInsets.set(mPendingContentInsets);
if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
+ mAttachInfo.mContentInsets);
@@ -1582,7 +1583,7 @@ public final class ViewRootImpl implements ViewParent,
if (mScroller != null) {
mScroller.abortAnimation();
}
- disposeResizeBuffer();
+// disposeResizeBuffer();
// Our surface is gone
if (mAttachInfo.mHardwareRenderer != null &&
mAttachInfo.mHardwareRenderer.isEnabled()) {
@@ -2181,23 +2182,28 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void onHardwarePostDraw(HardwareCanvas canvas) {
- if (mResizeBuffer != null) {
- mResizePaint.setAlpha(mResizeAlpha);
- canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
+// if (mResizeBuffer != null) {
+// mResizePaint.setAlpha(mResizeAlpha);
+// canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
+// }
+ // TODO: this
+ if (!HardwareRenderer.sUseRenderThread) {
+ drawAccessibilityFocusedDrawableIfNeeded(canvas);
}
- drawAccessibilityFocusedDrawableIfNeeded(canvas);
}
/**
* @hide
*/
void outputDisplayList(View view) {
- if (mAttachInfo != null && mAttachInfo.mHardwareCanvas != null) {
- DisplayList displayList = view.getDisplayList();
- if (displayList != null) {
- mAttachInfo.mHardwareCanvas.outputDisplayList(displayList);
- }
- }
+ // TODO - route through HardwareCanvas so it can be
+ // proxied to the correct thread
+// if (mAttachInfo != null && mAttachInfo.mHardwareCanvas != null) {
+// DisplayList displayList = view.getDisplayList();
+// if (displayList != null) {
+// mAttachInfo.mHardwareCanvas.outputDisplayList(displayList);
+// }
+// }
}
/**
@@ -2342,17 +2348,17 @@ public final class ViewRootImpl implements ViewParent,
final boolean scalingRequired = attachInfo.mScalingRequired;
int resizeAlpha = 0;
- if (mResizeBuffer != null) {
- long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;
- if (deltaTime < mResizeBufferDuration) {
- float amt = deltaTime/(float) mResizeBufferDuration;
- amt = mResizeInterpolator.getInterpolation(amt);
- animating = true;
- resizeAlpha = 255 - (int)(amt*255);
- } else {
- disposeResizeBuffer();
- }
- }
+// if (mResizeBuffer != null) {
+// long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;
+// if (deltaTime < mResizeBufferDuration) {
+// float amt = deltaTime/(float) mResizeBufferDuration;
+// amt = mResizeInterpolator.getInterpolation(amt);
+// animating = true;
+// resizeAlpha = 255 - (int)(amt*255);
+// } else {
+// disposeResizeBuffer();
+// }
+// }
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
@@ -2362,7 +2368,7 @@ public final class ViewRootImpl implements ViewParent,
if (mScroller != null) {
mScroller.abortAnimation();
}
- disposeResizeBuffer();
+// disposeResizeBuffer();
}
return;
}
@@ -2725,7 +2731,7 @@ public final class ViewRootImpl implements ViewParent,
if (scrollY != mScrollY) {
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old="
+ mScrollY + " , new=" + scrollY);
- if (!immediate && mResizeBuffer == null) {
+ if (!immediate /*&& mResizeBuffer == null*/) {
if (mScroller == null) {
mScroller = new Scroller(mView.getContext());
}
@@ -5404,7 +5410,7 @@ public final class ViewRootImpl implements ViewParent,
// Hardware rendering
if (mAttachInfo.mHardwareRenderer != null) {
- if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mSurface)) {
+ if (mAttachInfo.mHardwareRenderer.loadSystemProperties()) {
invalidate();
}
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ca519d1..a558909 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -55,6 +55,7 @@ LOCAL_SRC_FILES:= \
android_view_GLRenderer.cpp \
android_view_GLES20DisplayList.cpp \
android_view_GLES20Canvas.cpp \
+ android_view_ThreadedRenderer.cpp \
android_view_MotionEvent.cpp \
android_view_PointerIcon.cpp \
android_view_VelocityTracker.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 783e794..b20dc09 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -123,6 +123,7 @@ extern int register_android_view_GraphicBuffer(JNIEnv* env);
extern int register_android_view_GLES20DisplayList(JNIEnv* env);
extern int register_android_view_GLES20Canvas(JNIEnv* env);
extern int register_android_view_GLRenderer(JNIEnv* env);
+extern int register_android_view_ThreadedRenderer(JNIEnv* env);
extern int register_android_view_Surface(JNIEnv* env);
extern int register_android_view_SurfaceControl(JNIEnv* env);
extern int register_android_view_SurfaceSession(JNIEnv* env);
@@ -1127,6 +1128,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_view_GLES20DisplayList),
REG_JNI(register_android_view_GLES20Canvas),
REG_JNI(register_android_view_GLRenderer),
+ REG_JNI(register_android_view_ThreadedRenderer),
REG_JNI(register_android_view_Surface),
REG_JNI(register_android_view_SurfaceControl),
REG_JNI(register_android_view_SurfaceSession),
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
new file mode 100644
index 0000000..8bb6cb4
--- /dev/null
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2010 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 "GLRenderer"
+
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <renderthread/RenderTask.h>
+#include <renderthread/RenderThread.h>
+
+namespace android {
+
+#ifdef USE_OPENGL_RENDERER
+
+namespace RT = android::uirenderer::renderthread;
+
+static jmethodID gRunnableMethod;
+
+class JavaTask : public RT::RenderTask {
+public:
+ JavaTask(JNIEnv* env, jobject jrunnable) {
+ env->GetJavaVM(&mVm);
+ mRunnable = env->NewGlobalRef(jrunnable);
+ }
+
+ virtual ~JavaTask() {
+ env()->DeleteGlobalRef(mRunnable);
+ }
+
+ virtual void run() {
+ env()->CallVoidMethod(mRunnable, gRunnableMethod);
+ };
+
+private:
+ JNIEnv* env() {
+ JNIEnv* env;
+ if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ return 0;
+ }
+ return env;
+ }
+
+ JavaVM* mVm;
+ jobject mRunnable;
+};
+
+static void android_view_ThreadedRenderer_postToRenderThread(JNIEnv* env, jobject clazz,
+ jobject jrunnable) {
+ RT::RenderTask* task = new JavaTask(env, jrunnable);
+ RT::RenderThread::getInstance().queue(task);
+}
+
+#endif
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/view/ThreadedRenderer";
+
+static JNINativeMethod gMethods[] = {
+#ifdef USE_OPENGL_RENDERER
+ { "postToRenderThread", "(Ljava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_postToRenderThread },
+#endif
+};
+
+int register_android_view_ThreadedRenderer(JNIEnv* env) {
+#ifdef USE_OPENGL_RENDERER
+ jclass cls = env->FindClass("java/lang/Runnable");
+ gRunnableMethod = env->GetMethodID(cls, "run", "()V");
+ env->DeleteLocalRef(cls);
+#endif
+ return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index ce8364e..1f37925 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -45,6 +45,11 @@ ifeq ($(USE_OPENGL_RENDERER),true)
TextureCache.cpp \
TextDropShadowCache.cpp
+ # RenderThread stuff
+ LOCAL_SRC_FILES += \
+ renderthread/RenderTask.cpp \
+ renderthread/RenderThread.cpp
+
intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
LOCAL_C_INCLUDES += \
diff --git a/libs/hwui/renderthread/RenderTask.cpp b/libs/hwui/renderthread/RenderTask.cpp
new file mode 100644
index 0000000..2da91c5
--- /dev/null
+++ b/libs/hwui/renderthread/RenderTask.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 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 "RenderTask"
+
+#include "RenderTask.h"
+
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+RenderTask::RenderTask() : mNext(0) {
+}
+
+RenderTask::~RenderTask() {
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderTask.h b/libs/hwui/renderthread/RenderTask.h
new file mode 100644
index 0000000..865b1e6
--- /dev/null
+++ b/libs/hwui/renderthread/RenderTask.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 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 RENDERTASK_H_
+#define RENDERTASK_H_
+
+#include <cutils/compiler.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+class ANDROID_API RenderTask {
+public:
+ ANDROID_API RenderTask();
+ ANDROID_API virtual ~RenderTask();
+
+ ANDROID_API virtual void run() = 0;
+
+ RenderTask* mNext;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+#endif /* RENDERTASK_H_ */
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
new file mode 100644
index 0000000..18d9300
--- /dev/null
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 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 "RenderThread"
+
+#include "RenderThread.h"
+
+#include <utils/Log.h>
+
+namespace android {
+using namespace uirenderer::renderthread;
+ANDROID_SINGLETON_STATIC_INSTANCE(RenderThread);
+
+namespace uirenderer {
+namespace renderthread {
+
+RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
+ , mQueueHead(0), mQueueTail(0) {
+ mLooper = new Looper(false);
+ run("RenderThread");
+}
+
+RenderThread::~RenderThread() {
+}
+
+bool RenderThread::threadLoop() {
+ for (;;) {
+ int result = mLooper->pollAll(-1);
+ if (result == ALOOPER_POLL_ERROR) {
+ // TODO Something?
+ break;
+ }
+ // Process our queue, if we have anything
+ while (RenderTask* task = nextTask()) {
+ task->run();
+ delete task;
+ }
+ }
+
+ return false;
+}
+
+void RenderThread::queue(RenderTask* task) {
+ AutoMutex _lock(mLock);
+ if (mQueueTail) {
+ mQueueTail->mNext = task;
+ } else {
+ mQueueHead = task;
+ }
+ mQueueTail = task;
+ if (mQueueHead == task) {
+ // Only wake if this is the first task
+ mLooper->wake();
+ }
+}
+
+RenderTask* RenderThread::nextTask() {
+ AutoMutex _lock(mLock);
+ RenderTask* ret = mQueueHead;
+ if (ret) {
+ if (mQueueTail == mQueueHead) {
+ mQueueTail = mQueueHead = 0;
+ } else {
+ mQueueHead = ret->mNext;
+ }
+ ret->mNext = 0;
+ }
+ return ret;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
new file mode 100644
index 0000000..4edd575
--- /dev/null
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 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 RENDERTHREAD_H_
+#define RENDERTHREAD_H_
+
+#include "RenderTask.h"
+#include <cutils/compiler.h>
+#include <utils/Looper.h>
+#include <utils/Mutex.h>
+#include <utils/Singleton.h>
+#include <utils/Thread.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+class ANDROID_API RenderThread : public Thread, public Singleton<RenderThread> {
+public:
+ // RenderThread takes complete ownership of tasks that are queued
+ // and will delete them after they are run
+ ANDROID_API void queue(RenderTask* task);
+
+protected:
+ virtual bool threadLoop();
+
+private:
+ friend class Singleton<RenderThread>;
+
+ RenderThread();
+ virtual ~RenderThread();
+
+ RenderTask* nextTask();
+
+ sp<Looper> mLooper;
+ Mutex mLock;
+
+ RenderTask* mQueueHead;
+ RenderTask* mQueueTail;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+#endif /* RENDERTHREAD_H_ */
diff --git a/tests/RenderThreadTest/Android.mk b/tests/RenderThreadTest/Android.mk
new file mode 100644
index 0000000..bdcba2e
--- /dev/null
+++ b/tests/RenderThreadTest/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := RenderThreadTest
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-common
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/RenderThreadTest/AndroidManifest.xml b/tests/RenderThreadTest/AndroidManifest.xml
new file mode 100644
index 0000000..c76cfce
--- /dev/null
+++ b/tests/RenderThreadTest/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.renderthread"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk
+ android:minSdkVersion="18"
+ android:targetSdkVersion="18" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/AppTheme" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".SubActivity"
+ android:theme="@style/AppTheme.Transparent" />
+ </application>
+
+</manifest>
diff --git a/tests/RenderThreadTest/res/drawable-hdpi/ic_launcher.png b/tests/RenderThreadTest/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/tests/RenderThreadTest/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/RenderThreadTest/res/drawable-mdpi/ic_launcher.png b/tests/RenderThreadTest/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/tests/RenderThreadTest/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/RenderThreadTest/res/drawable-xhdpi/ic_launcher.png b/tests/RenderThreadTest/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/tests/RenderThreadTest/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg b/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg
new file mode 100644
index 0000000..755232d
--- /dev/null
+++ b/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg
Binary files differ
diff --git a/tests/RenderThreadTest/res/layout/activity_main.xml b/tests/RenderThreadTest/res/layout/activity_main.xml
new file mode 100644
index 0000000..1fd5459
--- /dev/null
+++ b/tests/RenderThreadTest/res/layout/activity_main.xml
@@ -0,0 +1,12 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity" >
+
+ <ListView android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:divider="@null" />
+
+</FrameLayout>
diff --git a/tests/RenderThreadTest/res/layout/activity_sub.xml b/tests/RenderThreadTest/res/layout/activity_sub.xml
new file mode 100644
index 0000000..713cee4
--- /dev/null
+++ b/tests/RenderThreadTest/res/layout/activity_sub.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <View
+ android:id="@+id/bg_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/starry_night_bg" />
+
+ <LinearLayout
+ android:id="@+id/my_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+
+ <View
+ android:id="@+id/from_left"
+ android:layout_width="match_parent"
+ android:layout_height="48dip"
+ android:background="#7000FF00" />
+
+ <View
+ android:id="@+id/from_right"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_margin="80dip"
+ android:layout_weight="1"
+ android:background="#90FF0000" />
+
+ <View
+ android:id="@+id/from_left"
+ android:layout_width="match_parent"
+ android:layout_height="48dip"
+ android:background="#7000FF00" />
+ </LinearLayout>
+
+</FrameLayout> \ No newline at end of file
diff --git a/tests/RenderThreadTest/res/layout/item_layout.xml b/tests/RenderThreadTest/res/layout/item_layout.xml
new file mode 100644
index 0000000..5bdb1ac
--- /dev/null
+++ b/tests/RenderThreadTest/res/layout/item_layout.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceListItemSmall"
+ android:gravity="center_vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:background="#33b5e5"
+/> \ No newline at end of file
diff --git a/tests/RenderThreadTest/res/values/strings.xml b/tests/RenderThreadTest/res/values/strings.xml
new file mode 100644
index 0000000..f782e98
--- /dev/null
+++ b/tests/RenderThreadTest/res/values/strings.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">Render Thread</string>
+ <string name="action_settings">Settings</string>
+ <string name="hello_world">Hello world!</string>
+
+</resources>
diff --git a/tests/RenderThreadTest/res/values/styles.xml b/tests/RenderThreadTest/res/values/styles.xml
new file mode 100644
index 0000000..f6b5d6a
--- /dev/null
+++ b/tests/RenderThreadTest/res/values/styles.xml
@@ -0,0 +1,11 @@
+<resources>
+ <!-- Application theme. -->
+ <style name="AppTheme" parent="android:Theme.Holo.Light">
+ </style>
+
+ <style name="AppTheme.Transparent">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ </style>
+
+</resources>
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
new file mode 100644
index 0000000..a39aba8
--- /dev/null
+++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
@@ -0,0 +1,158 @@
+
+package com.example.renderthread;
+
+import android.animation.TimeInterpolator;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.DisplayList;
+import android.view.HardwareRenderer;
+import android.view.ThreadedRenderer;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MainActivity extends Activity implements OnItemClickListener {
+
+ static final int DURATION = 400;
+
+ static final String KEY_NAME = "name";
+ static final String KEY_CLASS = "clazz";
+
+ static Map<String,?> make(String name) {
+ Map<String,Object> ret = new HashMap<String,Object>();
+ ret.put(KEY_NAME, name);
+ return ret;
+ }
+
+ @SuppressWarnings("serial")
+ static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{
+ for (int i = 1; i < 25; i++) {
+ add(make("List Item: " + i));
+ }
+ }};
+
+ Handler mHandler = new Handler();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ HardwareRenderer.sUseRenderThread = true;
+ setContentView(R.layout.activity_main);
+ ListView lv = (ListView) findViewById(android.R.id.list);
+ lv.setDrawSelectorOnTop(true);
+ lv.setAdapter(new SimpleAdapter(this, SAMPLES,
+ R.layout.item_layout, new String[] { KEY_NAME },
+ new int[] { android.R.id.text1 }));
+ lv.setOnItemClickListener(this);
+ getActionBar().setTitle("MainActivity");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ ListView lv = (ListView) findViewById(android.R.id.list);
+ for (int i = 0; i < lv.getChildCount(); i++) {
+ lv.getChildAt(i).animate().translationY(0).setDuration(DURATION);
+ }
+ }
+
+ private static class DisplayListAnimator {
+ private static final TimeInterpolator sDefaultInterpolator =
+ new AccelerateDecelerateInterpolator();
+
+ DisplayList mDisplayList;
+ float mFromValue;
+ float mDelta;
+ long mDuration = DURATION * 2;
+ long mStartTime;
+
+ DisplayListAnimator(View view, float translateXBy) {
+ mDelta = translateXBy;
+ mFromValue = view.getTranslationY();
+ mDisplayList = view.getDisplayList();
+ }
+
+ boolean animate(long currentTime) {
+ if (mStartTime == 0) mStartTime = currentTime;
+
+ float fraction = (float)(currentTime - mStartTime) / mDuration;
+ if (fraction > 1) {
+ return false;
+ }
+ fraction = sDefaultInterpolator.getInterpolation(fraction);
+ float translation = mFromValue + (mDelta * fraction);
+ mDisplayList.setTranslationY(translation);
+ return fraction < 1f;
+ }
+ }
+
+ private static class AnimationExecutor implements Runnable {
+ DisplayListAnimator[] mAnimations;
+ ThreadedRenderer mRenderer;
+
+ AnimationExecutor(ThreadedRenderer renderer, DisplayListAnimator[] animations) {
+ mRenderer = renderer;
+ mAnimations = animations;
+ ThreadedRenderer.postToRenderThread(this);
+ }
+
+ @Override
+ public void run() {
+ boolean hasMore = false;
+ long now = SystemClock.uptimeMillis();
+ for (DisplayListAnimator animator : mAnimations) {
+ hasMore |= animator.animate(now);
+ }
+ mRenderer.repeatLastDraw();
+ if (hasMore) {
+ ThreadedRenderer.postToRenderThread(this);
+ }
+ }
+
+ }
+
+ @Override
+ public void onItemClick(final AdapterView<?> adapterView, View clickedView,
+ int clickedPosition, long clickedId) {
+ int topPosition = adapterView.getFirstVisiblePosition();
+ int dy = adapterView.getHeight();
+ final DisplayListAnimator[] animators = new DisplayListAnimator[adapterView.getChildCount()];
+ for (int i = 0; i < adapterView.getChildCount(); i++) {
+ int pos = topPosition + i;
+ View child = adapterView.getChildAt(i);
+ float delta = (pos - clickedPosition) * 1.1f;
+ if (delta == 0) delta = -1;
+ animators[i] = new DisplayListAnimator(child, dy * delta);
+ }
+ adapterView.invalidate();
+ adapterView.post(new Runnable() {
+
+ @Override
+ public void run() {
+ new AnimationExecutor((ThreadedRenderer) adapterView.getHardwareRenderer(), animators);
+ }
+ });
+ //mHandler.postDelayed(mLaunchActivity, (long) (DURATION * .4));
+ mLaunchActivity.run();
+ }
+
+ private Runnable mLaunchActivity = new Runnable() {
+
+ @Override
+ public void run() {
+ startActivity(new Intent(MainActivity.this, SubActivity.class));
+ overridePendingTransition(0, 0);
+ }
+ };
+
+}
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/SubActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/SubActivity.java
new file mode 100644
index 0000000..892cbae
--- /dev/null
+++ b/tests/RenderThreadTest/src/com/example/renderthread/SubActivity.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 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.example.renderthread;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class SubActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ long before = SystemClock.currentThreadTimeMillis();
+ setContentView(R.layout.activity_sub);
+ getActionBar().setTitle("SubActivity");
+ // Simulate being a real app!
+ while (SystemClock.currentThreadTimeMillis() - before < 100) {
+ View v = new View(this, null);
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ ViewGroup container = (ViewGroup) findViewById(R.id.my_container);
+ int dx = getWindowManager().getDefaultDisplay().getWidth();
+ for (int i = 0; i < container.getChildCount(); i++) {
+ View child = container.getChildAt(i);
+ int dir = child.getId() == R.id.from_left ? 1 : -1;
+ child.setTranslationX(dx * dir);
+ child.animate().translationX(0).setDuration(MainActivity.DURATION);
+ }
+ View bg = findViewById(R.id.bg_container);
+ bg.setAlpha(0f);
+ bg.animate().alpha(1f).setDuration(MainActivity.DURATION);
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ overridePendingTransition(0, 0);
+ }
+}