Native-side proxy
Remove RemoteGLRenderer
Remove reflection-based control
Change-Id: If17c2bbb61c7141986d88c4763def77ed1074985
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index ffb8a32..a848c8f 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -19,14 +19,22 @@
#include "CanvasContext.h"
#include <cutils/properties.h>
+#include <private/hwui/DrawGlInfo.h>
#include <strings.h>
+#include "RenderThread.h"
#include "../Caches.h"
+#include "../OpenGLRenderer.h"
#include "../Stencil.h"
#define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions"
#define GLES_VERSION 2
+#ifdef USE_OPENGL_RENDERER
+// Android-specific addition that is used to show when frames began in systrace
+EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
+#endif
+
namespace android {
namespace uirenderer {
namespace renderthread {
@@ -69,19 +77,19 @@
public:
static GlobalContext* get();
- // Returns true if EGL was initialized,
- // false if it was already initialized
- bool initialize();
+ // Returns true on success, false on failure
+ void initialize();
- bool usePBufferSurface();
+ void usePBufferSurface();
EGLSurface createSurface(EGLNativeWindowType window);
void destroySurface(EGLSurface surface);
void destroy();
bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; }
- bool makeCurrent(EGLSurface surface);
- bool swapBuffers(EGLSurface surface);
+ void makeCurrent(EGLSurface surface);
+ void beginFrame(EGLSurface surface, EGLint* width, EGLint* height);
+ void swapBuffers(EGLSurface surface);
bool enableDirtyRegions(EGLSurface surface);
@@ -90,8 +98,9 @@
// GlobalContext is never destroyed, method is purposely not implemented
~GlobalContext();
- bool loadConfig();
- bool createContext();
+ void loadConfig();
+ void createContext();
+ void initAtlas();
static GlobalContext* sContext;
@@ -126,33 +135,27 @@
ALOGD("Render dirty regions requested: %s", mRequestDirtyRegions ? "true" : "false");
}
-bool GlobalContext::initialize() {
- if (mEglDisplay != EGL_NO_DISPLAY) return false;
+void GlobalContext::initialize() {
+ if (mEglDisplay != EGL_NO_DISPLAY) return;
mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (mEglDisplay == EGL_NO_DISPLAY) {
- ALOGE("Failed to get EGL_DEFAULT_DISPLAY! err=%s", egl_error_str());
- return false;
- }
+ LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
+ "Failed to get EGL_DEFAULT_DISPLAY! err=%s", egl_error_str());
EGLint major, minor;
- if (eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE) {
- ALOGE("Failed to initialize display %p! err=%s", mEglDisplay, egl_error_str());
- return false;
- }
+ LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE,
+ "Failed to initialize display %p! err=%s", mEglDisplay, egl_error_str());
+
ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor);
- if (!loadConfig()) {
- return false;
- }
- if (!createContext()) {
- return false;
- }
-
- return true;
+ loadConfig();
+ createContext();
+ usePBufferSurface();
+ Caches::getInstance().init();
+ initAtlas();
}
-bool GlobalContext::loadConfig() {
+void GlobalContext::loadConfig() {
EGLint swapBehavior = mCanSetDirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
EGLint attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
@@ -177,31 +180,32 @@
mCanSetDirtyRegions = false;
loadConfig();
} else {
- ALOGE("Failed to choose config, error = %s", egl_error_str());
- return false;
+ LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str());
}
}
- return true;
}
-bool GlobalContext::createContext() {
+void GlobalContext::createContext() {
EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, EGL_NONE };
mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs);
- if (mEglContext == EGL_NO_CONTEXT) {
- ALOGE("Failed to create context, error = %s", egl_error_str());
- return false;
- }
- return true;
+ LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT,
+ "Failed to create context, error = %s", egl_error_str());
}
-bool GlobalContext::usePBufferSurface() {
- if (mEglDisplay == EGL_NO_DISPLAY) return false;
+void GlobalContext::initAtlas() {
+ // TODO implement
+ // For now just run without an atlas
+}
+
+void GlobalContext::usePBufferSurface() {
+ LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
+ "usePBufferSurface() called on uninitialized GlobalContext!");
if (mPBufferSurface == EGL_NO_SURFACE) {
EGLint attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
mPBufferSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
}
- return makeCurrent(mPBufferSurface);
+ makeCurrent(mPBufferSurface);
}
EGLSurface GlobalContext::createSurface(EGLNativeWindowType window) {
@@ -238,8 +242,8 @@
mCurrentSurface = EGL_NO_SURFACE;
}
-bool GlobalContext::makeCurrent(EGLSurface surface) {
- if (isCurrent(surface)) return true;
+void GlobalContext::makeCurrent(EGLSurface surface) {
+ if (isCurrent(surface)) return;
if (surface == EGL_NO_SURFACE) {
// If we are setting EGL_NO_SURFACE we don't care about any of the potential
@@ -247,19 +251,31 @@
// destroyed in which case the current context is already NO_CONTEXT
eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
} else if (!eglMakeCurrent(mEglDisplay, surface, surface, mEglContext)) {
- ALOGE("Failed to make current on surface %p, error=%s", (void*)surface, egl_error_str());
- return false;
+ LOG_ALWAYS_FATAL("Failed to make current on surface %p, error=%s",
+ (void*)surface, egl_error_str());
}
mCurrentSurface = surface;
- return true;
}
-bool GlobalContext::swapBuffers(EGLSurface surface) {
- if (!eglSwapBuffers(mEglDisplay, surface)) {
- ALOGW("eglSwapBuffers failed on surface %p, error=%s", (void*)surface, egl_error_str());
- return false;
+void GlobalContext::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {
+ LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
+ "Tried to beginFrame on EGL_NO_SURFACE!");
+ makeCurrent(surface);
+ if (width) {
+ eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width);
}
- return true;
+ if (height) {
+ eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height);
+ }
+ eglBeginFrame(mEglDisplay, surface);
+}
+
+void GlobalContext::swapBuffers(EGLSurface surface) {
+ eglSwapBuffers(mEglDisplay, surface);
+ EGLint err = eglGetError();
+ // TODO: Check whether we need to special case EGL_CONTEXT_LOST
+ LOG_ALWAYS_FATAL_IF(err != EGL_SUCCESS,
+ "Encountered EGL error %d %s during rendering", err, egl_error_str(err));
}
bool GlobalContext::enableDirtyRegions(EGLSurface surface) {
@@ -283,17 +299,32 @@
return value == EGL_BUFFER_PRESERVED;
}
-CanvasContext::CanvasContext()
- : mEglSurface(EGL_NO_SURFACE)
- , mDirtyRegionsEnabled(false) {
+CanvasContext::CanvasContext(bool translucent)
+ : mRenderThread(RenderThread::getInstance())
+ , mEglSurface(EGL_NO_SURFACE)
+ , mDirtyRegionsEnabled(false)
+ , mOpaque(!translucent)
+ , mCanvas(0)
+ , mHaveNewSurface(false)
+ , mInvokeFunctorsPending(false)
+ , mInvokeFunctorsTask(this) {
mGlobalContext = GlobalContext::get();
}
CanvasContext::~CanvasContext() {
+ removeFunctorsTask();
+ destroyCanvas();
+}
+
+void CanvasContext::destroyCanvas() {
+ if (mCanvas) {
+ delete mCanvas;
+ mCanvas = 0;
+ }
setSurface(NULL);
}
-bool CanvasContext::setSurface(EGLNativeWindowType window) {
+void CanvasContext::setSurface(EGLNativeWindowType window) {
if (mEglSurface != EGL_NO_SURFACE) {
mGlobalContext->destroySurface(mEglSurface);
mEglSurface = EGL_NO_SURFACE;
@@ -301,24 +332,134 @@
if (window) {
mEglSurface = mGlobalContext->createSurface(window);
+ LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
+ "Failed to create EGLSurface for window %p, eglErr = %s",
+ (void*) window, egl_error_str());
}
if (mEglSurface != EGL_NO_SURFACE) {
mDirtyRegionsEnabled = mGlobalContext->enableDirtyRegions(mEglSurface);
+ mHaveNewSurface = true;
}
- return !window || mEglSurface != EGL_NO_SURFACE;
}
-bool CanvasContext::swapBuffers() {
- return mGlobalContext->swapBuffers(mEglSurface);
+void CanvasContext::swapBuffers() {
+ mGlobalContext->swapBuffers(mEglSurface);
+ mHaveNewSurface = false;
}
-bool CanvasContext::makeCurrent() {
- return mGlobalContext->makeCurrent(mEglSurface);
+void CanvasContext::makeCurrent() {
+ mGlobalContext->makeCurrent(mEglSurface);
}
-bool CanvasContext::useGlobalPBufferSurface() {
- return GlobalContext::get()->usePBufferSurface();
+bool CanvasContext::initialize(EGLNativeWindowType window) {
+ if (mCanvas) return false;
+ setSurface(window);
+ makeCurrent();
+ mCanvas = new OpenGLRenderer();
+ mCanvas->initProperties();
+ return true;
+}
+
+void CanvasContext::updateSurface(EGLNativeWindowType window) {
+ setSurface(window);
+ makeCurrent();
+}
+
+void CanvasContext::setup(int width, int height) {
+ if (!mCanvas) return;
+ mCanvas->setViewport(width, height);
+}
+
+void CanvasContext::drawDisplayList(DisplayList* displayList, Rect* dirty) {
+ LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
+ "drawDisplayList called on a context with no canvas or surface!");
+
+ EGLint width, height;
+ mGlobalContext->beginFrame(mEglSurface, &width, &height);
+ if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
+ mCanvas->setViewport(width, height);
+ dirty = NULL;
+ } else if (!mDirtyRegionsEnabled || mHaveNewSurface) {
+ dirty = NULL;
+ }
+
+ status_t status;
+ if (dirty) {
+ status = mCanvas->prepareDirty(dirty->left, dirty->top,
+ dirty->right, dirty->bottom, mOpaque);
+ } else {
+ status = mCanvas->prepare(mOpaque);
+ }
+
+ Rect outBounds;
+ status |= mCanvas->drawDisplayList(displayList, outBounds);
+ handleFunctorStatus(status, outBounds);
+
+ // TODO: Draw debug info
+ // TODO: Performance tracking
+
+ mCanvas->finish();
+
+ if (status & DrawGlInfo::kStatusDrew) {
+ swapBuffers();
+ }
+}
+
+void InvokeFunctorsTask::run() {
+ mContext->invokeFunctors();
+}
+
+void CanvasContext::attachFunctor(Functor* functor) {
+ if (!mCanvas) return;
+
+ mCanvas->attachFunctor(functor);
+ removeFunctorsTask();
+ queueFunctorsTask(0);
+}
+
+void CanvasContext::detachFunctor(Functor* functor) {
+ if (!mCanvas) return;
+
+ mCanvas->detachFunctor(functor);
+}
+
+void CanvasContext::invokeFunctors() {
+ mInvokeFunctorsPending = false;
+
+ if (!mCanvas) return;
+
+ makeCurrent();
+ Rect dirty;
+ int status = mCanvas->invokeFunctors(dirty);
+ handleFunctorStatus(status, dirty);
+}
+
+void CanvasContext::handleFunctorStatus(int status, const Rect& redrawClip) {
+ if (status & DrawGlInfo::kStatusDraw) {
+ // TODO: Invalidate the redrawClip
+ // Do we need to post to ViewRootImpl like the current renderer?
+ // Can we just enqueue ourselves to re-invoke the same display list?
+ // Something else entirely? Does ChromiumView still want this in a
+ // RenderThread world?
+ }
+
+ if (status & DrawGlInfo::kStatusInvoke) {
+ queueFunctorsTask();
+ }
+}
+
+void CanvasContext::removeFunctorsTask() {
+ if (!mInvokeFunctorsPending) return;
+
+ mRenderThread.remove(&mInvokeFunctorsTask);
+}
+
+void CanvasContext::queueFunctorsTask(int delayMs) {
+ if (mInvokeFunctorsPending) return;
+
+ mInvokeFunctorsPending = true;
+ mRenderThread.queueDelayed(&mInvokeFunctorsTask, delayMs);
}
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 77ae737..2daa905 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -19,31 +19,75 @@
#include <cutils/compiler.h>
#include <EGL/egl.h>
+#include <utils/Functor.h>
+
+#include "RenderTask.h"
+
+#define FUNCTOR_PROCESS_DELAY 4
namespace android {
namespace uirenderer {
+
+class DisplayList;
+class OpenGLRenderer;
+class Rect;
+
namespace renderthread {
class GlobalContext;
+class CanvasContext;
+class RenderThread;
+
+class InvokeFunctorsTask : public RenderTask {
+public:
+ InvokeFunctorsTask(CanvasContext* context)
+ : mContext(context) {}
+
+ virtual void run();
+
+private:
+ CanvasContext* mContext;
+};
// This per-renderer class manages the bridge between the global EGL context
// and the render surface.
class CanvasContext {
public:
- ANDROID_API CanvasContext();
- ANDROID_API ~CanvasContext();
+ CanvasContext(bool translucent);
+ ~CanvasContext();
- ANDROID_API bool setSurface(EGLNativeWindowType window);
- ANDROID_API bool swapBuffers();
- ANDROID_API bool makeCurrent();
+ bool initialize(EGLNativeWindowType window);
+ void updateSurface(EGLNativeWindowType window);
+ void setup(int width, int height);
+ void drawDisplayList(DisplayList* displayList, Rect* dirty);
+ void destroyCanvas();
- ANDROID_API static bool useGlobalPBufferSurface();
+ void attachFunctor(Functor* functor);
+ void detachFunctor(Functor* functor);
private:
+ void setSurface(EGLNativeWindowType window);
+ void swapBuffers();
+ void makeCurrent();
+
+ friend class InvokeFunctorsTask;
+ void invokeFunctors();
+ void handleFunctorStatus(int status, const Rect& redrawClip);
+ void removeFunctorsTask();
+ void queueFunctorsTask(int delayMs = FUNCTOR_PROCESS_DELAY);
GlobalContext* mGlobalContext;
+ RenderThread& mRenderThread;
EGLSurface mEglSurface;
bool mDirtyRegionsEnabled;
+
+ bool mOpaque;
+ OpenGLRenderer* mCanvas;
+ bool mHaveNewSurface;
+
+ bool mInvokeFunctorsPending;
+ InvokeFunctorsTask mInvokeFunctorsTask;
+
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
new file mode 100644
index 0000000..25badac
--- /dev/null
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -0,0 +1,194 @@
+/*
+ * 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 "RenderProxy"
+
+#include "RenderProxy.h"
+
+#include "CanvasContext.h"
+#include "RenderTask.h"
+#include "RenderThread.h"
+
+#include "../DisplayList.h"
+#include "../Rect.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+#define ARGS(method) method ## Args
+
+#define CREATE_BRIDGE1(name, a1) CREATE_BRIDGE(name, a1,,,,,,,)
+#define CREATE_BRIDGE2(name, a1, a2) CREATE_BRIDGE(name, a1,a2,,,,,,)
+#define CREATE_BRIDGE3(name, a1, a2, a3) CREATE_BRIDGE(name, a1,a2,a3,,,,,)
+#define CREATE_BRIDGE4(name, a1, a2, a3, a4) CREATE_BRIDGE(name, a1,a2,a3,a4,,,,)
+#define CREATE_BRIDGE(name, a1, a2, a3, a4, a5, a6, a7, a8) \
+ typedef struct { \
+ a1; a2; a3; a4; a5; a6; a7; a8; \
+ } ARGS(name); \
+ static void* Bridge_ ## name(ARGS(name)* args)
+
+#define SETUP_TASK(method) \
+ LOG_ALWAYS_FATAL_IF( METHOD_INVOKE_PAYLOAD_SIZE < sizeof(ARGS(method)), \
+ "METHOD_INVOKE_PAYLOAD_SIZE %d is smaller than sizeof(" #method "Args) %d", \
+ METHOD_INVOKE_PAYLOAD_SIZE, sizeof(ARGS(method))); \
+ MethodInvokeRenderTask* task = createTask((RunnableMethod) Bridge_ ## method); \
+ ARGS(method) *args = (ARGS(method) *) task->payload()
+
+CREATE_BRIDGE1(createContext, bool translucent) {
+ return new CanvasContext(args->translucent);
+}
+
+RenderProxy::RenderProxy(bool translucent)
+ : mRenderThread(RenderThread::getInstance())
+ , mContext(0) {
+ SETUP_TASK(createContext);
+ args->translucent = translucent;
+ mContext = (CanvasContext*) postAndWait(task);
+}
+
+RenderProxy::~RenderProxy() {
+ destroyContext();
+}
+
+CREATE_BRIDGE1(destroyContext, CanvasContext* context) {
+ delete args->context;
+ return NULL;
+}
+
+void RenderProxy::destroyContext() {
+ if (mContext) {
+ SETUP_TASK(destroyContext);
+ args->context = mContext;
+ mContext = 0;
+ post(task);
+ }
+}
+
+CREATE_BRIDGE2(initialize, CanvasContext* context, EGLNativeWindowType window) {
+ return (void*) args->context->initialize(args->window);
+}
+
+bool RenderProxy::initialize(EGLNativeWindowType window) {
+ SETUP_TASK(initialize);
+ args->context = mContext;
+ args->window = window;
+ return (bool) postAndWait(task);
+}
+
+CREATE_BRIDGE2(updateSurface, CanvasContext* context, EGLNativeWindowType window) {
+ args->context->updateSurface(args->window);
+ return NULL;
+}
+
+void RenderProxy::updateSurface(EGLNativeWindowType window) {
+ SETUP_TASK(updateSurface);
+ args->context = mContext;
+ args->window = window;
+ post(task);
+}
+
+CREATE_BRIDGE3(setup, CanvasContext* context, int width, int height) {
+ args->context->setup(args->width, args->height);
+ return NULL;
+}
+
+void RenderProxy::setup(int width, int height) {
+ SETUP_TASK(setup);
+ args->context = mContext;
+ args->width = width;
+ args->height = height;
+ post(task);
+}
+
+CREATE_BRIDGE3(drawDisplayList, CanvasContext* context, DisplayList* displayList,
+ Rect dirty) {
+ Rect* dirty = &args->dirty;
+ if (dirty->bottom == -1 && dirty->left == -1 &&
+ dirty->top == -1 && dirty->right == -1) {
+ dirty = 0;
+ }
+ args->context->drawDisplayList(args->displayList, dirty);
+ return NULL;
+}
+
+void RenderProxy::drawDisplayList(DisplayList* displayList,
+ int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom) {
+ SETUP_TASK(drawDisplayList);
+ args->context = mContext;
+ args->displayList = displayList;
+ args->dirty.set(dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
+ // TODO: Switch to post() once some form of thread safety strategy is in place
+ postAndWait(task);
+}
+
+CREATE_BRIDGE1(destroyCanvas, CanvasContext* context) {
+ args->context->destroyCanvas();
+ return NULL;
+}
+
+void RenderProxy::destroyCanvas() {
+ SETUP_TASK(destroyCanvas);
+ args->context = mContext;
+ post(task);
+}
+
+CREATE_BRIDGE2(attachFunctor, CanvasContext* context, Functor* functor) {
+ args->context->attachFunctor(args->functor);
+ return NULL;
+}
+
+void RenderProxy::attachFunctor(Functor* functor) {
+ SETUP_TASK(attachFunctor);
+ args->context = mContext;
+ args->functor = functor;
+ post(task);
+}
+
+CREATE_BRIDGE2(detachFunctor, CanvasContext* context, Functor* functor) {
+ args->context->detachFunctor(args->functor);
+ return NULL;
+}
+
+void RenderProxy::detachFunctor(Functor* functor) {
+ SETUP_TASK(detachFunctor);
+ args->context = mContext;
+ args->functor = functor;
+ post(task);
+}
+
+MethodInvokeRenderTask* RenderProxy::createTask(RunnableMethod method) {
+ // TODO: Consider having a small pool of these to avoid alloc churn
+ return new MethodInvokeRenderTask(method);
+}
+
+void RenderProxy::post(RenderTask* task) {
+ mRenderThread.queue(task);
+}
+
+void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) {
+ void* retval;
+ task->setReturnPtr(&retval);
+ SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
+ AutoMutex _lock(mSyncMutex);
+ mRenderThread.queue(&syncTask);
+ mSyncCondition.wait(mSyncMutex);
+ return retval;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
new file mode 100644
index 0000000..113c5a8
--- /dev/null
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -0,0 +1,85 @@
+/*
+ * 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 RENDERPROXY_H_
+#define RENDERPROXY_H_
+
+#include "RenderTask.h"
+
+#include <cutils/compiler.h>
+#include <EGL/egl.h>
+#include <utils/Condition.h>
+#include <utils/Functor.h>
+#include <utils/Mutex.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace uirenderer {
+
+class DisplayList;
+class Rect;
+
+namespace renderthread {
+
+class CanvasContext;
+class ErrorChannel;
+class RenderThread;
+class RenderProxyBridge;
+
+/*
+ * RenderProxy is strictly single threaded. All methods must be invoked on the owning
+ * thread. It is important to note that RenderProxy may be deleted while it has
+ * tasks post()'d as a result. Therefore any RenderTask that is post()'d must not
+ * reference RenderProxy or any of its fields. The exception here is that postAndWait()
+ * references RenderProxy fields. This is safe as RenderProxy cannot
+ * be deleted if it is blocked inside a call.
+ */
+class ANDROID_API RenderProxy {
+public:
+ ANDROID_API RenderProxy(bool translucent);
+ ANDROID_API virtual ~RenderProxy();
+
+ ANDROID_API bool initialize(EGLNativeWindowType window);
+ ANDROID_API void updateSurface(EGLNativeWindowType window);
+ ANDROID_API void setup(int width, int height);
+ ANDROID_API void drawDisplayList(DisplayList* displayList,
+ int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
+ ANDROID_API void destroyCanvas();
+
+ ANDROID_API void attachFunctor(Functor* functor);
+ ANDROID_API void detachFunctor(Functor* functor);
+
+private:
+ RenderThread& mRenderThread;
+ CanvasContext* mContext;
+
+ Mutex mSyncMutex;
+ Condition mSyncCondition;
+
+ void destroyContext();
+
+ MethodInvokeRenderTask* createTask(RunnableMethod method);
+ void post(RenderTask* task);
+ void* postAndWait(MethodInvokeRenderTask* task);
+
+ // Friend class to help with bridging
+ friend class RenderProxyBridge;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+#endif /* RENDERPROXY_H_ */
diff --git a/libs/hwui/renderthread/RenderTask.cpp b/libs/hwui/renderthread/RenderTask.cpp
index 2da91c5..7ca61e4 100644
--- a/libs/hwui/renderthread/RenderTask.cpp
+++ b/libs/hwui/renderthread/RenderTask.cpp
@@ -19,15 +19,18 @@
#include "RenderTask.h"
#include <utils/Log.h>
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
namespace android {
namespace uirenderer {
namespace renderthread {
-RenderTask::RenderTask() : mNext(0) {
-}
-
-RenderTask::~RenderTask() {
+void SignalingRenderTask::run() {
+ mTask->run();
+ mLock->lock();
+ mSignal->signal();
+ mLock->unlock();
}
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderTask.h b/libs/hwui/renderthread/RenderTask.h
index 865b1e6..9fe7573 100644
--- a/libs/hwui/renderthread/RenderTask.h
+++ b/libs/hwui/renderthread/RenderTask.h
@@ -18,19 +18,79 @@
#define RENDERTASK_H_
#include <cutils/compiler.h>
+#include <utils/Timers.h>
namespace android {
+class Mutex;
+class Condition;
namespace uirenderer {
namespace renderthread {
+#define METHOD_INVOKE_PAYLOAD_SIZE (8 * sizeof(void*))
+
+/*
+ * Notes about memory management
+ *
+ * RenderThread will only invoke RenderTask::run(). It is the responsibility
+ * of the RenderTask to know if it needs to suicide at the end of run() or
+ * if some other lifecycle is being used. As such, it is not valid to reference
+ * anything on RenderTask after the first call to run().
+ *
+ * For example SignalingRenderTask
+ * is expected to be stack allocated by the calling thread, so it does not
+ * suicide in run() but instead relies on the caller to destroy it.
+ *
+ * MethodInvokeRenderTask however is currently allocated with new, so it will
+ * suicide at the end of run(). TODO: Replace this with a small pool to avoid
+ * malloc/free churn of small objects?
+ */
+
class ANDROID_API RenderTask {
public:
- ANDROID_API RenderTask();
- ANDROID_API virtual ~RenderTask();
+ ANDROID_API RenderTask() : mNext(0), mRunAt(0) {}
+ ANDROID_API virtual ~RenderTask() {}
ANDROID_API virtual void run() = 0;
RenderTask* mNext;
+ nsecs_t mRunAt;
+};
+
+class SignalingRenderTask : public RenderTask {
+public:
+ // Takes ownership of task, caller owns lock and signal
+ SignalingRenderTask(RenderTask* task, Mutex* lock, Condition* signal)
+ : mTask(task), mLock(lock), mSignal(signal) {}
+ virtual void run();
+
+private:
+ RenderTask* mTask;
+ Mutex* mLock;
+ Condition* mSignal;
+};
+
+typedef void* (*RunnableMethod)(void* data);
+
+class MethodInvokeRenderTask : public RenderTask {
+public:
+ MethodInvokeRenderTask(RunnableMethod method)
+ : mMethod(method), mReturnPtr(0) {}
+
+ void* payload() { return mData; }
+ void setReturnPtr(void** retptr) { mReturnPtr = retptr; }
+
+ virtual void run() {
+ void* retval = mMethod(mData);
+ if (mReturnPtr) {
+ *mReturnPtr = retval;
+ }
+ // Commit suicide
+ delete this;
+ }
+private:
+ RunnableMethod mMethod;
+ char mData[METHOD_INVOKE_PAYLOAD_SIZE];
+ void** mReturnPtr;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index bccd6e6..e4ec164 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -18,6 +18,8 @@
#include "RenderThread.h"
+#include "CanvasContext.h"
+#include "RenderProxy.h"
#include <utils/Log.h>
namespace android {
@@ -27,8 +29,82 @@
namespace uirenderer {
namespace renderthread {
+TaskQueue::TaskQueue() : mHead(0), mTail(0) {}
+
+RenderTask* TaskQueue::next() {
+ RenderTask* ret = mHead;
+ if (ret) {
+ mHead = ret->mNext;
+ if (!mHead) {
+ mTail = 0;
+ }
+ ret->mNext = 0;
+ }
+ return ret;
+}
+
+RenderTask* TaskQueue::peek() {
+ return mHead;
+}
+
+void TaskQueue::queue(RenderTask* task) {
+ // Since the RenderTask itself forms the linked list it is not allowed
+ // to have the same task queued twice
+ LOG_ALWAYS_FATAL_IF(task->mNext || mTail == task, "Task is already in the queue!");
+ if (mTail) {
+ // Fast path if we can just append
+ if (mTail->mRunAt <= task->mRunAt) {
+ mTail->mNext = task;
+ mTail = task;
+ } else {
+ // Need to find the proper insertion point
+ RenderTask* previous = 0;
+ RenderTask* next = mHead;
+ while (next && next->mRunAt <= task->mRunAt) {
+ previous = next;
+ next = next->mNext;
+ }
+ if (!previous) {
+ task->mNext = mHead;
+ mHead = task;
+ } else {
+ previous->mNext = task;
+ if (next) {
+ task->mNext = next;
+ } else {
+ mTail = task;
+ }
+ }
+ }
+ } else {
+ mTail = mHead = task;
+ }
+}
+
+void TaskQueue::remove(RenderTask* task) {
+ // TaskQueue is strict here to enforce that users are keeping track of
+ // their RenderTasks due to how their memory is managed
+ LOG_ALWAYS_FATAL_IF(!task->mNext && mTail != task,
+ "Cannot remove a task that isn't in the queue!");
+
+ // If task is the head we can just call next() to pop it off
+ // Otherwise we need to scan through to find the task before it
+ if (peek() == task) {
+ next();
+ } else {
+ RenderTask* previous = mHead;
+ while (previous->mNext != task) {
+ previous = previous->mNext;
+ }
+ previous->mNext = task->mNext;
+ if (mTail == task) {
+ mTail = previous;
+ }
+ }
+}
+
RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
- , mQueueHead(0), mQueueTail(0) {
+ , mNextWakeup(LLONG_MAX) {
mLooper = new Looper(false);
run("RenderThread");
}
@@ -37,16 +113,25 @@
}
bool RenderThread::threadLoop() {
+ int timeoutMillis = -1;
for (;;) {
- int result = mLooper->pollAll(-1);
- if (result == Looper::POLL_ERROR) {
- // TODO Something?
- break;
- }
+ int result = mLooper->pollAll(timeoutMillis);
+ LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
+ "RenderThread Looper POLL_ERROR!");
+
+ nsecs_t nextWakeup;
// Process our queue, if we have anything
- while (RenderTask* task = nextTask()) {
+ while (RenderTask* task = nextTask(&nextWakeup)) {
task->run();
- delete task;
+ // task may have deleted itself, do not reference it again
+ }
+ if (nextWakeup == LLONG_MAX) {
+ timeoutMillis = -1;
+ } else {
+ timeoutMillis = nextWakeup - systemTime(SYSTEM_TIME_MONOTONIC);
+ if (timeoutMillis < 0) {
+ timeoutMillis = 0;
+ }
}
}
@@ -55,30 +140,40 @@
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
+ mQueue.queue(task);
+ if (mNextWakeup && task->mRunAt < mNextWakeup) {
+ mNextWakeup = 0;
mLooper->wake();
}
}
-RenderTask* RenderThread::nextTask() {
+void RenderThread::queueDelayed(RenderTask* task, int delayMs) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ task->mRunAt = now + delayMs;
+ queue(task);
+}
+
+void RenderThread::remove(RenderTask* task) {
AutoMutex _lock(mLock);
- RenderTask* ret = mQueueHead;
- if (ret) {
- if (mQueueTail == mQueueHead) {
- mQueueTail = mQueueHead = 0;
- } else {
- mQueueHead = ret->mNext;
+ mQueue.remove(task);
+}
+
+RenderTask* RenderThread::nextTask(nsecs_t* nextWakeup) {
+ AutoMutex _lock(mLock);
+ RenderTask* next = mQueue.peek();
+ if (!next) {
+ mNextWakeup = LLONG_MAX;
+ } else {
+ // Most tasks won't be delayed, so avoid unnecessary systemTime() calls
+ if (next->mRunAt <= 0 || next->mRunAt <= systemTime(SYSTEM_TIME_MONOTONIC)) {
+ next = mQueue.next();
}
- ret->mNext = 0;
+ mNextWakeup = next->mRunAt;
}
- return ret;
+ if (nextWakeup) {
+ *nextWakeup = mNextWakeup;
+ }
+ return next;
}
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 4edd575..e444aa0 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -28,11 +28,27 @@
namespace uirenderer {
namespace renderthread {
+class TaskQueue {
+public:
+ TaskQueue();
+
+ RenderTask* next();
+ void queue(RenderTask* task);
+ RenderTask* peek();
+ void remove(RenderTask* task);
+
+private:
+ RenderTask* mHead;
+ RenderTask* mTail;
+};
+
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);
+ void queueDelayed(RenderTask* task, int delayMs);
+ void remove(RenderTask* task);
protected:
virtual bool threadLoop();
@@ -43,13 +59,16 @@
RenderThread();
virtual ~RenderThread();
- RenderTask* nextTask();
+ // Returns the next task to be run. If this returns NULL nextWakeup is set
+ // to the time to requery for the nextTask to run. mNextWakeup is also
+ // set to this time
+ RenderTask* nextTask(nsecs_t* nextWakeup);
sp<Looper> mLooper;
Mutex mLock;
- RenderTask* mQueueHead;
- RenderTask* mQueueTail;
+ nsecs_t mNextWakeup;
+ TaskQueue mQueue;
};
} /* namespace renderthread */