summaryrefslogtreecommitdiff
path: root/libs/hwui/renderthread
diff options
context:
space:
mode:
Diffstat (limited to 'libs/hwui/renderthread')
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp430
-rw-r--r--libs/hwui/renderthread/CanvasContext.h46
-rw-r--r--libs/hwui/renderthread/IRenderPipeline.h76
-rw-r--r--libs/hwui/renderthread/OpenGLPipeline.cpp189
-rw-r--r--libs/hwui/renderthread/OpenGLPipeline.h69
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp39
-rw-r--r--libs/hwui/renderthread/RenderProxy.h5
7 files changed, 495 insertions, 359 deletions
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5003c6aefb8b..2eccca9fee37 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -21,15 +21,16 @@
#include "Caches.h"
#include "DeferredLayerUpdater.h"
#include "EglManager.h"
-#include "LayerUpdateQueue.h"
#include "LayerRenderer.h"
-#include "OpenGLRenderer.h"
+#include "LayerUpdateQueue.h"
#include "Properties.h"
+#include "Readback.h"
#include "RenderThread.h"
#include "hwui/Canvas.h"
#include "renderstate/RenderState.h"
#include "renderstate/Stencil.h"
#include "protos/hwui.pb.h"
+#include "OpenGLPipeline.h"
#include "utils/GLUtils.h"
#include "utils/TimeUtils.h"
@@ -61,15 +62,40 @@ namespace android {
namespace uirenderer {
namespace renderthread {
+CanvasContext* CanvasContext::create(RenderThread& thread,
+ bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) {
+
+ auto renderType = Properties::getRenderPipelineType();
+
+ switch (renderType) {
+ case RenderPipelineType::OpenGL:
+ return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
+ std::make_unique<OpenGLPipeline>(thread));
+ case RenderPipelineType::SkiaGL:
+ //TODO: implement SKIA GL
+ LOG_ALWAYS_FATAL("skiaGL canvas type not implemented.");
+ break;
+ case RenderPipelineType::SkiaVulkan:
+ //TODO: implement Vulkan
+ LOG_ALWAYS_FATAL("Vulkan canvas type not implemented.");
+ break;
+ default:
+ LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
+ break;
+ }
+ return nullptr;
+}
+
CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
- RenderNode* rootRenderNode, IContextFactory* contextFactory)
+ RenderNode* rootRenderNode, IContextFactory* contextFactory,
+ std::unique_ptr<IRenderPipeline> renderPipeline)
: mRenderThread(thread)
- , mEglManager(thread.eglManager())
, mOpaque(!translucent)
, mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
, mJankTracker(thread.timeLord().frameIntervalNanos())
, mProfiler(mFrames)
- , mContentDrawBounds(0, 0, 0, 0) {
+ , mContentDrawBounds(0, 0, 0, 0)
+ , mRenderPipeline(std::move(renderPipeline)) {
mRenderNodes.emplace_back(rootRenderNode);
mRenderThread.renderState().registerCanvasContext(this);
mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
@@ -86,12 +112,6 @@ void CanvasContext::destroy(TreeObserver* observer) {
freePrefetchedLayers(observer);
destroyHardwareResources(observer);
mAnimationContext->destroy();
-#if !HWUI_NEW_OPS
- if (mCanvas) {
- delete mCanvas;
- mCanvas = nullptr;
- }
-#endif
}
void CanvasContext::setSurface(Surface* surface) {
@@ -99,24 +119,15 @@ void CanvasContext::setSurface(Surface* surface) {
mNativeSurface = surface;
- if (mEglSurface != EGL_NO_SURFACE) {
- mEglManager.destroySurface(mEglSurface);
- mEglSurface = EGL_NO_SURFACE;
- }
-
- if (surface) {
- mEglSurface = mEglManager.createSurface(surface);
- }
+ bool hasSurface = mRenderPipeline->setSurface(surface, mSwapBehavior);
mFrameNumber = -1;
- if (mEglSurface != EGL_NO_SURFACE) {
- const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer);
- mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
- mHaveNewSurface = true;
- mSwapHistory.clear();
+ if (hasSurface) {
+ mHaveNewSurface = true;
+ mSwapHistory.clear();
} else {
- mRenderThread.removeFrameCallback(this);
+ mRenderThread.removeFrameCallback(this);
}
}
@@ -126,11 +137,6 @@ void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) {
void CanvasContext::initialize(Surface* surface) {
setSurface(surface);
-#if !HWUI_NEW_OPS
- if (mCanvas) return;
- mCanvas = new OpenGLRenderer(mRenderThread.renderState());
- mCanvas->initProperties();
-#endif
}
void CanvasContext::updateSurface(Surface* surface) {
@@ -146,35 +152,22 @@ void CanvasContext::setStopped(bool stopped) {
mStopped = stopped;
if (mStopped) {
mRenderThread.removeFrameCallback(this);
- if (mEglManager.isCurrent(mEglSurface)) {
- mEglManager.makeCurrent(EGL_NO_SURFACE);
- }
+ mRenderPipeline->onStop();
} else if (mIsDirty && hasSurface()) {
mRenderThread.postFrameCallback(this);
}
}
}
-// TODO: don't pass viewport size, it's automatic via EGL
-void CanvasContext::setup(int width, int height, float lightRadius,
+void CanvasContext::setup(float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
-#if HWUI_NEW_OPS
mLightGeometry.radius = lightRadius;
mLightInfo.ambientShadowAlpha = ambientShadowAlpha;
mLightInfo.spotShadowAlpha = spotShadowAlpha;
-#else
- if (!mCanvas) return;
- mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha);
-#endif
}
void CanvasContext::setLightCenter(const Vector3& lightCenter) {
-#if HWUI_NEW_OPS
mLightGeometry.center = lightCenter;
-#else
- if (!mCanvas) return;
- mCanvas->setLightCenter(lightCenter);
-#endif
}
void CanvasContext::setOpaque(bool opaque) {
@@ -184,14 +177,23 @@ void CanvasContext::setOpaque(bool opaque) {
bool CanvasContext::makeCurrent() {
if (mStopped) return false;
- // TODO: Figure out why this workaround is needed, see b/13913604
- // In the meantime this matches the behavior of GLRenderer, so it is not a regression
- EGLint error = 0;
- mHaveNewSurface |= mEglManager.makeCurrent(mEglSurface, &error);
- if (error) {
- setSurface(nullptr);
+ auto result = mRenderPipeline->makeCurrent();
+ switch (result) {
+ case MakeCurrentResult::AlreadyCurrent:
+ return true;
+ case MakeCurrentResult::Failed:
+ mHaveNewSurface = true;
+ setSurface(nullptr);
+ return false;
+ case MakeCurrentResult::Succeeded:
+ mHaveNewSurface = true;
+ return true;
+ default:
+ LOG_ALWAYS_FATAL("unexpected result %d from IRenderPipeline::makeCurrent",
+ (int32_t) result);
}
- return !error;
+
+ return true;
}
static bool wasSkipped(FrameInfo* info) {
@@ -251,11 +253,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
mCurrentFrameInfo->markSyncStart();
info.damageAccumulator = &mDamageAccumulator;
-#if HWUI_NEW_OPS
info.layerUpdateQueue = &mLayerUpdateQueue;
-#else
- info.renderer = mCanvas;
-#endif
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
@@ -280,7 +278,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
return;
}
- if (CC_LIKELY(mSwapHistory.size())) {
+ if (CC_LIKELY(mSwapHistory.size() && !Properties::forceDrawFrame)) {
nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
SwapHistory& lastSwap = mSwapHistory.back();
int durationUs;
@@ -332,11 +330,6 @@ void CanvasContext::notifyFramePending() {
}
void CanvasContext::draw() {
-#if !HWUI_NEW_OPS
- LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
- "drawRenderNode called on a context with no canvas or surface!");
-#endif
-
SkRect dirty;
mDamageAccumulator.finish(&dirty);
@@ -348,215 +341,27 @@ void CanvasContext::draw() {
mCurrentFrameInfo->markIssueDrawCommandsStart();
- Frame frame = mEglManager.beginFrame(mEglSurface);
-
- if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) {
- // can't rely on prior content of window if viewport size changes
- dirty.setEmpty();
- mLastFrameWidth = frame.width();
- mLastFrameHeight = frame.height();
- } else if (mHaveNewSurface || frame.bufferAge() == 0) {
- // New surface needs a full draw
- dirty.setEmpty();
- } else {
- if (!dirty.isEmpty() && !dirty.intersect(0, 0, frame.width(), frame.height())) {
- ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?",
- SK_RECT_ARGS(dirty), frame.width(), frame.height());
- dirty.setEmpty();
- }
- profiler().unionDirty(&dirty);
- }
-
- if (dirty.isEmpty()) {
- dirty.set(0, 0, frame.width(), frame.height());
- }
-
- // At this point dirty is the area of the screen to update. However,
- // the area of the frame we need to repaint is potentially different, so
- // stash the screen area for later
- SkRect screenDirty(dirty);
-
- // If the buffer age is 0 we do a full-screen repaint (handled above)
- // If the buffer age is 1 the buffer contents are the same as they were
- // last frame so there's nothing to union() against
- // Therefore we only care about the > 1 case.
- if (frame.bufferAge() > 1) {
- if (frame.bufferAge() > (int) mSwapHistory.size()) {
- // We don't have enough history to handle this old of a buffer
- // Just do a full-draw
- dirty.set(0, 0, frame.width(), frame.height());
- } else {
- // At this point we haven't yet added the latest frame
- // to the damage history (happens below)
- // So we need to damage
- for (int i = mSwapHistory.size() - 1;
- i > ((int) mSwapHistory.size()) - frame.bufferAge(); i--) {
- dirty.join(mSwapHistory[i].damage);
- }
- }
- }
-
- mEglManager.damageFrame(frame, dirty);
-
-#if HWUI_NEW_OPS
- auto& caches = Caches::getInstance();
- FrameBuilder frameBuilder(dirty, frame.width(), frame.height(), mLightGeometry, caches);
+ Frame frame = mRenderPipeline->getFrame();
- frameBuilder.deferLayers(mLayerUpdateQueue);
- mLayerUpdateQueue.clear();
+ SkRect windowDirty = computeDirtyRect(frame, &dirty);
- frameBuilder.deferRenderNodeScene(mRenderNodes, mContentDrawBounds);
-
- BakedOpRenderer renderer(caches, mRenderThread.renderState(),
- mOpaque, mLightInfo);
- frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
- profiler().draw(&renderer);
- bool drew = renderer.didDraw();
-
- // post frame cleanup
- caches.clearGarbage();
- caches.pathCache.trim();
- caches.tessellationCache.trim();
-
-#if DEBUG_MEMORY_USAGE
- mCaches.dumpMemoryUsage();
-#else
- if (CC_UNLIKELY(Properties::debugLevel & kDebugMemory)) {
- caches.dumpMemoryUsage();
- }
-#endif
-
-#else
- mCanvas->prepareDirty(frame.width(), frame.height(),
- dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque);
-
- Rect outBounds;
- // It there are multiple render nodes, they are laid out as follows:
- // #0 - backdrop (content + caption)
- // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds)
- // #2 - additional overlay nodes
- // Usually the backdrop cannot be seen since it will be entirely covered by the content. While
- // resizing however it might become partially visible. The following render loop will crop the
- // backdrop against the content and draw the remaining part of it. It will then draw the content
- // cropped to the backdrop (since that indicates a shrinking of the window).
- //
- // Additional nodes will be drawn on top with no particular clipping semantics.
-
- // The bounds of the backdrop against which the content should be clipped.
- Rect backdropBounds = mContentDrawBounds;
- // Usually the contents bounds should be mContentDrawBounds - however - we will
- // move it towards the fixed edge to give it a more stable appearance (for the moment).
- Rect contentBounds;
- // If there is no content bounds we ignore the layering as stated above and start with 2.
- int layer = (mContentDrawBounds.isEmpty() || mRenderNodes.size() == 1) ? 2 : 0;
- // Draw all render nodes. Note that
- for (const sp<RenderNode>& node : mRenderNodes) {
- if (layer == 0) { // Backdrop.
- // Draw the backdrop clipped to the inverse content bounds, but assume that the content
- // was moved to the upper left corner.
- const RenderProperties& properties = node->properties();
- Rect targetBounds(properties.getLeft(), properties.getTop(),
- properties.getRight(), properties.getBottom());
- // Move the content bounds towards the fixed corner of the backdrop.
- const int x = targetBounds.left;
- const int y = targetBounds.top;
- contentBounds.set(x, y, x + mContentDrawBounds.getWidth(),
- y + mContentDrawBounds.getHeight());
- // Remember the intersection of the target bounds and the intersection bounds against
- // which we have to crop the content.
- backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight());
- backdropBounds.doIntersect(targetBounds);
- // Check if we have to draw something on the left side ...
- if (targetBounds.left < contentBounds.left) {
- mCanvas->save(SaveFlags::Clip);
- if (mCanvas->clipRect(targetBounds.left, targetBounds.top,
- contentBounds.left, targetBounds.bottom,
- SkRegion::kIntersect_Op)) {
- mCanvas->drawRenderNode(node.get(), outBounds);
- }
- // Reduce the target area by the area we have just painted.
- targetBounds.left = std::min(contentBounds.left, targetBounds.right);
- mCanvas->restore();
- }
- // ... or on the right side ...
- if (targetBounds.right > contentBounds.right &&
- !targetBounds.isEmpty()) {
- mCanvas->save(SaveFlags::Clip);
- if (mCanvas->clipRect(contentBounds.right, targetBounds.top,
- targetBounds.right, targetBounds.bottom,
- SkRegion::kIntersect_Op)) {
- mCanvas->drawRenderNode(node.get(), outBounds);
- }
- // Reduce the target area by the area we have just painted.
- targetBounds.right = std::max(targetBounds.left, contentBounds.right);
- mCanvas->restore();
- }
- // ... or at the top ...
- if (targetBounds.top < contentBounds.top &&
- !targetBounds.isEmpty()) {
- mCanvas->save(SaveFlags::Clip);
- if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right,
- contentBounds.top,
- SkRegion::kIntersect_Op)) {
- mCanvas->drawRenderNode(node.get(), outBounds);
- }
- // Reduce the target area by the area we have just painted.
- targetBounds.top = std::min(contentBounds.top, targetBounds.bottom);
- mCanvas->restore();
- }
- // ... or at the bottom.
- if (targetBounds.bottom > contentBounds.bottom &&
- !targetBounds.isEmpty()) {
- mCanvas->save(SaveFlags::Clip);
- if (mCanvas->clipRect(targetBounds.left, contentBounds.bottom, targetBounds.right,
- targetBounds.bottom, SkRegion::kIntersect_Op)) {
- mCanvas->drawRenderNode(node.get(), outBounds);
- }
- mCanvas->restore();
- }
- } else if (layer == 1) { // Content
- // It gets cropped against the bounds of the backdrop to stay inside.
- mCanvas->save(SaveFlags::MatrixClip);
-
- // We shift and clip the content to match its final location in the window.
- const float left = mContentDrawBounds.left;
- const float top = mContentDrawBounds.top;
- const float dx = backdropBounds.left - left;
- const float dy = backdropBounds.top - top;
- const float width = backdropBounds.getWidth();
- const float height = backdropBounds.getHeight();
-
- mCanvas->translate(dx, dy);
- if (mCanvas->clipRect(left, top, left + width, top + height, SkRegion::kIntersect_Op)) {
- mCanvas->drawRenderNode(node.get(), outBounds);
- }
- mCanvas->restore();
- } else { // draw the rest on top at will!
- mCanvas->drawRenderNode(node.get(), outBounds);
- }
- layer++;
- }
-
- profiler().draw(mCanvas);
-
- bool drew = mCanvas->finish();
-#endif
+ bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
+ mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes, &(profiler()));
waitOnFences();
- GL_CHECKPOINT(LOW);
+ bool requireSwap = false;
+ bool didSwap = mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo,
+ &requireSwap);
- // Even if we decided to cancel the frame, from the perspective of jank
- // metrics the frame was swapped at this point
- mCurrentFrameInfo->markSwapBuffers();
mIsDirty = false;
- if (drew || mEglManager.damageRequiresSwap()) {
- if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) {
+ if (requireSwap) {
+ if (!didSwap) { //some error happened
setSurface(nullptr);
}
SwapHistory& swap = mSwapHistory.next();
- swap.damage = screenDirty;
+ swap.damage = windowDirty;
swap.swapCompletedTime = systemTime(CLOCK_MONOTONIC);
swap.vsyncTime = mRenderThread.timeLord().latestVsync();
mHaveNewSurface = false;
@@ -592,11 +397,7 @@ void CanvasContext::draw() {
// Called by choreographer to do an RT-driven animation
void CanvasContext::doFrame() {
-#if HWUI_NEW_OPS
- if (CC_UNLIKELY(mEglSurface == EGL_NO_SURFACE)) return;
-#else
- if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) return;
-#endif
+ if (!mRenderPipeline->isSurfaceReady()) return;
prepareAndDraw(nullptr);
}
@@ -646,10 +447,7 @@ void CanvasContext::freePrefetchedLayers(TreeObserver* observer) {
void CanvasContext::buildLayer(RenderNode* node, TreeObserver* observer) {
ATRACE_CALL();
- if (!mEglManager.hasEglContext()) return;
-#if !HWUI_NEW_OPS
- if (!mCanvas) return;
-#endif
+ if (!mRenderPipeline->isContextReady()) return;
// buildLayer() will leave the tree in an unknown state, so we must stop drawing
stopDrawing();
@@ -657,11 +455,7 @@ void CanvasContext::buildLayer(RenderNode* node, TreeObserver* observer) {
TreeInfo info(TreeInfo::MODE_FULL, *this);
info.damageAccumulator = &mDamageAccumulator;
info.observer = observer;
-#if HWUI_NEW_OPS
info.layerUpdateQueue = &mLayerUpdateQueue;
-#else
- info.renderer = mCanvas;
-#endif
info.runAnimations = false;
node->prepareTree(info);
SkRect ignore;
@@ -670,41 +464,24 @@ void CanvasContext::buildLayer(RenderNode* node, TreeObserver* observer) {
// purposes when the frame is actually drawn
node->setPropertyFieldsDirty(RenderNode::GENERIC);
-#if HWUI_NEW_OPS
- static const std::vector< sp<RenderNode> > emptyNodeList;
- auto& caches = Caches::getInstance();
- FrameBuilder frameBuilder(mLayerUpdateQueue, mLightGeometry, caches);
- mLayerUpdateQueue.clear();
- BakedOpRenderer renderer(caches, mRenderThread.renderState(),
- mOpaque, mLightInfo);
- LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case");
- frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
-#else
- mCanvas->markLayersAsBuildLayers();
- mCanvas->flushLayerUpdates();
-#endif
+ mRenderPipeline->renderLayers(mLightGeometry, &mLayerUpdateQueue, mOpaque, mLightInfo);
node->incStrong(nullptr);
mPrefetchedLayers.insert(node);
}
bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
- layer->apply();
- return LayerRenderer::copyLayer(mRenderThread.renderState(), layer->backingLayer(), bitmap);
+ return mRenderPipeline->copyLayerInto(layer, bitmap);
}
void CanvasContext::destroyHardwareResources(TreeObserver* observer) {
stopDrawing();
- if (mEglManager.hasEglContext()) {
+ if (mRenderPipeline->isContextReady()) {
freePrefetchedLayers(observer);
for (const sp<RenderNode>& node : mRenderNodes) {
node->destroyHardwareResources(observer);
}
- Caches& caches = Caches::getInstance();
- // Make sure to release all the textures we were owning as there won't
- // be another draw
- caches.textureCache.resetMarkInUse(this);
- mRenderThread.renderState().flush(Caches::FlushMode::Layers);
+ mRenderPipeline->onDestroyHardwareResources();
}
}
@@ -721,15 +498,8 @@ void CanvasContext::trimMemory(RenderThread& thread, int level) {
}
}
-void CanvasContext::runWithGlContext(RenderTask* task) {
- LOG_ALWAYS_FATAL_IF(!mEglManager.hasEglContext(),
- "GL context not initialized!");
- task->run();
-}
-
Layer* CanvasContext::createTextureLayer() {
- mEglManager.initialize();
- return LayerRenderer::createTextureLayer(mRenderThread.renderState());
+ return mRenderPipeline->createTextureLayer();
}
void CanvasContext::setTextureAtlas(RenderThread& thread,
@@ -809,8 +579,8 @@ void CanvasContext::waitOnFences() {
class CanvasContext::FuncTaskProcessor : public TaskProcessor<bool> {
public:
- FuncTaskProcessor(Caches& caches)
- : TaskProcessor<bool>(&caches.tasks) {}
+ explicit FuncTaskProcessor(TaskManager* taskManager)
+ : TaskProcessor<bool>(taskManager) {}
virtual void onProcess(const sp<Task<bool> >& task) override {
FuncTask* t = static_cast<FuncTask*>(task.get());
@@ -821,7 +591,7 @@ public:
void CanvasContext::enqueueFrameWork(std::function<void()>&& func) {
if (!mFrameWorkProcessor.get()) {
- mFrameWorkProcessor = new FuncTaskProcessor(Caches::getInstance());
+ mFrameWorkProcessor = new FuncTaskProcessor(mRenderPipeline->getTaskManager());
}
sp<FuncTask> task(new FuncTask());
task->func = func;
@@ -837,6 +607,56 @@ int64_t CanvasContext::getFrameNumber() {
return mFrameNumber;
}
+SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) {
+ if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) {
+ // can't rely on prior content of window if viewport size changes
+ dirty->setEmpty();
+ mLastFrameWidth = frame.width();
+ mLastFrameHeight = frame.height();
+ } else if (mHaveNewSurface || frame.bufferAge() == 0) {
+ // New surface needs a full draw
+ dirty->setEmpty();
+ } else {
+ if (!dirty->isEmpty() && !dirty->intersect(0, 0, frame.width(), frame.height())) {
+ ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?",
+ SK_RECT_ARGS(*dirty), frame.width(), frame.height());
+ dirty->setEmpty();
+ }
+ profiler().unionDirty(dirty);
+ }
+
+ if (dirty->isEmpty()) {
+ dirty->set(0, 0, frame.width(), frame.height());
+ }
+
+ // At this point dirty is the area of the window to update. However,
+ // the area of the frame we need to repaint is potentially different, so
+ // stash the screen area for later
+ SkRect windowDirty(*dirty);
+
+ // If the buffer age is 0 we do a full-screen repaint (handled above)
+ // If the buffer age is 1 the buffer contents are the same as they were
+ // last frame so there's nothing to union() against
+ // Therefore we only care about the > 1 case.
+ if (frame.bufferAge() > 1) {
+ if (frame.bufferAge() > (int) mSwapHistory.size()) {
+ // We don't have enough history to handle this old of a buffer
+ // Just do a full-draw
+ dirty->set(0, 0, frame.width(), frame.height());
+ } else {
+ // At this point we haven't yet added the latest frame
+ // to the damage history (happens below)
+ // So we need to damage
+ for (int i = mSwapHistory.size() - 1;
+ i > ((int) mSwapHistory.size()) - frame.bufferAge(); i--) {
+ dirty->join(mSwapHistory[i].damage);
+ }
+ }
+ }
+
+ return windowDirty;
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index b0d980b94308..42e9be33d4ea 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-#ifndef CANVASCONTEXT_H_
-#define CANVASCONTEXT_H_
+#pragma once
+#include "BakedOpDispatcher.h"
+#include "BakedOpRenderer.h"
#include "DamageAccumulator.h"
+#include "FrameBuilder.h"
#include "FrameInfo.h"
#include "FrameInfoVisualizer.h"
#include "FrameMetricsReporter.h"
#include "IContextFactory.h"
+#include "IRenderPipeline.h"
#include "LayerUpdateQueue.h"
#include "RenderNode.h"
#include "thread/Task.h"
@@ -30,12 +33,6 @@
#include "renderthread/RenderTask.h"
#include "renderthread/RenderThread.h"
-#if HWUI_NEW_OPS
-#include "BakedOpDispatcher.h"
-#include "BakedOpRenderer.h"
-#include "FrameBuilder.h"
-#endif
-
#include <cutils/compiler.h>
#include <EGL/egl.h>
#include <SkBitmap.h>
@@ -53,27 +50,22 @@ namespace uirenderer {
class AnimationContext;
class DeferredLayerUpdater;
-class OpenGLRenderer;
-class Rect;
class Layer;
+class Rect;
class RenderState;
namespace renderthread {
class EglManager;
-
-enum SwapBehavior {
- kSwap_default,
- kSwap_discardBuffer,
-};
+class Frame;
// This per-renderer class manages the bridge between the global EGL context
// and the render surface.
// TODO: Rename to Renderer or some other per-window, top-level manager
class CanvasContext : public IFrameCallback {
public:
- CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
- IContextFactory* contextFactory);
+ static CanvasContext* create(RenderThread& thread, bool translucent,
+ RenderNode* rootRenderNode, IContextFactory* contextFactory);
virtual ~CanvasContext();
// Won't take effect until next EGLSurface creation
@@ -85,7 +77,7 @@ public:
void setStopped(bool stopped);
bool hasSurface() { return mNativeSurface.get(); }
- void setup(int width, int height, float lightRadius,
+ void setup(float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
void setLightCenter(const Vector3& lightCenter);
void setOpaque(bool opaque);
@@ -108,8 +100,6 @@ public:
static void invokeFunctor(RenderThread& thread, Functor* functor);
- void runWithGlContext(RenderTask* task);
-
Layer* createTextureLayer();
ANDROID_API static void setTextureAtlas(RenderThread& thread,
@@ -169,6 +159,9 @@ public:
ANDROID_API int64_t getFrameNumber();
private:
+ CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
+ IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline);
+
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
// lifecycle tracking
@@ -182,21 +175,20 @@ private:
bool isSwapChainStuffed();
+ SkRect computeDirtyRect(const Frame& frame, SkRect* dirty);
+
EGLint mLastFrameWidth = 0;
EGLint mLastFrameHeight = 0;
RenderThread& mRenderThread;
- EglManager& mEglManager;
sp<Surface> mNativeSurface;
- EGLSurface mEglSurface = EGL_NO_SURFACE;
// stopped indicates the CanvasContext will reject actual redraw operations,
// and defer repaint until it is un-stopped
bool mStopped = false;
// CanvasContext is dirty if it has received an update that it has not
// painted onto its surface.
bool mIsDirty = false;
- bool mBufferPreserved = false;
- SwapBehavior mSwapBehavior = kSwap_default;
+ SwapBehavior mSwapBehavior = SwapBehavior::kSwap_default;
struct SwapHistory {
SkRect damage;
nsecs_t vsyncTime;
@@ -209,12 +201,8 @@ private:
int64_t mFrameNumber = -1;
bool mOpaque;
-#if HWUI_NEW_OPS
BakedOpRenderer::LightInfo mLightInfo;
FrameBuilder::LightGeometry mLightGeometry = { {0, 0, 0}, 0 };
-#else
- OpenGLRenderer* mCanvas = nullptr;
-#endif
bool mHaveNewSurface = false;
DamageAccumulator mDamageAccumulator;
@@ -245,9 +233,9 @@ private:
std::vector< sp<FuncTask> > mFrameFences;
sp<TaskProcessor<bool> > mFrameWorkProcessor;
+ std::unique_ptr<IRenderPipeline> mRenderPipeline;
};
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
-#endif /* CANVASCONTEXT_H_ */
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
new file mode 100644
index 000000000000..0c0fbe9d716c
--- /dev/null
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include "FrameInfoVisualizer.h"
+#include "EglManager.h"
+
+#include <SkRect.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+class Surface;
+
+namespace uirenderer {
+
+class DeferredLayerUpdater;
+
+namespace renderthread {
+
+enum class SwapBehavior {
+ kSwap_default,
+ kSwap_discardBuffer,
+};
+
+enum class MakeCurrentResult {
+ AlreadyCurrent,
+ Failed,
+ Succeeded
+};
+
+class IRenderPipeline {
+public:
+ virtual MakeCurrentResult makeCurrent() = 0;
+ virtual Frame getFrame() = 0;
+ virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const FrameBuilder::LightGeometry& lightGeometry,
+ LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque,
+ const BakedOpRenderer::LightInfo& lightInfo,
+ const std::vector< sp<RenderNode> >& renderNodes,
+ FrameInfoVisualizer* profiler) = 0;
+ virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
+ FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
+ virtual bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) = 0;
+ virtual Layer* createTextureLayer() = 0;
+ virtual bool setSurface(Surface* window, SwapBehavior swapBehavior) = 0;
+ virtual void onStop() = 0;
+ virtual bool isSurfaceReady() = 0;
+ virtual bool isContextReady() = 0;
+ virtual void onDestroyHardwareResources() = 0;
+ virtual void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
+ LayerUpdateQueue* layerUpdateQueue, bool opaque,
+ const BakedOpRenderer::LightInfo& lightInfo) = 0;
+ virtual TaskManager* getTaskManager() = 0;
+
+ virtual ~IRenderPipeline() {}
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp
new file mode 100644
index 000000000000..3a2b15584d50
--- /dev/null
+++ b/libs/hwui/renderthread/OpenGLPipeline.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include "OpenGLPipeline.h"
+
+#include "DeferredLayerUpdater.h"
+#include "EglManager.h"
+#include "LayerRenderer.h"
+#include "renderstate/RenderState.h"
+#include "Readback.h"
+
+#include <android/native_window.h>
+#include <cutils/properties.h>
+#include <strings.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+OpenGLPipeline::OpenGLPipeline(RenderThread& thread)
+ : mEglManager(thread.eglManager()), mRenderThread(thread) {
+}
+
+MakeCurrentResult OpenGLPipeline::makeCurrent() {
+ // TODO: Figure out why this workaround is needed, see b/13913604
+ // In the meantime this matches the behavior of GLRenderer, so it is not a regression
+ EGLint error = 0;
+ bool haveNewSurface = mEglManager.makeCurrent(mEglSurface, &error);
+
+ Caches::getInstance().textureCache.resetMarkInUse(this);
+ if (!haveNewSurface) {
+ return MakeCurrentResult::AlreadyCurrent;
+ }
+ return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
+}
+
+Frame OpenGLPipeline::getFrame() {
+ LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
+ "drawRenderNode called on a context with no surface!");
+ return mEglManager.beginFrame(mEglSurface);
+}
+
+bool OpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const FrameBuilder::LightGeometry& lightGeometry,
+ LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque,
+ const BakedOpRenderer::LightInfo& lightInfo,
+ const std::vector< sp<RenderNode> >& renderNodes,
+ FrameInfoVisualizer* profiler) {
+
+ mEglManager.damageFrame(frame, dirty);
+
+ bool drew = false;
+
+
+ auto& caches = Caches::getInstance();
+ FrameBuilder frameBuilder(dirty, frame.width(), frame.height(), lightGeometry, caches);
+
+ frameBuilder.deferLayers(*layerUpdateQueue);
+ layerUpdateQueue->clear();
+
+ frameBuilder.deferRenderNodeScene(renderNodes, contentDrawBounds);
+
+ BakedOpRenderer renderer(caches, mRenderThread.renderState(),
+ opaque, lightInfo);
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+ profiler->draw(&renderer);
+ drew = renderer.didDraw();
+
+ // post frame cleanup
+ caches.clearGarbage();
+ caches.pathCache.trim();
+ caches.tessellationCache.trim();
+
+#if DEBUG_MEMORY_USAGE
+ mCaches.dumpMemoryUsage();
+#else
+ if (CC_UNLIKELY(Properties::debugLevel & kDebugMemory)) {
+ caches.dumpMemoryUsage();
+ }
+#endif
+
+ return drew;
+}
+
+bool OpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
+ FrameInfo* currentFrameInfo, bool* requireSwap) {
+
+ GL_CHECKPOINT(LOW);
+
+ // Even if we decided to cancel the frame, from the perspective of jank
+ // metrics the frame was swapped at this point
+ currentFrameInfo->markSwapBuffers();
+
+ *requireSwap = drew || mEglManager.damageRequiresSwap();
+
+ if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
+ return false;
+ }
+
+ return *requireSwap;
+}
+
+bool OpenGLPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
+ layer->apply();
+ return Readback::copyTextureLayerInto(mRenderThread, *(layer->backingLayer()), bitmap)
+ == CopyResult::Success;
+}
+
+Layer* OpenGLPipeline::createTextureLayer() {
+ mEglManager.initialize();
+ return LayerRenderer::createTextureLayer(mRenderThread.renderState());
+}
+
+void OpenGLPipeline::onStop() {
+ if (mEglManager.isCurrent(mEglSurface)) {
+ mEglManager.makeCurrent(EGL_NO_SURFACE);
+ }
+}
+
+bool OpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior) {
+
+ if (mEglSurface != EGL_NO_SURFACE) {
+ mEglManager.destroySurface(mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+ }
+
+ if (surface) {
+ mEglSurface = mEglManager.createSurface(surface);
+ }
+
+ if (mEglSurface != EGL_NO_SURFACE) {
+ const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
+ mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
+ return true;
+ }
+
+ return false;
+}
+
+bool OpenGLPipeline::isSurfaceReady() {
+ return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE);
+}
+
+bool OpenGLPipeline::isContextReady() {
+ return CC_LIKELY(mEglManager.hasEglContext());
+}
+
+void OpenGLPipeline::onDestroyHardwareResources() {
+ Caches& caches = Caches::getInstance();
+ // Make sure to release all the textures we were owning as there won't
+ // be another draw
+ caches.textureCache.resetMarkInUse(this);
+ mRenderThread.renderState().flush(Caches::FlushMode::Layers);
+}
+
+void OpenGLPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
+ LayerUpdateQueue* layerUpdateQueue, bool opaque,
+ const BakedOpRenderer::LightInfo& lightInfo) {
+ static const std::vector< sp<RenderNode> > emptyNodeList;
+ auto& caches = Caches::getInstance();
+ FrameBuilder frameBuilder(*layerUpdateQueue, lightGeometry, caches);
+ layerUpdateQueue->clear();
+ BakedOpRenderer renderer(caches, mRenderThread.renderState(),
+ opaque, lightInfo);
+ LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case");
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+}
+
+TaskManager* OpenGLPipeline::getTaskManager() {
+ return &Caches::getInstance().tasks;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h
new file mode 100644
index 000000000000..a6d22ee8b34f
--- /dev/null
+++ b/libs/hwui/renderthread/OpenGLPipeline.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include "CanvasContext.h"
+#include "BakedOpDispatcher.h"
+#include "BakedOpRenderer.h"
+#include "FrameBuilder.h"
+#include "IRenderPipeline.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+class Frame;
+
+
+class OpenGLPipeline : public IRenderPipeline {
+public:
+ OpenGLPipeline(RenderThread& thread);
+ virtual ~OpenGLPipeline() {}
+
+ MakeCurrentResult makeCurrent() override;
+ Frame getFrame() override;
+ bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
+ const FrameBuilder::LightGeometry& lightGeometry,
+ LayerUpdateQueue* layerUpdateQueue,
+ const Rect& contentDrawBounds, bool opaque,
+ const BakedOpRenderer::LightInfo& lightInfo,
+ const std::vector< sp<RenderNode> >& renderNodes,
+ FrameInfoVisualizer* profiler) override;
+ bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
+ FrameInfo* currentFrameInfo, bool* requireSwap) override;
+ bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override;
+ Layer* createTextureLayer() override;
+ bool setSurface(Surface* window, SwapBehavior swapBehavior) override;
+ void onStop() override;
+ bool isSurfaceReady() override;
+ bool isContextReady() override;
+ void onDestroyHardwareResources() override;
+ void renderLayers(const FrameBuilder::LightGeometry& lightGeometry,
+ LayerUpdateQueue* layerUpdateQueue, bool opaque,
+ const BakedOpRenderer::LightInfo& lightInfo) override;
+ TaskManager* getTaskManager() override;
+
+private:
+ EglManager& mEglManager;
+ EGLSurface mEglSurface = EGL_NO_SURFACE;
+ bool mBufferPreserved = false;
+ RenderThread& mRenderThread;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 06a24b248bf6..d860acd1edd1 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -55,7 +55,7 @@ namespace renderthread {
CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory) {
- return new CanvasContext(*args->thread, args->translucent,
+ return CanvasContext::create(*args->thread, args->translucent,
args->rootRenderNode, args->contextFactory);
}
@@ -180,19 +180,17 @@ void RenderProxy::setStopped(bool stopped) {
postAndWait(task);
}
-CREATE_BRIDGE6(setup, CanvasContext* context, int width, int height,
+CREATE_BRIDGE4(setup, CanvasContext* context,
float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
- args->context->setup(args->width, args->height, args->lightRadius,
+ args->context->setup(args->lightRadius,
args->ambientShadowAlpha, args->spotShadowAlpha);
return nullptr;
}
-void RenderProxy::setup(int width, int height, float lightRadius,
+void RenderProxy::setup(float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
SETUP_TASK(setup);
args->context = mContext;
- args->width = width;
- args->height = height;
args->lightRadius = lightRadius;
args->ambientShadowAlpha = ambientShadowAlpha;
args->spotShadowAlpha = spotShadowAlpha;
@@ -267,18 +265,6 @@ void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) {
}
}
-CREATE_BRIDGE2(runWithGlContext, CanvasContext* context, RenderTask* task) {
- args->context->runWithGlContext(args->task);
- return nullptr;
-}
-
-void RenderProxy::runWithGlContext(RenderTask* gltask) {
- SETUP_TASK(runWithGlContext);
- args->context = mContext;
- args->task = gltask;
- postAndWait(task);
-}
-
CREATE_BRIDGE1(createTextureLayer, CanvasContext* context) {
Layer* layer = args->context->createTextureLayer();
if (!layer) return nullptr;
@@ -456,6 +442,19 @@ void RenderProxy::resetProfileInfo() {
postAndWait(task);
}
+CREATE_BRIDGE2(frameTimePercentile, RenderThread* thread, int percentile) {
+ return reinterpret_cast<void*>(static_cast<uintptr_t>(
+ args->thread->jankTracker().findPercentile(args->percentile)));
+}
+
+uint32_t RenderProxy::frameTimePercentile(int p) {
+ SETUP_TASK(frameTimePercentile);
+ args->thread = &mRenderThread;
+ args->percentile = p;
+ return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(
+ postAndWait(task)));
+}
+
CREATE_BRIDGE2(dumpGraphicsMemory, int fd, RenderThread* thread) {
args->thread->jankTracker().dump(args->fd);
@@ -467,11 +466,7 @@ CREATE_BRIDGE2(dumpGraphicsMemory, int fd, RenderThread* thread) {
} else {
fprintf(file, "\nNo caches instance.\n");
}
-#if HWUI_NEW_OPS
fprintf(file, "\nPipeline=FrameBuilder\n");
-#else
- fprintf(file, "\nPipeline=OpenGLRenderer\n");
-#endif
fflush(file);
return nullptr;
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index e31062c83734..0bee858a84fa 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -80,7 +80,7 @@ public:
ANDROID_API void updateSurface(const sp<Surface>& surface);
ANDROID_API bool pauseSurface(const sp<Surface>& surface);
ANDROID_API void setStopped(bool stopped);
- ANDROID_API void setup(int width, int height, float lightRadius,
+ ANDROID_API void setup(float lightRadius,
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
ANDROID_API void setLightCenter(const Vector3& lightCenter);
ANDROID_API void setOpaque(bool opaque);
@@ -90,8 +90,6 @@ public:
ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion);
- ANDROID_API void runWithGlContext(RenderTask* task);
-
ANDROID_API DeferredLayerUpdater* createTextureLayer();
ANDROID_API void buildLayer(RenderNode* node, TreeObserver* observer);
ANDROID_API bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap);
@@ -111,6 +109,7 @@ public:
ANDROID_API void dumpProfileInfo(int fd, int dumpFlags);
// Not exported, only used for testing
void resetProfileInfo();
+ uint32_t frameTimePercentile(int p);
ANDROID_API static void dumpGraphicsMemory(int fd);
ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size);