diff options
-rw-r--r-- | libs/hwui/Android.common.mk | 1 | ||||
-rw-r--r-- | libs/hwui/Caches.cpp | 34 | ||||
-rw-r--r-- | libs/hwui/Caches.h | 10 | ||||
-rw-r--r-- | libs/hwui/Extensions.cpp | 11 | ||||
-rw-r--r-- | libs/hwui/Extensions.h | 4 | ||||
-rw-r--r-- | libs/hwui/OpenGLRenderer.cpp | 67 | ||||
-rwxr-xr-x | libs/hwui/OpenGLRenderer.h | 25 | ||||
-rw-r--r-- | libs/hwui/Properties.cpp | 4 | ||||
-rw-r--r-- | libs/hwui/Properties.h | 24 | ||||
-rw-r--r-- | libs/hwui/RenderNode.cpp | 7 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.cpp | 102 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.h | 5 | ||||
-rw-r--r-- | libs/hwui/renderthread/DirtyHistory.cpp | 69 | ||||
-rw-r--r-- | libs/hwui/renderthread/DirtyHistory.h | 45 | ||||
-rw-r--r-- | libs/hwui/renderthread/EglManager.cpp | 190 | ||||
-rw-r--r-- | libs/hwui/renderthread/EglManager.h | 51 | ||||
-rw-r--r-- | libs/hwui/tests/main.cpp | 64 | ||||
-rw-r--r-- | libs/hwui/tests/nullgles.cpp | 2 |
18 files changed, 292 insertions, 423 deletions
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk index 7b2bba138df5..fe4f1be81bb9 100644 --- a/libs/hwui/Android.common.mk +++ b/libs/hwui/Android.common.mk @@ -19,7 +19,6 @@ LOCAL_SRC_FILES := \ renderthread/RenderTask.cpp \ renderthread/RenderThread.cpp \ renderthread/TimeLord.cpp \ - renderthread/DirtyHistory.cpp \ thread/TaskManager.cpp \ utils/Blur.cpp \ utils/GLUtils.cpp \ diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index 693b28eb7922..f663f07269d7 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -68,8 +68,6 @@ bool Caches::init() { mRegionMesh = nullptr; mProgram = nullptr; - mFunctorsCount = 0; - patchCache.init(); mInitialized = true; @@ -276,38 +274,6 @@ void Caches::flush(FlushMode mode) { } /////////////////////////////////////////////////////////////////////////////// -// Tiling -/////////////////////////////////////////////////////////////////////////////// - -void Caches::startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard) { - if (mExtensions.hasTiledRendering() && !Properties::debugOverdraw) { - glStartTilingQCOM(x, y, width, height, (discard ? GL_NONE : GL_COLOR_BUFFER_BIT0_QCOM)); - } -} - -void Caches::endTiling() { - if (mExtensions.hasTiledRendering() && !Properties::debugOverdraw) { - glEndTilingQCOM(GL_COLOR_BUFFER_BIT0_QCOM); - } -} - -bool Caches::hasRegisteredFunctors() { - return mFunctorsCount > 0; -} - -void Caches::registerFunctors(uint32_t functorCount) { - mFunctorsCount += functorCount; -} - -void Caches::unregisterFunctors(uint32_t functorCount) { - if (functorCount > mFunctorsCount) { - mFunctorsCount = 0; - } else { - mFunctorsCount -= functorCount; - } -} - -/////////////////////////////////////////////////////////////////////////////// // Regions /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 049d58bcd133..a02e15de7fb0 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -124,10 +124,6 @@ public: */ void deleteLayerDeferred(Layer* layer); - - void startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard); - void endTiling(); - /** * Returns the mesh used to draw regions. Calling this method will * bind a VBO of type GL_ELEMENT_ARRAY_BUFFER that contains the @@ -141,10 +137,6 @@ public: void dumpMemoryUsage(); void dumpMemoryUsage(String8& log); - bool hasRegisteredFunctors(); - void registerFunctors(uint32_t functorCount); - void unregisterFunctors(uint32_t functorCount); - // Misc GLint maxTextureSize; @@ -206,8 +198,6 @@ private: bool mInitialized; - uint32_t mFunctorsCount; - // TODO: move below to RenderState PixelBufferState* mPixelBufferState = nullptr; TextureState* mTextureState = nullptr; diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp index 814bada81b35..3d350c98892b 100644 --- a/libs/hwui/Extensions.cpp +++ b/libs/hwui/Extensions.cpp @@ -53,21 +53,10 @@ Extensions::Extensions() { mHasFramebufferFetch = hasGlExtension("GL_NV_shader_framebuffer_fetch"); mHasDiscardFramebuffer = hasGlExtension("GL_EXT_discard_framebuffer"); mHasDebugMarker = hasGlExtension("GL_EXT_debug_marker"); - mHasTiledRendering = hasGlExtension("GL_QCOM_tiled_rendering"); mHas1BitStencil = hasGlExtension("GL_OES_stencil1"); mHas4BitStencil = hasGlExtension("GL_OES_stencil4"); mHasUnpackSubImage = hasGlExtension("GL_EXT_unpack_subimage"); - // Query EGL extensions - findExtensions(eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS), mEglExtensionList); - - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_DEBUG_NV_PROFILING, property, nullptr) > 0) { - mHasNvSystemTime = !strcmp(property, "true") && hasEglExtension("EGL_NV_system_time"); - } else { - mHasNvSystemTime = false; - } - const char* version = (const char*) glGetString(GL_VERSION); // Section 6.1.5 of the OpenGL ES specification indicates the GL version diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h index a4eef0f0bb86..636b631532bf 100644 --- a/libs/hwui/Extensions.h +++ b/libs/hwui/Extensions.h @@ -40,10 +40,8 @@ public: inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; } inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; } inline bool hasDebugMarker() const { return mHasDebugMarker; } - inline bool hasTiledRendering() const { return mHasTiledRendering; } inline bool has1BitStencil() const { return mHas1BitStencil; } inline bool has4BitStencil() const { return mHas4BitStencil; } - inline bool hasNvSystemTime() const { return mHasNvSystemTime; } inline bool hasUnpackRowLength() const { return mVersionMajor >= 3 || mHasUnpackSubImage; } inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; } inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; } @@ -67,10 +65,8 @@ private: bool mHasFramebufferFetch; bool mHasDiscardFramebuffer; bool mHasDebugMarker; - bool mHasTiledRendering; bool mHas1BitStencil; bool mHas4BitStencil; - bool mHasNvSystemTime; bool mHasUnpackSubImage; int mVersionMajor; diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 8e04bdf3b8ad..7a56d42b65e2 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -70,8 +70,6 @@ OpenGLRenderer::OpenGLRenderer(RenderState& renderState) , mRenderState(renderState) , mFrameStarted(false) , mScissorOptimizationDisabled(false) - , mSuppressTiling(false) - , mFirstFrameAfterResize(true) , mDirty(false) , mLightCenter((Vector3){FLT_MIN, FLT_MIN, FLT_MIN}) , mLightRadius(FLT_MIN) @@ -113,7 +111,6 @@ void OpenGLRenderer::setLightCenter(const Vector3& lightCenter) { void OpenGLRenderer::onViewportInitialized() { glDisable(GL_DITHER); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - mFirstFrameAfterResize = true; } void OpenGLRenderer::setupFrameState(float left, float top, @@ -134,15 +131,6 @@ void OpenGLRenderer::startFrame() { mRenderState.setViewport(mState.getWidth(), mState.getHeight()); - // Functors break the tiling extension in pretty spectacular ways - // This ensures we don't use tiling when a functor is going to be - // invoked during the frame - mSuppressTiling = mCaches.hasRegisteredFunctors() - || mFirstFrameAfterResize; - mFirstFrameAfterResize = false; - - startTilingCurrentClip(true); - debugOverdraw(true, true); clear(mTilingClip.left, mTilingClip.top, @@ -192,46 +180,8 @@ void OpenGLRenderer::clear(float left, float top, float right, float bottom, boo mRenderState.scissor().reset(); } -void OpenGLRenderer::startTilingCurrentClip(bool opaque, bool expand) { - if (!mSuppressTiling) { - const Snapshot* snapshot = currentSnapshot(); - - const Rect* clip = &mTilingClip; - if (snapshot->flags & Snapshot::kFlagFboTarget) { - clip = &(snapshot->layer->clipRect); - } - - startTiling(*clip, getViewportHeight(), opaque, expand); - } -} - -void OpenGLRenderer::startTiling(const Rect& clip, int windowHeight, bool opaque, bool expand) { - if (!mSuppressTiling) { - if(expand) { - // Expand the startTiling region by 1 - int leftNotZero = (clip.left > 0) ? 1 : 0; - int topNotZero = (windowHeight - clip.bottom > 0) ? 1 : 0; - - mCaches.startTiling( - clip.left - leftNotZero, - windowHeight - clip.bottom - topNotZero, - clip.right - clip.left + leftNotZero + 1, - clip.bottom - clip.top + topNotZero + 1, - opaque); - } else { - mCaches.startTiling(clip.left, windowHeight - clip.bottom, - clip.right - clip.left, clip.bottom - clip.top, opaque); - } - } -} - -void OpenGLRenderer::endTiling() { - if (!mSuppressTiling) mCaches.endTiling(); -} - bool OpenGLRenderer::finish() { renderOverdraw(); - endTiling(); mTempPaths.clear(); // When finish() is invoked on FBO 0 we've reached the end @@ -381,7 +331,6 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { && layer->renderNode.get() && layer->renderNode->isRenderable()) { if (inFrame) { - endTiling(); debugOverdraw(false, false); } @@ -393,7 +342,6 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { if (inFrame) { resumeAfterLayer(); - startTilingCurrentClip(); } layer->debugDrawUpdate = Properties::debugLayersUpdates; @@ -736,7 +684,6 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) { writableSnapshot()->initializeViewport(bounds.getWidth(), bounds.getHeight()); writableSnapshot()->roundRectClipState = nullptr; - endTiling(); debugOverdraw(false, false); // Bind texture to FBO mRenderState.bindFramebuffer(layer->getFbo()); @@ -751,9 +698,6 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, layer->getTextureId(), 0); - // Expand the startTiling region by 1 - startTilingCurrentClip(true, true); - // Clear the FBO, expand the clear region by 1 to get nice bilinear filtering mRenderState.scissor().setEnabled(true); mRenderState.scissor().set(clip.left - 1.0f, bounds.getHeight() - clip.bottom - 1.0f, @@ -786,8 +730,6 @@ void OpenGLRenderer::composeLayer(const Snapshot& removed, const Snapshot& resto mRenderState.scissor().setEnabled(mScissorOptimizationDisabled || clipRequired); if (fboLayer) { - endTiling(); - // Detach the texture from the FBO glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); @@ -796,8 +738,6 @@ void OpenGLRenderer::composeLayer(const Snapshot& removed, const Snapshot& resto // Unbind current FBO and restore previous one mRenderState.bindFramebuffer(restored.fbo); debugOverdraw(true, false); - - startTilingCurrentClip(); } if (!fboLayer && layer->getAlpha() < 255) { @@ -1267,17 +1207,10 @@ void OpenGLRenderer::ensureStencilBuffer() { void OpenGLRenderer::attachStencilBufferToLayer(Layer* layer) { // The layer's FBO is already bound when we reach this stage if (!layer->getStencilRenderBuffer()) { - // GL_QCOM_tiled_rendering doesn't like it if a renderbuffer - // is attached after we initiated tiling. We must turn it off, - // attach the new render buffer then turn tiling back on - endTiling(); - RenderBuffer* buffer = mCaches.renderBufferCache.get( Stencil::getLayerStencilFormat(), layer->getWidth(), layer->getHeight()); layer->setStencilRenderBuffer(buffer); - - startTiling(layer->clipRect, layer->layer.getHeight()); } } diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 390f6207f4ea..4f75482f06e5 100755 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -555,27 +555,6 @@ private: void discardFramebuffer(float left, float top, float right, float bottom); /** - * Tells the GPU what part of the screen is about to be redrawn. - * This method will use the current layer space clip rect. - * This method needs to be invoked every time getTargetFbo() is - * bound again. - */ - void startTilingCurrentClip(bool opaque = false, bool expand = false); - - /** - * Tells the GPU what part of the screen is about to be redrawn. - * This method needs to be invoked every time getTargetFbo() is - * bound again. - */ - void startTiling(const Rect& clip, int windowHeight, bool opaque = false, bool expand = false); - - /** - * Tells the GPU that we are done drawing the frame or that we - * are switching to another render target. - */ - void endTiling(); - - /** * Sets the clipping rectangle using glScissor. The clip is defined by * the current snapshot's clipRect member. */ @@ -862,10 +841,6 @@ private: // Properties.h bool mScissorOptimizationDisabled; - // No-ops start/endTiling when set - bool mSuppressTiling; - bool mFirstFrameAfterResize; - bool mSkipOutlineClip; // True if anything has been drawn since the last call to diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 2e63793f6ffe..b8f8585e5af6 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -30,6 +30,8 @@ bool Properties::debugOverdraw = false; bool Properties::showDirtyRegions = false; bool Properties::skipEmptyFrames = true; bool Properties::swapBuffersWithDamage = true; +bool Properties::useBufferAge = true; +bool Properties::enablePartialUpdates = true; DebugLevel Properties::debugLevel = kDebugDisabled; OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default; @@ -105,6 +107,8 @@ bool Properties::load() { skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true); swapBuffersWithDamage = property_get_bool(PROPERTY_SWAP_WITH_DAMAGE, true); + useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true); + enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true); return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw) diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 1a70d7c20f03..76028482ba86 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -84,12 +84,6 @@ enum DebugLevel { #define PROPERTY_DEBUG_OVERDRAW "debug.hwui.overdraw" /** - * Used to enable/disable PerfHUD ES profiling. The accepted values - * are "true" and "false". The default value is "false". - */ -#define PROPERTY_DEBUG_NV_PROFILING "debug.hwui.nv_profiling" - -/** * System property used to enable or disable hardware rendering profiling. * The default value of this property is assumed to be false. * @@ -150,9 +144,25 @@ enum DebugLevel { /** * Setting this property will enable or disable usage of EGL_KHR_swap_buffers_with_damage * See: https://www.khronos.org/registry/egl/extensions/KHR/EGL_KHR_swap_buffers_with_damage.txt + * Default is "true" */ #define PROPERTY_SWAP_WITH_DAMAGE "debug.hwui.swap_with_damage" +/** + * Controls whether or not HWUI will use the EGL_EXT_buffer_age extension + * to do partial invalidates. Setting this to "false" will fall back to + * using BUFFER_PRESERVED instead + * Default is "true" + */ +#define PROPERTY_USE_BUFFER_AGE "debug.hwui.use_buffer_age" + +/** + * Setting this to "false" will force HWUI to always do full-redraws of the surface. + * This will disable the use of EGL_EXT_buffer_age and BUFFER_PRESERVED. + * Default is "true" + */ +#define PROPERTY_ENABLE_PARTIAL_UPDATES "debug.hwui.enable_partial_updates" + /////////////////////////////////////////////////////////////////////////////// // Runtime configuration properties /////////////////////////////////////////////////////////////////////////////// @@ -287,6 +297,8 @@ public: static bool skipEmptyFrames; // TODO: Remove after stabilization period static bool swapBuffersWithDamage; + static bool useBufferAge; + static bool enablePartialUpdates; static DebugLevel debugLevel; static OverdrawColorSet overdrawColorSet; diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index af8aaa20342e..b75b764c50c4 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -303,10 +303,6 @@ void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) { // changes in isRenderable or, in the future, bounds damageSelf(info); deleteDisplayListData(); - // TODO: Remove this caches stuff - if (mStagingDisplayListData && mStagingDisplayListData->functors.size()) { - Caches::getInstance().registerFunctors(mStagingDisplayListData->functors.size()); - } mDisplayListData = mStagingDisplayListData; mStagingDisplayListData = nullptr; if (mDisplayListData) { @@ -323,9 +319,6 @@ void RenderNode::deleteDisplayListData() { for (size_t i = 0; i < mDisplayListData->children().size(); i++) { mDisplayListData->children()[i]->mRenderNode->decParentRefCount(); } - if (mDisplayListData->functors.size()) { - Caches::getInstance().unregisterFunctors(mDisplayListData->functors.size()); - } } delete mDisplayListData; mDisplayListData = nullptr; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index ea7338732664..67c42f3c9977 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -35,6 +35,14 @@ #define TRIM_MEMORY_COMPLETE 80 #define TRIM_MEMORY_UI_HIDDEN 20 +#define LOG_FRAMETIME_MMA 0 + +#if LOG_FRAMETIME_MMA +static float sBenchMma = 0; +static int sFrameCount = 0; +static const float NANOS_PER_MILLIS_F = 1000000.0f; +#endif + namespace android { namespace uirenderer { namespace renderthread { @@ -93,16 +101,6 @@ void CanvasContext::setSurface(ANativeWindow* window) { } } -void CanvasContext::swapBuffers(const SkRect& dirty, EGLint width, EGLint height) { - if (CC_UNLIKELY(!mEglManager.swapBuffers(mEglSurface, dirty, width, height))) { - setSurface(nullptr); - } - mHaveNewSurface = false; - if (mEglManager.useBufferAgeExt()) { - mDirtyHistory.prepend(Rect(dirty)); - } -} - void CanvasContext::requireSurface() { LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, "requireSurface() called but no surface set!"); @@ -230,8 +228,6 @@ void CanvasContext::draw() { "drawRenderNode called on a context with no canvas or surface!"); SkRect dirty; - bool useBufferAgeExt = mEglManager.useBufferAgeExt(); - Rect patchedDirty; mDamageAccumulator.finish(&dirty); // TODO: Re-enable after figuring out cause of b/22592975 @@ -242,40 +238,59 @@ void CanvasContext::draw() { mCurrentFrameInfo->markIssueDrawCommandsStart(); - EGLint width, height, framebufferAge; - mEglManager.beginFrame(mEglSurface, &width, &height, &framebufferAge); - - if (useBufferAgeExt && mHaveNewSurface) { - mDirtyHistory.clear(); - } - - if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) { - mCanvas->setViewport(width, height); + Frame frame = mEglManager.beginFrame(mEglSurface); + if (frame.width() != mCanvas->getViewportWidth() + || frame.height() != mCanvas->getViewportHeight()) { + mCanvas->setViewport(frame.width(), frame.height()); dirty.setEmpty(); - } else if (!mBufferPreserved || mHaveNewSurface) { - mDirtyHistory.clear(); + } else if (mHaveNewSurface || frame.bufferAge() == 0) { + // New surface needs a full draw dirty.setEmpty(); } else { - if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) { + 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), width, height); + SK_RECT_ARGS(dirty), frame.width(), frame.height()); dirty.setEmpty(); } profiler().unionDirty(&dirty); } - patchedDirty = dirty; - if (useBufferAgeExt && !dirty.isEmpty()) { - patchedDirty = mDirtyHistory.unionWith(Rect(dirty), framebufferAge-1); + if (dirty.isEmpty()) { + dirty.set(0, 0, frame.width(), frame.height()); } - if (!patchedDirty.isEmpty()) { - mCanvas->prepareDirty(patchedDirty.left, patchedDirty.top, - patchedDirty.right, patchedDirty.bottom, mOpaque); - } else { - mCanvas->prepare(mOpaque); + // 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) mDamageHistory.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 = mDamageHistory.size() - 1; + i > ((int) mDamageHistory.size()) - frame.bufferAge(); i--) { + dirty.join(mDamageHistory[i]); + } + } } + // Add the screen damage to the ring buffer. + mDamageHistory.next() = screenDirty; + + mEglManager.damageFrame(frame, dirty); + mCanvas->prepareDirty(dirty.fLeft, dirty.fTop, + dirty.fRight, dirty.fBottom, mOpaque); + Rect outBounds; mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds); @@ -288,11 +303,30 @@ void CanvasContext::draw() { mCurrentFrameInfo->markSwapBuffers(); if (drew) { - swapBuffers(dirty, width, height); + if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) { + setSurface(nullptr); + } + mHaveNewSurface = false; } // TODO: Use a fence for real completion? mCurrentFrameInfo->markFrameCompleted(); + +#if LOG_FRAMETIME_MMA + float thisFrame = mCurrentFrameInfo->duration( + FrameInfoIndex::IssueDrawCommandsStart, + FrameInfoIndex::FrameCompleted) / NANOS_PER_MILLIS_F; + if (sFrameCount) { + sBenchMma = ((9 * sBenchMma) + thisFrame) / 10; + } else { + sBenchMma = thisFrame; + } + if (++sFrameCount == 10) { + sFrameCount = 1; + ALOGD("Average frame time: %.4f", sBenchMma); + } +#endif + mJankTracker.addFrame(*mCurrentFrameInfo); mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo); } diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 59f9c3a288dc..0ceb9f1d04ad 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -25,7 +25,6 @@ #include "utils/RingBuffer.h" #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" -#include "renderthread/DirtyHistory.h" #include <cutils/compiler.h> #include <EGL/egl.h> @@ -119,7 +118,6 @@ private: friend class android::uirenderer::RenderState; void setSurface(ANativeWindow* window); - void swapBuffers(const SkRect& dirty, EGLint width, EGLint height); void requireSurface(); void freePrefetechedLayers(); @@ -130,6 +128,7 @@ private: EGLSurface mEglSurface = EGL_NO_SURFACE; bool mBufferPreserved = false; SwapBehavior mSwapBehavior = kSwap_default; + RingBuffer<SkRect, 3> mDamageHistory; bool mOpaque; OpenGLRenderer* mCanvas = nullptr; @@ -147,8 +146,6 @@ private: FrameInfoVisualizer mProfiler; std::set<RenderNode*> mPrefetechedLayers; - - DirtyHistory mDirtyHistory; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/DirtyHistory.cpp b/libs/hwui/renderthread/DirtyHistory.cpp deleted file mode 100644 index 1419e84ec444..000000000000 --- a/libs/hwui/renderthread/DirtyHistory.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2015 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 "DirtyHistory.h" - -namespace android { -namespace uirenderer { -namespace renderthread { - -DirtyHistory::DirtyHistory() - : mBack(DIRTY_HISTORY_SIZE - 1) { - clear(); -} - -void DirtyHistory::clear() -{ - for (int i = 0; i < DIRTY_HISTORY_SIZE; i++) { - mHistory[i].clear(); - } -} - -Rect DirtyHistory::get(int index) { - if (index >= DIRTY_HISTORY_SIZE || index < 0) - return Rect(); - return mHistory[(1 + mBack + index) % DIRTY_HISTORY_SIZE]; -} - -Rect DirtyHistory::unionWith(Rect rect, int count) { - if (rect.isEmpty() || count > DIRTY_HISTORY_SIZE || count < 0) - return Rect(); - - for (int i = 0; i < count; i++) { - Rect ith = get(i); - if (ith.isEmpty()) - return Rect(); - - // rect union - rect.left = fminf(rect.left, ith.left); - rect.top = fminf(rect.top, ith.top); - rect.right = fmaxf(rect.right, ith.right); - rect.bottom = fmaxf(rect.bottom, ith.bottom); - } - return rect; -} - -void DirtyHistory::prepend(Rect rect) { - if (rect.isEmpty()) { - mHistory[mBack].clear(); - } else { - mHistory[mBack].set(rect); - } - mBack = (mBack + DIRTY_HISTORY_SIZE - 1) % DIRTY_HISTORY_SIZE; -} - -} /* namespace renderthread */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/renderthread/DirtyHistory.h b/libs/hwui/renderthread/DirtyHistory.h deleted file mode 100644 index d5ea597878aa..000000000000 --- a/libs/hwui/renderthread/DirtyHistory.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2015 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 DIRTYHISTORY_H -#define DIRTYHISTORY_H - -#include <Rect.h> - -namespace android { -namespace uirenderer { -namespace renderthread { - -#define DIRTY_HISTORY_SIZE 4 - -class DirtyHistory { -public: - DirtyHistory(); - ~DirtyHistory() {} - - Rect get(int index); - Rect unionWith(Rect rect, int count); - void prepend(Rect rect); - void clear(); -private: - Rect mHistory[DIRTY_HISTORY_SIZE]; - int mBack; -}; - -} /* namespace renderthread */ -} /* namespace uirenderer */ -} /* namespace android */ - -#endif /* DIRTYHISTORY_H */ diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index ac36f5363eec..d2ce49f800fc 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -25,7 +25,8 @@ #include <cutils/properties.h> #include <EGL/eglext.h> -#define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions" +#include <string> + #define GLES_VERSION 2 #define WAIT_FOR_GPU_COMPLETION 0 @@ -63,10 +64,27 @@ static const char* egl_error_str() { return egl_error_str(eglGetError()); } -static bool load_dirty_regions_property() { - char buf[PROPERTY_VALUE_MAX]; - int len = property_get(PROPERTY_RENDER_DIRTY_REGIONS, buf, "true"); - return !strncasecmp("true", buf, len); +static struct { + bool bufferAge = false; + bool setDamage = false; +} EglExtensions; + +void Frame::map(const SkRect& in, EGLint* out) const { + /* The rectangles are specified relative to the bottom-left of the surface + * and the x and y components of each rectangle specify the bottom-left + * position of that rectangle. + * + * HWUI does everything with 0,0 being top-left, so need to map + * the rect + */ + SkIRect idirty; + in.roundOut(&idirty); + EGLint y = mHeight - (idirty.y() + idirty.height()); + // layout: {x, y, width, height} + out[0] = idirty.x(); + out[1] = y; + out[2] = idirty.width(); + out[3] = idirty.height(); } EglManager::EglManager(RenderThread& thread) @@ -75,13 +93,9 @@ EglManager::EglManager(RenderThread& thread) , mEglConfig(nullptr) , mEglContext(EGL_NO_CONTEXT) , mPBufferSurface(EGL_NO_SURFACE) - , mAllowPreserveBuffer(load_dirty_regions_property()) - , mHasBufferAgeExt(false) , mCurrentSurface(EGL_NO_SURFACE) , mAtlasMap(nullptr) , mAtlasMapSize(0) { - mCanSetPreserveBuffer = mAllowPreserveBuffer; - ALOGD("Use EGL_SWAP_BEHAVIOR_PRESERVED: %s", mAllowPreserveBuffer ? "true" : "false"); } void EglManager::initialize() { @@ -99,10 +113,18 @@ void EglManager::initialize() { ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor); - findExtensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS), mEglExtensionList); - mHasBufferAgeExt = hasEglExtension("EGL_EXT_buffer_age"); + initExtensions(); + + // Now that extensions are loaded, pick a swap behavior + if (Properties::enablePartialUpdates) { + if (Properties::useBufferAge && EglExtensions.bufferAge) { + mSwapBehavior = SwapBehavior::BufferAge; + } else { + mSwapBehavior = SwapBehavior::Preserved; + } + } - loadConfig(mHasBufferAgeExt); + loadConfig(); createContext(); createPBufferSurface(); makeCurrent(mPBufferSurface); @@ -110,17 +132,23 @@ void EglManager::initialize() { initAtlas(); } -bool EglManager::hasEglContext() { - return mEglDisplay != EGL_NO_DISPLAY; +void EglManager::initExtensions() { + std::string extensions(eglQueryString(mEglDisplay, EGL_EXTENSIONS)); + auto has = [&](const char* ext) { + return extensions.find(ext) != std::string::npos; + }; + EglExtensions.bufferAge = has("EGL_EXT_buffer_age"); + EglExtensions.setDamage = has("EGL_KHR_partial_update"); } -bool EglManager::hasEglExtension(const char* extension) const { - const std::string s(extension); - return mEglExtensionList.find(s) != mEglExtensionList.end(); +bool EglManager::hasEglContext() { + return mEglDisplay != EGL_NO_DISPLAY; } -void EglManager::loadConfig(bool useBufferAgeExt) { - EGLint swapBehavior = (!useBufferAgeExt && mCanSetPreserveBuffer) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; +void EglManager::loadConfig() { + ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior)); + EGLint swapBehavior = (mSwapBehavior == SwapBehavior::Preserved) + ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; EGLint attribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, @@ -137,13 +165,13 @@ void EglManager::loadConfig(bool useBufferAgeExt) { EGLint num_configs = 1; if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs) || num_configs != 1) { - // Failed to get a valid config - if (mCanSetPreserveBuffer) { - ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without..."); + if (mSwapBehavior == SwapBehavior::Preserved) { // Try again without dirty regions enabled - mCanSetPreserveBuffer = false; - loadConfig(useBufferAgeExt); + ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without..."); + mSwapBehavior = SwapBehavior::Discard; + loadConfig(); } else { + // Failed to get a valid config LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str()); } } @@ -247,24 +275,47 @@ bool EglManager::makeCurrent(EGLSurface surface, EGLint* errOut) { return true; } -void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height, EGLint* framebufferAge) { +EGLint EglManager::queryBufferAge(EGLSurface surface) { + switch (mSwapBehavior) { + case SwapBehavior::Discard: + return 0; + case SwapBehavior::Preserved: + return 1; + case SwapBehavior::BufferAge: + EGLint bufferAge; + eglQuerySurface(mEglDisplay, surface, EGL_BUFFER_AGE_EXT, &bufferAge); + return bufferAge; + } + return 0; +} + +Frame EglManager::beginFrame(EGLSurface surface) { 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); - } - if (height) { - eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height); - } - if (useBufferAgeExt()) { - eglQuerySurface(mEglDisplay, surface, EGL_BUFFER_AGE_EXT, framebufferAge); - } + Frame frame; + frame.mSurface = surface; + eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, &frame.mWidth); + eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, &frame.mHeight); + frame.mBufferAge = queryBufferAge(surface); eglBeginFrame(mEglDisplay, surface); + return frame; +} + +void EglManager::damageFrame(const Frame& frame, const SkRect& dirty) { +#ifdef EGL_KHR_partial_update + if (EglExtensions.setDamage && mSwapBehavior == SwapBehavior::BufferAge) { + EGLint rects[4]; + frame.map(dirty, rects); + if (!eglSetDamageRegionKHR(mEglDisplay, frame.mSurface, rects, 1)) { + LOG_ALWAYS_FATAL("Failed to set damage region on surface %p, error=%s", + (void*)frame.mSurface, egl_error_str()); + } + } +#endif } -bool EglManager::swapBuffers(EGLSurface surface, const SkRect& dirty, - EGLint width, EGLint height) { +bool EglManager::swapBuffers(const Frame& frame, const SkRect& screenDirty) { #if WAIT_FOR_GPU_COMPLETION { @@ -275,28 +326,15 @@ bool EglManager::swapBuffers(EGLSurface surface, const SkRect& dirty, #ifdef EGL_KHR_swap_buffers_with_damage if (CC_LIKELY(Properties::swapBuffersWithDamage)) { - SkIRect idirty; - dirty.roundOut(&idirty); - /* - * EGL_KHR_swap_buffers_with_damage spec states: - * - * The rectangles are specified relative to the bottom-left of the surface - * and the x and y components of each rectangle specify the bottom-left - * position of that rectangle. - * - * HWUI does everything with 0,0 being top-left, so need to map - * the rect - */ - EGLint y = height - (idirty.y() + idirty.height()); - // layout: {x, y, width, height} - EGLint rects[4] = { idirty.x(), y, idirty.width(), idirty.height() }; - EGLint numrects = dirty.isEmpty() ? 0 : 1; - eglSwapBuffersWithDamageKHR(mEglDisplay, surface, rects, numrects); + EGLint rects[4]; + frame.map(screenDirty, rects); + eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects, + screenDirty.isEmpty() ? 0 : 1); } else { - eglSwapBuffers(mEglDisplay, surface); + eglSwapBuffers(mEglDisplay, frame.mSurface); } #else - eglSwapBuffers(mEglDisplay, surface); + eglSwapBuffers(mEglDisplay, frame.mSurface); #endif EGLint err = eglGetError(); @@ -307,7 +345,8 @@ bool EglManager::swapBuffers(EGLSurface surface, const SkRect& dirty, // For some reason our surface was destroyed out from under us // This really shouldn't happen, but if it does we can recover easily // by just not trying to use the surface anymore - ALOGW("swapBuffers encountered EGL_BAD_SURFACE on %p, halting rendering...", surface); + ALOGW("swapBuffers encountered EGL_BAD_SURFACE on %p, halting rendering...", + frame.mSurface); return false; } LOG_ALWAYS_FATAL("Encountered EGL error %d %s during rendering", @@ -316,10 +355,6 @@ bool EglManager::swapBuffers(EGLSurface surface, const SkRect& dirty, return false; } -bool EglManager::useBufferAgeExt() { - return mAllowPreserveBuffer && mHasBufferAgeExt; -} - void EglManager::fence() { EGLSyncKHR fence = eglCreateSyncKHR(mEglDisplay, EGL_SYNC_FENCE_KHR, NULL); eglClientWaitSyncKHR(mEglDisplay, fence, @@ -328,21 +363,13 @@ void EglManager::fence() { } bool EglManager::setPreserveBuffer(EGLSurface surface, bool preserve) { - if (CC_UNLIKELY(!mAllowPreserveBuffer)) return false; - - // Use EGL_EXT_buffer_age instead if supported - if (mHasBufferAgeExt) return true; - - bool preserved = false; - if (mCanSetPreserveBuffer) { - preserved = eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, - preserve ? EGL_BUFFER_PRESERVED : EGL_BUFFER_DESTROYED); - if (CC_UNLIKELY(!preserved)) { - ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s", - (void*) surface, egl_error_str()); - } - } - if (CC_UNLIKELY(!preserved)) { + if (mSwapBehavior != SwapBehavior::Preserved) return false; + + bool preserved = eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, + preserve ? EGL_BUFFER_PRESERVED : EGL_BUFFER_DESTROYED); + if (!preserved) { + ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s", + (void*) surface, egl_error_str()); // Maybe it's already set? EGLint swapBehavior; if (eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &swapBehavior)) { @@ -356,19 +383,6 @@ bool EglManager::setPreserveBuffer(EGLSurface surface, bool preserve) { return preserved; } -void EglManager::findExtensions(const char* extensions, std::set<std::string>& list) const { - const char* current = extensions; - const char* head = current; - do { - head = strchr(current, ' '); - std::string s(current, head ? head - current : strlen(current)); - if (s.length()) { - list.insert(s); - } - current = head + 1; - } while (head); -} - } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index bb5d24b0557f..62b5b99a6e30 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -21,13 +21,35 @@ #include <SkRect.h> #include <ui/GraphicBuffer.h> #include <utils/StrongPointer.h> -#include <set> namespace android { namespace uirenderer { namespace renderthread { class RenderThread; +class EglManager; + +class Frame { +public: + EGLint width() const { return mWidth; } + EGLint height() const { return mHeight; } + + // See: https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_buffer_age.txt + // for what this means + EGLint bufferAge() const { return mBufferAge; } + +private: + friend class EglManager; + + EGLSurface mSurface; + EGLint mWidth; + EGLint mHeight; + EGLint mBufferAge; + + // Maps from 0,0 in top-left to 0,0 in bottom-left + // If out is not an EGLint[4] you're going to have a bad time + void map(const SkRect& in, EGLint* out) const; +}; // This class contains the shared global EGL objects, such as EGLDisplay // and EGLConfig, which are re-used by CanvasContext @@ -38,8 +60,6 @@ public: bool hasEglContext(); - bool hasEglExtension(const char* extension) const; - EGLSurface createSurface(EGLNativeWindowType window); void destroySurface(EGLSurface surface); @@ -48,14 +68,13 @@ public: bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; } // Returns true if the current surface changed, false if it was already current bool makeCurrent(EGLSurface surface, EGLint* errOut = nullptr); - void beginFrame(EGLSurface surface, EGLint* width, EGLint* height, EGLint* framebufferAge); - bool swapBuffers(EGLSurface surface, const SkRect& dirty, EGLint width, EGLint height); + Frame beginFrame(EGLSurface surface); + void damageFrame(const Frame& frame, const SkRect& dirty); + bool swapBuffers(const Frame& frame, const SkRect& screenDirty); // Returns true iff the surface is now preserving buffers. bool setPreserveBuffer(EGLSurface surface, bool preserve); - bool useBufferAgeExt(); - void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize); void fence(); @@ -67,12 +86,12 @@ private: // EglContext is never destroyed, method is purposely not implemented ~EglManager(); + void initExtensions(); void createPBufferSurface(); - void loadConfig(bool useBufferAgeExt); + void loadConfig(); void createContext(); void initAtlas(); - - void findExtensions(const char* extensions, std::set<std::string>& list) const; + EGLint queryBufferAge(EGLSurface surface); RenderThread& mRenderThread; @@ -81,18 +100,18 @@ private: EGLContext mEglContext; EGLSurface mPBufferSurface; - const bool mAllowPreserveBuffer; - bool mCanSetPreserveBuffer; - - bool mHasBufferAgeExt; - EGLSurface mCurrentSurface; sp<GraphicBuffer> mAtlasBuffer; int64_t* mAtlasMap; size_t mAtlasMapSize; - std::set<std::string> mEglExtensionList; + enum class SwapBehavior { + Discard, + Preserved, + BufferAge, + }; + SwapBehavior mSwapBehavior = SwapBehavior::Discard; }; } /* namespace renderthread */ diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp index 69e822510c31..483fb357a998 100644 --- a/libs/hwui/tests/main.cpp +++ b/libs/hwui/tests/main.cpp @@ -283,6 +283,69 @@ private: } }; +class PartialInvalTest : public TreeContentAnimation { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, DisplayListCanvas* renderer) override { + static SkColor COLORS[] = { + 0xFFF44336, + 0xFF9C27B0, + 0xFF2196F3, + 0xFF4CAF50, + }; + + renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + + for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { + for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { + sp<RenderNode> card = createCard(x, y, dp(100), dp(100), + COLORS[static_cast<int>((y / dp(116))) % 4]); + renderer->drawRenderNode(card.get()); + cards.push_back(card); + } + } + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + cards[0]->mutateStagingProperties().setTranslationX(curFrame); + cards[0]->mutateStagingProperties().setTranslationY(curFrame); + cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + DisplayListCanvas* renderer = startRecording(cards[0].get()); + renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0), + SkXfermode::kSrcOver_Mode); + endRecording(renderer, cards[0].get()); + } + + static SkColor interpolateColor(float fraction, SkColor start, SkColor end) { + int startA = (start >> 24) & 0xff; + int startR = (start >> 16) & 0xff; + int startG = (start >> 8) & 0xff; + int startB = start & 0xff; + + int endA = (end >> 24) & 0xff; + int endR = (end >> 16) & 0xff; + int endG = (end >> 8) & 0xff; + int endB = end & 0xff; + + return (int)((startA + (int)(fraction * (endA - startA))) << 24) | + (int)((startR + (int)(fraction * (endR - startR))) << 16) | + (int)((startG + (int)(fraction * (endG - startG))) << 8) | + (int)((startB + (int)(fraction * (endB - startB)))); + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height, SkColor color) { + sp<RenderNode> node = new RenderNode(); + node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); + node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + DisplayListCanvas* renderer = startRecording(node.get()); + renderer->drawColor(color, SkXfermode::kSrcOver_Mode); + endRecording(renderer, node.get()); + return node; + } +}; + struct cstr_cmp { bool operator()(const char *a, const char *b) const { return std::strcmp(a, b) < 0; @@ -296,6 +359,7 @@ std::map<const char*, testProc, cstr_cmp> gTestMap { {"shadowgrid2", TreeContentAnimation::run<ShadowGrid2Animation>}, {"rectgrid", TreeContentAnimation::run<RectGridAnimation> }, {"oval", TreeContentAnimation::run<OvalAnimation> }, + {"partialinval", TreeContentAnimation::run<PartialInvalTest> }, }; int main(int argc, char* argv[]) { diff --git a/libs/hwui/tests/nullgles.cpp b/libs/hwui/tests/nullgles.cpp index 8ca7598a91a0..f8e8c98c20ba 100644 --- a/libs/hwui/tests/nullgles.cpp +++ b/libs/hwui/tests/nullgles.cpp @@ -261,8 +261,6 @@ void glInsertEventMarkerEXT(GLsizei length, const GLchar *marker) {} void glPushGroupMarkerEXT(GLsizei length, const GLchar *marker) {} void glPopGroupMarkerEXT(void) {} void glDiscardFramebufferEXT(GLenum target, GLsizei numAttachments, const GLenum *attachments) {} -void glStartTilingQCOM(GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield preserveMask) {} -void glEndTilingQCOM(GLbitfield preserveMask) {} void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) {} // GLES3 |