#define LOG_TAG "ThreadedRenderer"
#include <algorithm>
+#include <atomic>
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include "core_jni_helpers.h"
#include <GraphicsJNI.h>
-#include <ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+
+#include <gui/BufferItemConsumer.h>
+#include <gui/BufferQueue.h>
+#include <gui/Surface.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
-#include <EGL/egl_cache.h>
+#include <private/EGL/cache.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
#include <utils/StrongPointer.h>
+#include <utils/Timers.h>
#include <android_runtime/android_view_Surface.h>
#include <system/window.h>
-#include "android_view_GraphicBuffer.h"
+#include "android_os_MessageQueue.h"
#include <Animator.h>
#include <AnimationContext.h>
+#include <FrameInfo.h>
+#include <FrameMetricsObserver.h>
#include <IContextFactory.h>
-#include <JankTracker.h>
+#include <PropertyValuesAnimatorSet.h>
#include <RenderNode.h>
#include <renderthread/CanvasContext.h>
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
#include <renderthread/RenderThread.h>
-#include <Vector.h>
namespace android {
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
+struct {
+ jfieldID frameMetrics;
+ jfieldID timingDataBuffer;
+ jfieldID messageQueue;
+ jmethodID callback;
+} gFrameMetricsObserverClassInfo;
+
static JNIEnv* getenv(JavaVM* vm) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
class InvokeAnimationListeners : public MessageHandler {
public:
- InvokeAnimationListeners(std::vector<OnFinishedEvent>& events) {
+ explicit InvokeAnimationListeners(std::vector<OnFinishedEvent>& events) {
mOnFinishedEvents.swap(events);
}
std::vector<OnFinishedEvent> mOnFinishedEvents;
};
+class FinishAndInvokeListener : public MessageHandler {
+public:
+ explicit FinishAndInvokeListener(PropertyValuesAnimatorSet* anim)
+ : mAnimator(anim) {
+ mListener = anim->getOneShotListener();
+ mRequestId = anim->getRequestId();
+ }
+
+ virtual void handleMessage(const Message& message) {
+ if (mAnimator->getRequestId() == mRequestId) {
+ // Request Id has not changed, meaning there's no animation lifecyle change since the
+ // message is posted, so go ahead and call finish to make sure the PlayState is properly
+ // updated. This is needed because before the next frame comes in from UI thread to
+ // trigger an animation update, there could be reverse/cancel etc. So we need to update
+ // the playstate in time to ensure all the subsequent events get chained properly.
+ mAnimator->end();
+ }
+ mListener->onAnimationFinished(nullptr);
+ }
+private:
+ sp<PropertyValuesAnimatorSet> mAnimator;
+ sp<AnimationListener> mListener;
+ uint32_t mRequestId;
+};
+
class RenderingException : public MessageHandler {
public:
RenderingException(JavaVM* vm, const std::string& message)
class RootRenderNode : public RenderNode, ErrorHandler {
public:
- RootRenderNode(JNIEnv* env) : RenderNode() {
+ explicit RootRenderNode(JNIEnv* env) : RenderNode() {
mLooper = Looper::getForThread();
LOG_ALWAYS_FATAL_IF(!mLooper.get(),
"Must create RootRenderNode on a thread with a looper!");
virtual ~RootRenderNode() {}
- virtual void onError(const std::string& message) {
+ virtual void onError(const std::string& message) override {
mLooper->sendMessage(new RenderingException(mVm, message), 0);
}
- virtual void prepareTree(TreeInfo& info) {
+ virtual void prepareTree(TreeInfo& info) override {
info.errorHandler = this;
+
+ for (auto& anim : mRunningVDAnimators) {
+ // Assume that the property change in VD from the animators will not be consumed. Mark
+ // otherwise if the VDs are found in the display list tree. For VDs that are not in
+ // the display list tree, we stop providing animation pulses by 1) removing them from
+ // the animation list, 2) post a delayed message to end them at end time so their
+ // listeners can receive the corresponding callbacks.
+ anim->getVectorDrawable()->setPropertyChangeWillBeConsumed(false);
+ // Mark the VD dirty so it will damage itself during prepareTree.
+ anim->getVectorDrawable()->markDirty();
+ }
+ if (info.mode == TreeInfo::MODE_FULL) {
+ for (auto &anim : mPausedVDAnimators) {
+ anim->getVectorDrawable()->setPropertyChangeWillBeConsumed(false);
+ anim->getVectorDrawable()->markDirty();
+ }
+ }
+ // TODO: This is hacky
+ info.updateWindowPositions = true;
RenderNode::prepareTree(info);
- info.errorHandler = NULL;
+ info.updateWindowPositions = false;
+ info.errorHandler = nullptr;
}
void sendMessage(const sp<MessageHandler>& handler) {
mLooper->sendMessage(handler, 0);
}
+ void sendMessageDelayed(const sp<MessageHandler>& handler, nsecs_t delayInMs) {
+ mLooper->sendMessageDelayed(ms2ns(delayInMs), handler, 0);
+ }
+
void attachAnimatingNode(RenderNode* animatingNode) {
mPendingAnimatingRenderNodes.push_back(animatingNode);
}
+ void attachPendingVectorDrawableAnimators() {
+ mRunningVDAnimators.insert(mPendingVectorDrawableAnimators.begin(),
+ mPendingVectorDrawableAnimators.end());
+ mPendingVectorDrawableAnimators.clear();
+ }
+
+ void detachAnimators() {
+ // Remove animators from the list and post a delayed message in future to end the animator
+ // For infinite animators, remove the listener so we no longer hold a global ref to the AVD
+ // java object, and therefore the AVD objects in both native and Java can be properly
+ // released.
+ for (auto& anim : mRunningVDAnimators) {
+ detachVectorDrawableAnimator(anim.get());
+ anim->clearOneShotListener();
+ }
+ for (auto& anim : mPausedVDAnimators) {
+ anim->clearOneShotListener();
+ }
+ mRunningVDAnimators.clear();
+ mPausedVDAnimators.clear();
+ }
+
+ // Move all the animators to the paused list, and send a delayed message to notify the finished
+ // listener.
+ void pauseAnimators() {
+ mPausedVDAnimators.insert(mRunningVDAnimators.begin(), mRunningVDAnimators.end());
+ for (auto& anim : mRunningVDAnimators) {
+ detachVectorDrawableAnimator(anim.get());
+ }
+ mRunningVDAnimators.clear();
+ }
+
void doAttachAnimatingNodes(AnimationContext* context) {
for (size_t i = 0; i < mPendingAnimatingRenderNodes.size(); i++) {
RenderNode* node = mPendingAnimatingRenderNodes[i].get();
mPendingAnimatingRenderNodes.clear();
}
+ // Run VectorDrawable animators after prepareTree.
+ void runVectorDrawableAnimators(AnimationContext* context, TreeInfo& info) {
+ // Push staging.
+ if (info.mode == TreeInfo::MODE_FULL) {
+ pushStagingVectorDrawableAnimators(context);
+ }
+
+ // Run the animators in the running list.
+ for (auto it = mRunningVDAnimators.begin(); it != mRunningVDAnimators.end();) {
+ if ((*it)->animate(*context)) {
+ it = mRunningVDAnimators.erase(it);
+ } else {
+ it++;
+ }
+ }
+
+ // Run the animators in paused list during full sync.
+ if (info.mode == TreeInfo::MODE_FULL) {
+ // During full sync we also need to pulse paused animators, in case their targets
+ // have been added back to the display list. All the animators that passed the
+ // scheduled finish time will be removed from the paused list.
+ for (auto it = mPausedVDAnimators.begin(); it != mPausedVDAnimators.end();) {
+ if ((*it)->animate(*context)) {
+ // Animator has finished, remove from the list.
+ it = mPausedVDAnimators.erase(it);
+ } else {
+ it++;
+ }
+ }
+ }
+
+ // Move the animators with a target not in DisplayList to paused list.
+ for (auto it = mRunningVDAnimators.begin(); it != mRunningVDAnimators.end();) {
+ if (!(*it)->getVectorDrawable()->getPropertyChangeWillBeConsumed()) {
+ // Vector Drawable is not in the display list, we should remove this animator from
+ // the list, put it in the paused list, and post a delayed message to end the
+ // animator.
+ detachVectorDrawableAnimator(it->get());
+ mPausedVDAnimators.insert(*it);
+ it = mRunningVDAnimators.erase(it);
+ } else {
+ it++;
+ }
+ }
+
+ // Move the animators with a target in DisplayList from paused list to running list, and
+ // trim paused list.
+ if (info.mode == TreeInfo::MODE_FULL) {
+ // Check whether any paused animator's target is back in Display List. If so, put the
+ // animator back in the running list.
+ for (auto it = mPausedVDAnimators.begin(); it != mPausedVDAnimators.end();) {
+ if ((*it)->getVectorDrawable()->getPropertyChangeWillBeConsumed()) {
+ mRunningVDAnimators.insert(*it);
+ it = mPausedVDAnimators.erase(it);
+ } else {
+ it++;
+ }
+ }
+ // Trim paused VD animators at full sync, so that when Java loses reference to an
+ // animator, we know we won't be requested to animate it any more, then we remove such
+ // animators from the paused list so they can be properly freed. We also remove the
+ // animators from paused list when the time elapsed since start has exceeded duration.
+ trimPausedVDAnimators(context);
+ }
+
+ info.out.hasAnimations |= !mRunningVDAnimators.empty();
+ }
+
+ void trimPausedVDAnimators(AnimationContext* context) {
+ // Trim paused vector drawable animator list.
+ for (auto it = mPausedVDAnimators.begin(); it != mPausedVDAnimators.end();) {
+ // Remove paused VD animator if no one else is referencing it. Note that animators that
+ // have passed scheduled finish time are removed from list when they are being pulsed
+ // before prepare tree.
+ // TODO: this is a bit hacky, need to figure out a better way to track when the paused
+ // animators should be freed.
+ if ((*it)->getStrongCount() == 1) {
+ it = mPausedVDAnimators.erase(it);
+ } else {
+ it++;
+ }
+ }
+ }
+
+ void pushStagingVectorDrawableAnimators(AnimationContext* context) {
+ for (auto& anim : mRunningVDAnimators) {
+ anim->pushStaging(*context);
+ }
+ }
+
+ void destroy() {
+ for (auto& renderNode : mPendingAnimatingRenderNodes) {
+ renderNode->animators().endAllStagingAnimators();
+ }
+ mPendingAnimatingRenderNodes.clear();
+ mPendingVectorDrawableAnimators.clear();
+ }
+
+ void addVectorDrawableAnimator(PropertyValuesAnimatorSet* anim) {
+ mPendingVectorDrawableAnimators.insert(anim);
+ }
+
private:
sp<Looper> mLooper;
JavaVM* mVm;
std::vector< sp<RenderNode> > mPendingAnimatingRenderNodes;
+ std::set< sp<PropertyValuesAnimatorSet> > mPendingVectorDrawableAnimators;
+ std::set< sp<PropertyValuesAnimatorSet> > mRunningVDAnimators;
+ // mPausedVDAnimators stores a list of animators that have not yet passed the finish time, but
+ // their VectorDrawable targets are no longer in the DisplayList. We skip these animators when
+ // render thread runs animators independent of UI thread (i.e. RT_ONLY mode). These animators
+ // need to be re-activated once their VD target is added back into DisplayList. Since that could
+ // only happen when we do a full sync, we need to make sure to pulse these paused animators at
+ // full sync. If any animator's VD target is found in DisplayList during a full sync, we move
+ // the animator back to the running list.
+ std::set< sp<PropertyValuesAnimatorSet> > mPausedVDAnimators;
+ void detachVectorDrawableAnimator(PropertyValuesAnimatorSet* anim) {
+ if (anim->isInfinite() || !anim->isRunning()) {
+ // Do not need to post anything if the animation is infinite (i.e. no meaningful
+ // end listener action), or if the animation has already ended.
+ return;
+ }
+ nsecs_t remainingTimeInMs = anim->getRemainingPlayTime();
+ // Post a delayed onFinished event that is scheduled to be handled when the animator ends.
+ if (anim->getOneShotListener()) {
+ // VectorDrawable's oneshot listener is updated when there are user triggered animation
+ // lifecycle changes, such as start(), end(), etc. By using checking and clearing
+ // one shot listener, we ensure the same end listener event gets posted only once.
+ // Therefore no duplicates. Another benefit of using one shot listener is that no
+ // removal is necessary: the end time of animation will not change unless triggered by
+ // user events, in which case the already posted listener's id will become stale, and
+ // the onFinished callback will then be ignored.
+ sp<FinishAndInvokeListener> message
+ = new FinishAndInvokeListener(anim);
+ sendMessageDelayed(message, remainingTimeInMs);
+ anim->clearOneShotListener();
+ }
+ }
};
class AnimationContextBridge : public AnimationContext {
virtual void startFrame(TreeInfo::TraversalMode mode) {
if (mode == TreeInfo::MODE_FULL) {
mRootNode->doAttachAnimatingNodes(this);
+ mRootNode->attachPendingVectorDrawableAnimators();
}
AnimationContext::startFrame(mode);
}
// Runs any animations still left in mCurrentFrameAnimations
virtual void runRemainingAnimations(TreeInfo& info) {
AnimationContext::runRemainingAnimations(info);
+ mRootNode->runVectorDrawableAnimators(this, info);
postOnFinishedEvents();
}
+ virtual void pauseAnimators() override {
+ mRootNode->pauseAnimators();
+ }
+
virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener) {
OnFinishedEvent event(animator, listener);
mOnFinishedEvents.push_back(event);
virtual void destroy() {
AnimationContext::destroy();
+ mRootNode->detachAnimators();
postOnFinishedEvents();
}
class ContextFactoryImpl : public IContextFactory {
public:
- ContextFactoryImpl(RootRenderNode* rootNode) : mRootNode(rootNode) {}
+ explicit ContextFactoryImpl(RootRenderNode* rootNode) : mRootNode(rootNode) {}
virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) {
return new AnimationContextBridge(clock, mRootNode);
RootRenderNode* mRootNode;
};
-static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jobject graphicBuffer, jlongArray atlasMapArray) {
- sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer);
- jsize len = env->GetArrayLength(atlasMapArray);
- if (len <= 0) {
- ALOGW("Failed to initialize atlas, invalid map length: %d", len);
- return;
+class ObserverProxy;
+
+class NotifyHandler : public MessageHandler {
+public:
+ NotifyHandler(JavaVM* vm, ObserverProxy* observer) : mVm(vm), mObserver(observer) {}
+
+ virtual void handleMessage(const Message& message);
+
+private:
+ JavaVM* const mVm;
+ ObserverProxy* const mObserver;
+};
+
+static jlongArray get_metrics_buffer(JNIEnv* env, jobject observer) {
+ jobject frameMetrics = env->GetObjectField(
+ observer, gFrameMetricsObserverClassInfo.frameMetrics);
+ LOG_ALWAYS_FATAL_IF(frameMetrics == nullptr, "unable to retrieve data sink object");
+ jobject buffer = env->GetObjectField(
+ frameMetrics, gFrameMetricsObserverClassInfo.timingDataBuffer);
+ LOG_ALWAYS_FATAL_IF(buffer == nullptr, "unable to retrieve data sink buffer");
+ return reinterpret_cast<jlongArray>(buffer);
+}
+
+/*
+ * Implements JNI layer for hwui frame metrics reporting.
+ */
+class ObserverProxy : public FrameMetricsObserver {
+public:
+ ObserverProxy(JavaVM *vm, jobject observer) : mVm(vm) {
+ JNIEnv* env = getenv(mVm);
+
+ mObserverWeak = env->NewWeakGlobalRef(observer);
+ LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr,
+ "unable to create frame stats observer reference");
+
+ jlongArray buffer = get_metrics_buffer(env, observer);
+ jsize bufferSize = env->GetArrayLength(reinterpret_cast<jarray>(buffer));
+ LOG_ALWAYS_FATAL_IF(bufferSize != kBufferSize,
+ "Mismatched Java/Native FrameMetrics data format.");
+
+ jobject messageQueueLocal = env->GetObjectField(
+ observer, gFrameMetricsObserverClassInfo.messageQueue);
+ mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
+ LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
+
+ mMessageHandler = new NotifyHandler(mVm, this);
+ LOG_ALWAYS_FATAL_IF(mMessageHandler == nullptr,
+ "OOM: unable to allocate NotifyHandler");
+ }
+
+ ~ObserverProxy() {
+ JNIEnv* env = getenv(mVm);
+ env->DeleteWeakGlobalRef(mObserverWeak);
}
- int64_t* map = new int64_t[len];
- env->GetLongArrayRegion(atlasMapArray, 0, len, map);
- RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setTextureAtlas(buffer, map, len);
+ jweak getObserverReference() {
+ return mObserverWeak;
+ }
+
+ bool getNextBuffer(JNIEnv* env, jlongArray sink, int* dropCount) {
+ FrameMetricsNotification& elem = mRingBuffer[mNextInQueue];
+
+ if (elem.hasData.load()) {
+ env->SetLongArrayRegion(sink, 0, kBufferSize, elem.buffer);
+ *dropCount = elem.dropCount;
+ mNextInQueue = (mNextInQueue + 1) % kRingSize;
+ elem.hasData = false;
+ return true;
+ }
+
+ return false;
+ }
+
+ virtual void notify(const int64_t* stats) {
+ FrameMetricsNotification& elem = mRingBuffer[mNextFree];
+
+ if (!elem.hasData.load()) {
+ memcpy(elem.buffer, stats, kBufferSize * sizeof(stats[0]));
+
+ elem.dropCount = mDroppedReports;
+ mDroppedReports = 0;
+
+ incStrong(nullptr);
+ mNextFree = (mNextFree + 1) % kRingSize;
+ elem.hasData = true;
+
+ mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
+ } else {
+ mDroppedReports++;
+ }
+ }
+
+private:
+ static const int kBufferSize = static_cast<int>(FrameInfoIndex::NumIndexes);
+ static constexpr int kRingSize = 3;
+
+ class FrameMetricsNotification {
+ public:
+ FrameMetricsNotification() : hasData(false) {}
+
+ std::atomic_bool hasData;
+ int64_t buffer[kBufferSize];
+ int dropCount = 0;
+ };
+
+ JavaVM* const mVm;
+ jweak mObserverWeak;
+
+ sp<MessageQueue> mMessageQueue;
+ sp<NotifyHandler> mMessageHandler;
+ Message mMessage;
+
+ int mNextFree = 0;
+ int mNextInQueue = 0;
+ FrameMetricsNotification mRingBuffer[kRingSize];
+
+ int mDroppedReports = 0;
+};
+
+void NotifyHandler::handleMessage(const Message& message) {
+ JNIEnv* env = getenv(mVm);
+
+ jobject target = env->NewLocalRef(mObserver->getObserverReference());
+
+ if (target != nullptr) {
+ jlongArray javaBuffer = get_metrics_buffer(env, target);
+ int dropCount = 0;
+ while (mObserver->getNextBuffer(env, javaBuffer, &dropCount)) {
+ env->CallVoidMethod(target, gFrameMetricsObserverClassInfo.callback, dropCount);
+ }
+ env->DeleteLocalRef(target);
+ }
+
+ mObserver->decStrong(nullptr);
+}
+
+static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) {
+ RenderProxy::rotateProcessStatsBuffer();
}
static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jint fd) {
+ jint fd) {
+ RenderProxy::setProcessStatsBuffer(fd);
+}
+
+static jint android_view_ThreadedRenderer_getRenderThreadTid(JNIEnv* env, jobject clazz,
+ jlong proxyPtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setProcessStatsBuffer(fd);
+ return proxy->getRenderThreadTid();
}
static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) {
env->ReleaseStringUTFChars(jname, name);
}
-static jboolean android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
+static void android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- sp<ANativeWindow> window = android_view_Surface_getNativeWindow(env, jsurface);
- return proxy->initialize(window);
+ sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
+ proxy->initialize(surface);
}
static void android_view_ThreadedRenderer_updateSurface(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- sp<ANativeWindow> window;
+ sp<Surface> surface;
if (jsurface) {
- window = android_view_Surface_getNativeWindow(env, jsurface);
+ surface = android_view_Surface_getSurface(env, jsurface);
}
- proxy->updateSurface(window);
+ proxy->updateSurface(surface);
}
static jboolean android_view_ThreadedRenderer_pauseSurface(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- sp<ANativeWindow> window;
+ sp<Surface> surface;
if (jsurface) {
- window = android_view_Surface_getNativeWindow(env, jsurface);
+ surface = android_view_Surface_getSurface(env, jsurface);
}
- return proxy->pauseSurface(window);
+ return proxy->pauseSurface(surface);
+}
+
+static void android_view_ThreadedRenderer_setStopped(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jboolean stopped) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->setStopped(stopped);
}
static void android_view_ThreadedRenderer_setup(JNIEnv* env, jobject clazz, jlong proxyPtr,
- jint width, jint height, jfloat lightRadius, jint ambientShadowAlpha, jint spotShadowAlpha) {
+ jfloat lightRadius, jint ambientShadowAlpha, jint spotShadowAlpha) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->setup(width, height, lightRadius, ambientShadowAlpha, spotShadowAlpha);
+ proxy->setup(lightRadius, ambientShadowAlpha, spotShadowAlpha);
}
static void android_view_ThreadedRenderer_setLightCenter(JNIEnv* env, jobject clazz,
proxy->setOpaque(opaque);
}
+static void android_view_ThreadedRenderer_setWideGamut(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jboolean wideGamut) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->setWideGamut(wideGamut);
+}
+
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
}
static void android_view_ThreadedRenderer_destroy(JNIEnv* env, jobject clazz,
- jlong proxyPtr) {
+ jlong proxyPtr, jlong rootNodePtr) {
+ RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr);
+ rootRenderNode->destroy();
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
proxy->destroy();
}
rootRenderNode->attachAnimatingNode(animatingNode);
}
+static void android_view_ThreadedRenderer_registerVectorDrawableAnimator(JNIEnv* env, jobject clazz,
+ jlong rootNodePtr, jlong animatorPtr) {
+ RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootNodePtr);
+ PropertyValuesAnimatorSet* animator = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorPtr);
+ rootRenderNode->addVectorDrawableAnimator(animator);
+}
+
static void android_view_ThreadedRenderer_invokeFunctor(JNIEnv* env, jobject clazz,
jlong functorPtr, jboolean waitForCompletion) {
Functor* functor = reinterpret_cast<Functor*>(functorPtr);
proxy->notifyFramePending();
}
+static void android_view_ThreadedRenderer_serializeDisplayListTree(JNIEnv* env, jobject clazz,
+ jlong proxyPtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->serializeDisplayListTree();
+}
+
static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject javaFileDescriptor, jint dumpFlags) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
proxy->dumpProfileInfo(fd, dumpFlags);
}
-static void android_view_ThreadedRenderer_dumpProfileData(JNIEnv* env, jobject clazz,
- jbyteArray jdata, jobject javaFileDescriptor) {
- int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
- ScopedByteArrayRO buffer(env, jdata);
- if (buffer.get()) {
- JankTracker::dumpBuffer(buffer.get(), buffer.size(), fd);
+static void android_view_ThreadedRenderer_addRenderNode(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong renderNodePtr, jboolean placeFront) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ proxy->addRenderNode(renderNode, placeFront);
+}
+
+static void android_view_ThreadedRenderer_removeRenderNode(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong renderNodePtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ proxy->removeRenderNode(renderNode);
+}
+
+static void android_view_ThreadedRendererd_drawRenderNode(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong renderNodePtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ proxy->drawRenderNode(renderNode);
+}
+
+static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env,
+ jobject clazz, jlong proxyPtr, jint left, jint top, jint right, jint bottom) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ proxy->setContentDrawBounds(left, top, right, bottom);
+}
+
+static jint android_view_ThreadedRenderer_copySurfaceInto(JNIEnv* env,
+ jobject clazz, jobject jsurface, jint left, jint top,
+ jint right, jint bottom, jobject jbitmap) {
+ SkBitmap bitmap;
+ GraphicsJNI::getSkBitmap(env, jbitmap, &bitmap);
+ sp<Surface> surface = android_view_Surface_getSurface(env, jsurface);
+ return RenderProxy::copySurfaceInto(surface, left, top, right, bottom, &bitmap);
+}
+
+class ContextFactory : public IContextFactory {
+public:
+ virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) {
+ return new AnimationContext(clock);
+ }
+};
+
+static jobject android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode(JNIEnv* env,
+ jobject clazz, jlong renderNodePtr, jint jwidth, jint jheight) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ if (jwidth <= 0 || jheight <= 0) {
+ ALOGW("Invalid width %d or height %d", jwidth, jheight);
+ return nullptr;
+ }
+
+ uint32_t width = jwidth;
+ uint32_t height = jheight;
+
+ // Create a Surface wired up to a BufferItemConsumer
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> rawConsumer;
+ BufferQueue::createBufferQueue(&producer, &rawConsumer);
+ // We only need 1 buffer but some drivers have bugs so workaround it by setting max count to 2
+ rawConsumer->setMaxBufferCount(2);
+ sp<BufferItemConsumer> consumer = new BufferItemConsumer(rawConsumer,
+ GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_SW_READ_NEVER | GRALLOC_USAGE_SW_WRITE_NEVER);
+ consumer->setDefaultBufferSize(width, height);
+ sp<Surface> surface = new Surface(producer);
+
+ // Render into the surface
+ {
+ ContextFactory factory;
+ RenderProxy proxy{true, renderNode, &factory};
+ proxy.loadSystemProperties();
+ proxy.setSwapBehavior(SwapBehavior::kSwap_discardBuffer);
+ proxy.initialize(surface);
+ // Shadows can't be used via this interface, so just set the light source
+ // to all 0s.
+ proxy.setup(0, 0, 0);
+ proxy.setLightCenter((Vector3){0, 0, 0});
+ nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+ UiFrameInfoBuilder(proxy.frameInfo())
+ .setVsync(vsync, vsync)
+ .addFlag(FrameInfoFlags::SurfaceCanvas);
+ proxy.syncAndDrawFrame();
+ }
+
+ // Yank out the GraphicBuffer
+ BufferItem bufferItem;
+ status_t err;
+ if ((err = consumer->acquireBuffer(&bufferItem, 0, true)) != OK) {
+ ALOGW("Failed to acquireBuffer, error %d (%s)", err, strerror(-err));
+ return nullptr;
+ }
+ sp<GraphicBuffer> buffer = bufferItem.mGraphicBuffer;
+ // We don't really care if this fails or not since we're just going to destroy this anyway
+ consumer->releaseBuffer(bufferItem);
+ if (!buffer.get()) {
+ ALOGW("GraphicBuffer is null?");
+ return nullptr;
+ }
+ if (buffer->getWidth() != width || buffer->getHeight() != height) {
+ ALOGW("GraphicBuffer size mismatch, got %dx%d expected %dx%d",
+ buffer->getWidth(), buffer->getHeight(), width, height);
+ // Continue I guess?
+ }
+ sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer);
+ if (!bitmap.get()) {
+ ALOGW("Failed to create hardware bitmap from graphic buffer");
+ return nullptr;
+ }
+ return createBitmap(env, bitmap.release(), android::bitmap::kBitmapCreateFlag_Mutable);
+}
+
+static void android_view_ThreadedRenderer_disableVsync(JNIEnv*, jclass) {
+ RenderProxy::disableVsync();
+}
+
+// ----------------------------------------------------------------------------
+// FrameMetricsObserver
+// ----------------------------------------------------------------------------
+
+static jlong android_view_ThreadedRenderer_addFrameMetricsObserver(JNIEnv* env,
+ jclass clazz, jlong proxyPtr, jobject fso) {
+ JavaVM* vm = nullptr;
+ if (env->GetJavaVM(&vm) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Unable to get Java VM");
+ return 0;
}
+
+ renderthread::RenderProxy* renderProxy =
+ reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+
+ FrameMetricsObserver* observer = new ObserverProxy(vm, fso);
+ renderProxy->addFrameMetricsObserver(observer);
+ return reinterpret_cast<jlong>(observer);
}
+static void android_view_ThreadedRenderer_removeFrameMetricsObserver(JNIEnv* env, jclass clazz,
+ jlong proxyPtr, jlong observerPtr) {
+ FrameMetricsObserver* observer = reinterpret_cast<FrameMetricsObserver*>(observerPtr);
+ renderthread::RenderProxy* renderProxy =
+ reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+
+ renderProxy->removeFrameMetricsObserver(observer);
+}
// ----------------------------------------------------------------------------
// Shaders
static void android_view_ThreadedRenderer_setupShadersDiskCache(JNIEnv* env, jobject clazz,
jstring diskCachePath) {
-
const char* cacheArray = env->GetStringUTFChars(diskCachePath, NULL);
- egl_cache_t::get()->setCacheFilename(cacheArray);
+ android::egl_set_cache_filename(cacheArray);
env->ReleaseStringUTFChars(diskCachePath, cacheArray);
}
const char* const kClassPathName = "android/view/ThreadedRenderer";
-static JNINativeMethod gMethods[] = {
- { "nSetAtlas", "(JLandroid/view/GraphicBuffer;[J)V", (void*) android_view_ThreadedRenderer_setAtlas },
- { "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
+static const JNINativeMethod gMethods[] = {
+ { "nRotateProcessStatsBuffer", "()V", (void*) android_view_ThreadedRenderer_rotateProcessStatsBuffer },
+ { "nSetProcessStatsBuffer", "(I)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
+ { "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid },
{ "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
{ "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
{ "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
{ "nLoadSystemProperties", "(J)Z", (void*) android_view_ThreadedRenderer_loadSystemProperties },
{ "nSetName", "(JLjava/lang/String;)V", (void*) android_view_ThreadedRenderer_setName },
- { "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
+ { "nInitialize", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_initialize },
{ "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
{ "nPauseSurface", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_pauseSurface },
- { "nSetup", "(JIIFII)V", (void*) android_view_ThreadedRenderer_setup },
+ { "nSetStopped", "(JZ)V", (void*) android_view_ThreadedRenderer_setStopped },
+ { "nSetup", "(JFII)V", (void*) android_view_ThreadedRenderer_setup },
{ "nSetLightCenter", "(JFFF)V", (void*) android_view_ThreadedRenderer_setLightCenter },
{ "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
+ { "nSetWideGamut", "(JZ)V", (void*) android_view_ThreadedRenderer_setWideGamut },
{ "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
- { "nDestroy", "(J)V", (void*) android_view_ThreadedRenderer_destroy },
+ { "nDestroy", "(JJ)V", (void*) android_view_ThreadedRenderer_destroy },
{ "nRegisterAnimatingRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_registerAnimatingRenderNode },
+ { "nRegisterVectorDrawableAnimator", "(JJ)V", (void*) android_view_ThreadedRenderer_registerVectorDrawableAnimator },
{ "nInvokeFunctor", "(JZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
{ "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer },
{ "nBuildLayer", "(JJ)V", (void*) android_view_ThreadedRenderer_buildLayer },
{ "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence },
{ "nStopDrawing", "(J)V", (void*) android_view_ThreadedRenderer_stopDrawing },
{ "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
+ { "nSerializeDisplayListTree", "(J)V", (void*) android_view_ThreadedRenderer_serializeDisplayListTree },
{ "nDumpProfileInfo", "(JLjava/io/FileDescriptor;I)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
- { "nDumpProfileData", "([BLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileData },
{ "setupShadersDiskCache", "(Ljava/lang/String;)V",
(void*) android_view_ThreadedRenderer_setupShadersDiskCache },
+ { "nAddRenderNode", "(JJZ)V", (void*) android_view_ThreadedRenderer_addRenderNode},
+ { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
+ { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
+ { "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
+ { "nAddFrameMetricsObserver",
+ "(JLandroid/view/FrameMetricsObserver;)J",
+ (void*)android_view_ThreadedRenderer_addFrameMetricsObserver },
+ { "nRemoveFrameMetricsObserver",
+ "(JJ)V",
+ (void*)android_view_ThreadedRenderer_removeFrameMetricsObserver },
+ { "nCopySurfaceInto", "(Landroid/view/Surface;IIIILandroid/graphics/Bitmap;)I",
+ (void*)android_view_ThreadedRenderer_copySurfaceInto },
+ { "nCreateHardwareBitmap", "(JII)Landroid/graphics/Bitmap;",
+ (void*)android_view_ThreadedRenderer_createHardwareBitmapFromRenderNode },
+ { "disableVsync", "()V", (void*)android_view_ThreadedRenderer_disableVsync },
};
int register_android_view_ThreadedRenderer(JNIEnv* env) {
+ jclass observerClass = FindClassOrDie(env, "android/view/FrameMetricsObserver");
+ gFrameMetricsObserverClassInfo.frameMetrics = GetFieldIDOrDie(
+ env, observerClass, "mFrameMetrics", "Landroid/view/FrameMetrics;");
+ gFrameMetricsObserverClassInfo.messageQueue = GetFieldIDOrDie(
+ env, observerClass, "mMessageQueue", "Landroid/os/MessageQueue;");
+ gFrameMetricsObserverClassInfo.callback = GetMethodIDOrDie(
+ env, observerClass, "notifyDataAvailable", "(I)V");
+
+ jclass metricsClass = FindClassOrDie(env, "android/view/FrameMetrics");
+ gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
+ env, metricsClass, "mTimingData", "[J");
+
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}