diff options
Diffstat (limited to 'libs')
77 files changed, 2016 insertions, 875 deletions
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 13187fa8ac01..4813b4c83d04 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -3337,13 +3337,14 @@ struct ResTable::PackageGroup clearBagCache(); const size_t numTypes = types.size(); for (size_t i = 0; i < numTypes; i++) { - const TypeList& typeList = types[i]; + TypeList& typeList = types.editItemAt(i); const size_t numInnerTypes = typeList.size(); for (size_t j = 0; j < numInnerTypes; j++) { if (typeList[j]->package->owner == owner) { delete typeList[j]; } } + typeList.clear(); } const size_t N = packages.size(); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 303d05f084aa..770a57a5c445 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -36,6 +36,7 @@ cc_defaults { "external/skia/src/effects", "external/skia/src/image", "external/skia/src/utils", + "external/skia/src/gpu", ], product_variables: { @@ -133,6 +134,7 @@ cc_defaults { "pipeline/skia/SkiaProfileRenderer.cpp", "pipeline/skia/SkiaRecordingCanvas.cpp", "pipeline/skia/SkiaVulkanPipeline.cpp", + "pipeline/skia/VectorDrawableAtlas.cpp", "renderstate/Blend.cpp", "renderstate/MeshState.cpp", "renderstate/OffscreenBufferPool.cpp", @@ -201,6 +203,8 @@ cc_defaults { "PathParser.cpp", "PathTessellator.cpp", "PixelBuffer.cpp", + "ProfileData.cpp", + "ProfileDataContainer.cpp", "ProfileRenderer.cpp", "Program.cpp", "ProgramCache.cpp", @@ -300,7 +304,6 @@ cc_test { "tests/unit/BakedOpDispatcherTests.cpp", "tests/unit/BakedOpRendererTests.cpp", "tests/unit/BakedOpStateTests.cpp", - "tests/unit/BitmapTests.cpp", "tests/unit/CacheManagerTests.cpp", "tests/unit/CanvasContextTests.cpp", "tests/unit/CanvasStateTests.cpp", @@ -339,6 +342,7 @@ cc_test { "tests/unit/TextureCacheTests.cpp", "tests/unit/TypefaceTests.cpp", "tests/unit/VectorDrawableTests.cpp", + "tests/unit/VectorDrawableAtlasTests.cpp", ], } diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp index f5bb821f4e23..69ead5890566 100644 --- a/libs/hwui/AnimatorManager.cpp +++ b/libs/hwui/AnimatorManager.cpp @@ -71,9 +71,11 @@ void AnimatorManager::setAnimationHandle(AnimationHandle* handle) { void AnimatorManager::pushStaging() { if (mNewAnimators.size()) { - LOG_ALWAYS_FATAL_IF(!mAnimationHandle, - "Trying to start new animators on %p (%s) without an animation handle!", - &mParent, mParent.getName()); + if (CC_UNLIKELY(!mAnimationHandle)) { + ALOGW("Trying to start new animators on %p (%s) without an animation handle!", + &mParent, mParent.getName()); + return; + } // Only add new animators that are not already in the mAnimators list for (auto& anim : mNewAnimators) { diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index df2b35b39aa8..3c3b3177159b 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -208,7 +208,6 @@ void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* pa // TODO: Currently assume full FBO damage, due to FrameInfoVisualizer::unionDirty. // Should should scissor/set mHasDrawn safely. mRenderState.scissor().setEnabled(false); - mHasDrawn = true; Glop glop; GlopBuilder(mRenderState, mCaches, &glop) .setRoundRectClipState(nullptr) @@ -217,7 +216,8 @@ void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* pa .setTransform(Matrix4::identity(), TransformFlags::None) .setModelViewIdentityEmptyBounds() .build(); - mRenderState.render(glop, mRenderTarget.orthoMatrix); + mRenderState.render(glop, mRenderTarget.orthoMatrix, false); + mHasDrawn = true; } // clears and re-fills stencil with provided rendertarget space quads, @@ -234,7 +234,7 @@ void BakedOpRenderer::setupStencilQuads(std::vector<Vertex>& quadVertices, .setTransform(Matrix4::identity(), TransformFlags::None) .setModelViewIdentityEmptyBounds() .build(); - mRenderState.render(glop, mRenderTarget.orthoMatrix); + mRenderState.render(glop, mRenderTarget.orthoMatrix, false); mRenderState.stencil().enableTest(incrementThreshold); } @@ -346,7 +346,16 @@ void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const ClipBase* cli void BakedOpRenderer::renderGlopImpl(const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop) { prepareRender(dirtyBounds, clip); - mRenderState.render(glop, mRenderTarget.orthoMatrix); + // Disable blending if this is the first draw to the main framebuffer, in case app has defined + // transparency where it doesn't make sense - as first draw in opaque window. Note that we only + // apply this improvement when the blend mode is SRC_OVER - other modes (e.g. CLEAR) can be + // valid draws that affect other content (e.g. draw CLEAR, then draw DST_OVER) + bool overrideDisableBlending = !mHasDrawn + && mOpaque + && !mRenderTarget.frameBufferId + && glop.blend.src == GL_ONE + && glop.blend.dst == GL_ONE_MINUS_SRC_ALPHA; + mRenderState.render(glop, mRenderTarget.orthoMatrix, overrideDisableBlending); if (!mRenderTarget.frameBufferId) mHasDrawn = true; } diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index 2fdfcd42a1e1..0700d1fb9f70 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -50,9 +50,9 @@ Caches* Caches::sInstance = nullptr; /////////////////////////////////////////////////////////////////////////////// Caches::Caches(RenderState& renderState) - : gradientCache(mExtensions) + : gradientCache(extensions()) , patchCache(renderState) - , programCache(mExtensions) + , programCache(extensions()) , mRenderState(&renderState) , mInitialized(false) { INIT_LOGD("Creating OpenGL renderer caches"); @@ -80,7 +80,7 @@ bool Caches::init() { } void Caches::initExtensions() { - if (mExtensions.hasDebugMarker()) { + if (extensions().hasDebugMarker()) { eventMark = glInsertEventMarkerEXT; startMark = glPushGroupMarkerEXT; @@ -93,12 +93,12 @@ void Caches::initExtensions() { } void Caches::initConstraints() { - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + maxTextureSize = DeviceInfo::get()->maxTextureSize(); } void Caches::initStaticProperties() { // OpenGL ES 3.0+ specific features - gpuPixelBuffersEnabled = mExtensions.hasPixelBufferObjects() + gpuPixelBuffersEnabled = extensions().hasPixelBufferObjects() && property_get_bool(PROPERTY_ENABLE_GPU_PIXEL_BUFFERS, true); } diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 19063e3768cd..29eddde1e42b 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -16,6 +16,7 @@ #pragma once +#include "DeviceInfo.h" #include "Extensions.h" #include "FboCache.h" #include "GammaFontRenderer.h" @@ -145,10 +146,6 @@ public: // Misc GLint maxTextureSize; -private: - // Declared before gradientCache and programCache which need this to initialize. - // TODO: cleanup / move elsewhere - Extensions mExtensions; public: TextureCache textureCache; RenderBufferCache renderBufferCache; @@ -174,7 +171,7 @@ public: void setProgram(const ProgramDescription& description); void setProgram(Program* program); - const Extensions& extensions() const { return mExtensions; } + const Extensions& extensions() const { return DeviceInfo::get()->extensions(); } Program& program() { return *mProgram; } PixelBufferState& pixelBufferState() { return *mPixelBufferState; } TextureState& textureState() { return *mTextureState; } diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index d180ba51b304..37965daf9a8d 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -16,7 +16,8 @@ #include <DeviceInfo.h> -#include "Extensions.h" +#include <gui/ISurfaceComposer.h> +#include <gui/SurfaceComposerClient.h> #include <thread> #include <mutex> @@ -46,13 +47,22 @@ void DeviceInfo::initialize() { void DeviceInfo::initialize(int maxTextureSize) { std::call_once(sInitializedFlag, [maxTextureSize]() { sDeviceInfo = new DeviceInfo(); + sDeviceInfo->loadDisplayInfo(); sDeviceInfo->mMaxTextureSize = maxTextureSize; }); } void DeviceInfo::load() { + loadDisplayInfo(); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); } +void DeviceInfo::loadDisplayInfo() { + sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay( + ISurfaceComposer::eDisplayIdMain)); + status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &mDisplayInfo); + LOG_ALWAYS_FATAL_IF(status, "Failed to get display info, error %d", status); +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index aff84b02d85a..5bd7b14b156d 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -16,7 +16,10 @@ #ifndef DEVICEINFO_H #define DEVICEINFO_H +#include <ui/DisplayInfo.h> + #include "utils/Macros.h" +#include "Extensions.h" namespace android { namespace uirenderer { @@ -35,14 +38,24 @@ public: static void initialize(int maxTextureSize); int maxTextureSize() const { return mMaxTextureSize; } + const DisplayInfo& displayInfo() const { return mDisplayInfo; } + const Extensions& extensions() const { return mExtensions; } + + static uint32_t multiplyByResolution(uint32_t in) { + auto di = DeviceInfo::get()->displayInfo(); + return di.w * di.h * in; + } private: DeviceInfo() {} ~DeviceInfo() {} void load(); + void loadDisplayInfo(); int mMaxTextureSize; + DisplayInfo mDisplayInfo; + Extensions mExtensions; }; } /* namespace uirenderer */ diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp index b2181b60054f..a39e49f82eb0 100644 --- a/libs/hwui/FboCache.cpp +++ b/libs/hwui/FboCache.cpp @@ -28,7 +28,7 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// FboCache::FboCache() - : mMaxSize(Properties::fboCacheSize) {} + : mMaxSize(0) {} FboCache::~FboCache() { clear(); diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index ee99018fb652..8b0346867cbc 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -32,7 +32,6 @@ #include "utils/Timing.h" #include <algorithm> -#include <cutils/properties.h> #include <RenderScript.h> #include <SkGlyph.h> #include <SkUtils.h> @@ -99,22 +98,23 @@ FontRenderer::FontRenderer(const uint8_t* gammaTable) INIT_LOGD("Creating FontRenderer"); } - mSmallCacheWidth = property_get_int32(PROPERTY_TEXT_SMALL_CACHE_WIDTH, - DEFAULT_TEXT_SMALL_CACHE_WIDTH); - mSmallCacheHeight = property_get_int32(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, - DEFAULT_TEXT_SMALL_CACHE_HEIGHT); + auto deviceInfo = DeviceInfo::get(); + auto displayInfo = deviceInfo->displayInfo(); + int maxTextureSize = deviceInfo->maxTextureSize(); - mLargeCacheWidth = property_get_int32(PROPERTY_TEXT_LARGE_CACHE_WIDTH, - DEFAULT_TEXT_LARGE_CACHE_WIDTH); - mLargeCacheHeight = property_get_int32(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, - DEFAULT_TEXT_LARGE_CACHE_HEIGHT); + // Adjust cache size based on Pixel's desnsity. + constexpr float PIXEL_DENSITY = 2.6; + const float densityRatio = displayInfo.density / PIXEL_DENSITY; - uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize; - - mSmallCacheWidth = std::min(mSmallCacheWidth, maxTextureSize); - mSmallCacheHeight = std::min(mSmallCacheHeight, maxTextureSize); - mLargeCacheWidth = std::min(mLargeCacheWidth, maxTextureSize); - mLargeCacheHeight = std::min(mLargeCacheHeight, maxTextureSize); + // TODO: Most devices are hardcoded with this configuration, does it need to be dynamic? + mSmallCacheWidth = + OffscreenBuffer::computeIdealDimension(std::min(1024, maxTextureSize) * densityRatio); + mSmallCacheHeight = + OffscreenBuffer::computeIdealDimension(std::min(1024, maxTextureSize) * densityRatio); + mLargeCacheWidth = + OffscreenBuffer::computeIdealDimension(std::min(2048, maxTextureSize) * densityRatio); + mLargeCacheHeight = + OffscreenBuffer::computeIdealDimension(std::min(1024, maxTextureSize) * densityRatio); if (sLogFontRendererCreate) { INIT_LOGD(" Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i", diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index d4d0c997be11..20262349dda4 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -20,6 +20,7 @@ #include "Debug.h" #include "GradientCache.h" #include "Properties.h" +#include "DeviceInfo.h" #include <cutils/properties.h> @@ -62,14 +63,14 @@ int GradientCacheEntry::compare(const GradientCacheEntry& lhs, const GradientCac // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -GradientCache::GradientCache(Extensions& extensions) +GradientCache::GradientCache(const Extensions& extensions) : mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity) , mSize(0) - , mMaxSize(Properties::gradientCacheSize) + , mMaxSize(MB(1)) , mUseFloatTexture(extensions.hasFloatTextures()) , mHasNpot(extensions.hasNPot()) , mHasLinearBlending(extensions.hasLinearBlending()) { - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); + mMaxTextureSize = DeviceInfo::get()->maxTextureSize(); mCache.setOnEntryRemovedListener(this); } diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h index f299a40e994f..d95589c514eb 100644 --- a/libs/hwui/GradientCache.h +++ b/libs/hwui/GradientCache.h @@ -105,7 +105,7 @@ inline hash_t hash_type(const GradientCacheEntry& entry) { */ class GradientCache: public OnEntryRemoved<GradientCacheEntry, Texture*> { public: - explicit GradientCache(Extensions& extensions); + explicit GradientCache(const Extensions& extensions); ~GradientCache(); /** diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index 028d9f756fb7..9d1182819444 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -34,14 +34,6 @@ namespace android { namespace uirenderer { -static const char* JANK_TYPE_NAMES[] = { - "Missed Vsync", - "High input latency", - "Slow UI thread", - "Slow bitmap uploads", - "Slow issue draw commands", -}; - struct Comparison { FrameInfoIndex start; FrameInfoIndex end; @@ -68,70 +60,13 @@ static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10); */ static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas; -// The bucketing algorithm controls so to speak -// If a frame is <= to this it goes in bucket 0 -static const uint32_t kBucketMinThreshold = 5; -// If a frame is > this, start counting in increments of 2ms -static const uint32_t kBucket2msIntervals = 32; -// If a frame is > this, start counting in increments of 4ms -static const uint32_t kBucket4msIntervals = 48; - // For testing purposes to try and eliminate test infra overhead we will // consider any unknown delay of frame start as part of the test infrastructure // and filter it out of the frame profile data static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync; -// The interval of the slow frame histogram -static const uint32_t kSlowFrameBucketIntervalMs = 50; -// The start point of the slow frame bucket in ms -static const uint32_t kSlowFrameBucketStartMs = 150; - -// This will be called every frame, performance sensitive -// Uses bit twiddling to avoid branching while achieving the packing desired -static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) { - uint32_t index = static_cast<uint32_t>(ns2ms(frameTime)); - // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result - // of negating 1 (twos compliment, yaay) else mask will be 0 - uint32_t mask = -(index > kBucketMinThreshold); - // If index > threshold, this will essentially perform: - // amountAboveThreshold = index - threshold; - // index = threshold + (amountAboveThreshold / 2) - // However if index is <= this will do nothing. It will underflow, do - // a right shift by 0 (no-op), then overflow back to the original value - index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals)) - + kBucket4msIntervals; - index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals)) - + kBucket2msIntervals; - // If index was < minThreshold at the start of all this it's going to - // be a pretty garbage value right now. However, mask is 0 so we'll end - // up with the desired result of 0. - index = (index - kBucketMinThreshold) & mask; - return index; -} - -// Only called when dumping stats, less performance sensitive -int32_t JankTracker::frameTimeForFrameCountIndex(uint32_t index) { - index = index + kBucketMinThreshold; - if (index > kBucket2msIntervals) { - index += (index - kBucket2msIntervals); - } - if (index > kBucket4msIntervals) { - // This works because it was already doubled by the above if - // 1 is added to shift slightly more towards the middle of the bucket - index += (index - kBucket4msIntervals) + 1; - } - return index; -} - -int32_t JankTracker::frameTimeForSlowFrameCountIndex(uint32_t index) { - return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; -} - -JankTracker::JankTracker(const DisplayInfo& displayInfo) { - // By default this will use malloc memory. It may be moved later to ashmem - // if there is shared space for it and a request comes in to do that. - mData = new ProfileData; - reset(); +JankTracker::JankTracker(ProfileDataContainer* globalData, const DisplayInfo& displayInfo) { + mGlobalData = globalData; nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / displayInfo.fps); #if USE_HWC2 nsecs_t sfOffset = frameIntervalNanos - (displayInfo.presentationDeadline - 1_ms); @@ -151,82 +86,6 @@ JankTracker::JankTracker(const DisplayInfo& displayInfo) { setFrameInterval(frameIntervalNanos); } -JankTracker::~JankTracker() { - freeData(); -} - -void JankTracker::freeData() { - if (mIsMapped) { - munmap(mData, sizeof(ProfileData)); - } else { - delete mData; - } - mIsMapped = false; - mData = nullptr; -} - -void JankTracker::rotateStorage() { - // If we are mapped we want to stop using the ashmem backend and switch to malloc - // We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed - // If we aren't sitting on top of ashmem then just do a reset() as it's functionally - // equivalent do a free, malloc, reset. - if (mIsMapped) { - freeData(); - mData = new ProfileData; - } - reset(); -} - -void JankTracker::switchStorageToAshmem(int ashmemfd) { - int regionSize = ashmem_get_size_region(ashmemfd); - if (regionSize < 0) { - int err = errno; - ALOGW("Failed to get ashmem region size from fd %d, err %d %s", ashmemfd, err, strerror(err)); - return; - } - if (regionSize < static_cast<int>(sizeof(ProfileData))) { - ALOGW("Ashmem region is too small! Received %d, required %u", - regionSize, static_cast<unsigned int>(sizeof(ProfileData))); - return; - } - ProfileData* newData = reinterpret_cast<ProfileData*>( - mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE, - MAP_SHARED, ashmemfd, 0)); - if (newData == MAP_FAILED) { - int err = errno; - ALOGW("Failed to move profile data to ashmem fd %d, error = %d", - ashmemfd, err); - return; - } - - // The new buffer may have historical data that we want to build on top of - // But let's make sure we don't overflow Just In Case - uint32_t divider = 0; - if (newData->totalFrameCount > (1 << 24)) { - divider = 4; - } - for (size_t i = 0; i < mData->jankTypeCounts.size(); i++) { - newData->jankTypeCounts[i] >>= divider; - newData->jankTypeCounts[i] += mData->jankTypeCounts[i]; - } - for (size_t i = 0; i < mData->frameCounts.size(); i++) { - newData->frameCounts[i] >>= divider; - newData->frameCounts[i] += mData->frameCounts[i]; - } - newData->jankFrameCount >>= divider; - newData->jankFrameCount += mData->jankFrameCount; - newData->totalFrameCount >>= divider; - newData->totalFrameCount += mData->totalFrameCount; - if (newData->statStartTime > mData->statStartTime - || newData->statStartTime == 0) { - newData->statStartTime = mData->statStartTime; - } - - freeData(); - mData = newData; - mIsMapped = true; -} - void JankTracker::setFrameInterval(nsecs_t frameInterval) { mFrameInterval = frameInterval; mThresholds[kMissedVsync] = 1; @@ -250,8 +109,7 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) { } -void JankTracker::addFrame(const FrameInfo& frame) { - mData->totalFrameCount++; +void JankTracker::finishFrame(const FrameInfo& frame) { // Fast-path for jank-free frames int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted); if (mDequeueTimeForgiveness @@ -271,11 +129,11 @@ void JankTracker::addFrame(const FrameInfo& frame) { } } LOG_ALWAYS_FATAL_IF(totalDuration <= 0, "Impossible totalDuration %" PRId64, totalDuration); - uint32_t framebucket = frameCountIndexForFrameTime(totalDuration); - LOG_ALWAYS_FATAL_IF(framebucket < 0, "framebucket < 0 (%u)", framebucket); + mData->reportFrame(totalDuration); + (*mGlobalData)->reportFrame(totalDuration); + // Keep the fast path as fast as possible. if (CC_LIKELY(totalDuration < mFrameInterval)) { - mData->frameCounts[framebucket]++; return; } @@ -284,22 +142,14 @@ void JankTracker::addFrame(const FrameInfo& frame) { return; } - if (framebucket <= mData->frameCounts.size()) { - mData->frameCounts[framebucket]++; - } else { - framebucket = (ns2ms(totalDuration) - kSlowFrameBucketStartMs) - / kSlowFrameBucketIntervalMs; - framebucket = std::min(framebucket, - static_cast<uint32_t>(mData->slowFrameCounts.size() - 1)); - mData->slowFrameCounts[framebucket]++; - } - - mData->jankFrameCount++; + mData->reportJank(); + (*mGlobalData)->reportJank(); for (int i = 0; i < NUM_BUCKETS; i++) { int64_t delta = frame.duration(COMPARISONS[i].start, COMPARISONS[i].end); if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) { - mData->jankTypeCounts[i]++; + mData->reportJankType((JankType) i); + (*mGlobalData)->reportJankType((JankType) i); } } } @@ -320,58 +170,39 @@ void JankTracker::dumpData(int fd, const ProfileDataDescription* description, co if (sFrameStart != FrameInfoIndex::IntendedVsync) { dprintf(fd, "\nNote: Data has been filtered!"); } - dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime); - dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount); - dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount, - (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f); - dprintf(fd, "\n50th percentile: %ums", findPercentile(data, 50)); - dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90)); - dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95)); - dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99)); - for (int i = 0; i < NUM_BUCKETS; i++) { - dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]); - } - dprintf(fd, "\nHISTOGRAM:"); - for (size_t i = 0; i < data->frameCounts.size(); i++) { - dprintf(fd, " %ums=%u", frameTimeForFrameCountIndex(i), - data->frameCounts[i]); - } - for (size_t i = 0; i < data->slowFrameCounts.size(); i++) { - dprintf(fd, " %ums=%u", frameTimeForSlowFrameCountIndex(i), - data->slowFrameCounts[i]); - } + data->dump(fd); dprintf(fd, "\n"); } +void JankTracker::dumpFrames(int fd) { + FILE* file = fdopen(fd, "a"); + fprintf(file, "\n\n---PROFILEDATA---\n"); + for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) { + fprintf(file, "%s", FrameInfoNames[i].c_str()); + fprintf(file, ","); + } + for (size_t i = 0; i < mFrames.size(); i++) { + FrameInfo& frame = mFrames[i]; + if (frame[FrameInfoIndex::SyncStart] == 0) { + continue; + } + fprintf(file, "\n"); + for (int i = 0; i < static_cast<int>(FrameInfoIndex::NumIndexes); i++) { + fprintf(file, "%" PRId64 ",", frame[i]); + } + } + fprintf(file, "\n---PROFILEDATA---\n\n"); + fflush(file); +} + void JankTracker::reset() { - mData->jankTypeCounts.fill(0); - mData->frameCounts.fill(0); - mData->slowFrameCounts.fill(0); - mData->totalFrameCount = 0; - mData->jankFrameCount = 0; - mData->statStartTime = systemTime(CLOCK_MONOTONIC); + mFrames.clear(); + mData->reset(); + (*mGlobalData)->reset(); sFrameStart = Properties::filterOutTestOverhead ? FrameInfoIndex::HandleInputStart : FrameInfoIndex::IntendedVsync; } -uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) { - int pos = percentile * data->totalFrameCount / 100; - int remaining = data->totalFrameCount - pos; - for (int i = data->slowFrameCounts.size() - 1; i >= 0; i--) { - remaining -= data->slowFrameCounts[i]; - if (remaining <= 0) { - return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; - } - } - for (int i = data->frameCounts.size() - 1; i >= 0; i--) { - remaining -= data->frameCounts[i]; - if (remaining <= 0) { - return frameTimeForFrameCountIndex(i); - } - } - return 0; -} - } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h index 6ff5d890eaf7..e56c0791d3a4 100644 --- a/libs/hwui/JankTracker.h +++ b/libs/hwui/JankTracker.h @@ -17,6 +17,8 @@ #define JANKTRACKER_H_ #include "FrameInfo.h" +#include "ProfileData.h" +#include "ProfileDataContainer.h" #include "renderthread/TimeLord.h" #include "utils/RingBuffer.h" @@ -29,31 +31,6 @@ namespace android { namespace uirenderer { -enum JankType { - kMissedVsync = 0, - kHighInputLatency, - kSlowUI, - kSlowSync, - kSlowRT, - - // must be last - NUM_BUCKETS, -}; - -// Try to keep as small as possible, should match ASHMEM_SIZE in -// GraphicsStatsService.java -struct ProfileData { - std::array<uint32_t, NUM_BUCKETS> jankTypeCounts; - // See comments on kBucket* constants for what this holds - std::array<uint32_t, 57> frameCounts; - // Holds a histogram of frame times in 50ms increments from 150ms to 5s - std::array<uint16_t, 97> slowFrameCounts; - - uint32_t totalFrameCount; - uint32_t jankFrameCount; - nsecs_t statStartTime; -}; - enum class JankTrackerType { // The default, means there's no description set Generic, @@ -72,31 +49,27 @@ struct ProfileDataDescription { // TODO: Replace DrawProfiler with this class JankTracker { public: - explicit JankTracker(const DisplayInfo& displayInfo); - ~JankTracker(); + explicit JankTracker(ProfileDataContainer* globalData, const DisplayInfo& displayInfo); void setDescription(JankTrackerType type, const std::string&& name) { mDescription.type = type; mDescription.name = name; } - void addFrame(const FrameInfo& frame); + FrameInfo* startFrame() { return &mFrames.next(); } + void finishFrame(const FrameInfo& frame); - void dump(int fd) { dumpData(fd, &mDescription, mData); } + void dumpStats(int fd) { dumpData(fd, &mDescription, mData.get()); } + void dumpFrames(int fd); void reset(); - void rotateStorage(); - void switchStorageToAshmem(int ashmemfd); - - uint32_t findPercentile(int p) { return findPercentile(mData, p); } - static int32_t frameTimeForFrameCountIndex(uint32_t index); - static int32_t frameTimeForSlowFrameCountIndex(uint32_t index); + // Exposed for FrameInfoVisualizer + // TODO: Figure out a better way to handle this + RingBuffer<FrameInfo, 120>& frames() { return mFrames; } private: - void freeData(); void setFrameInterval(nsecs_t frameIntervalNanos); - static uint32_t findPercentile(const ProfileData* data, int p); static void dumpData(int fd, const ProfileDataDescription* description, const ProfileData* data); std::array<int64_t, NUM_BUCKETS> mThresholds; @@ -109,9 +82,12 @@ private: // This is only used if we are in pipelined mode and are using HWC2, // otherwise it's 0. nsecs_t mDequeueTimeForgiveness = 0; - ProfileData* mData; - bool mIsMapped = false; + ProfileDataContainer mData; + ProfileDataContainer* mGlobalData; ProfileDataDescription mDescription; + + // Ring buffer large enough for 2 seconds worth of frames + RingBuffer<FrameInfo, 120> mFrames; }; } /* namespace uirenderer */ diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp index 19d5d9d2250e..2687410897ac 100644 --- a/libs/hwui/OpenGLReadback.cpp +++ b/libs/hwui/OpenGLReadback.cpp @@ -85,11 +85,6 @@ CopyResult OpenGLReadback::copyGraphicBufferInto(GraphicBuffer* graphicBuffer, uint32_t width = graphicBuffer->getWidth(); uint32_t height = graphicBuffer->getHeight(); - // If this is a 90 or 270 degree rotation we need to swap width/height - // This is a fuzzy way of checking that. - if (texTransform[Matrix4::kSkewX] >= 0.5f || texTransform[Matrix4::kSkewX] <= -0.5f) { - std::swap(width, height); - } CopyResult copyResult = copyImageInto(sourceImage, texTransform, width, height, srcRect, bitmap); @@ -233,11 +228,12 @@ inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState, .build(); Matrix4 ortho; ortho.loadOrtho(destWidth, destHeight); - renderState.render(glop, ortho); + renderState.render(glop, ortho, false); // TODO: We should convert to linear space when the target is RGBA16F glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, type, bitmap->getPixels()); + bitmap->notifyPixelsChanged(); } // Cleanup @@ -253,6 +249,12 @@ CopyResult OpenGLReadbackImpl::copyImageInto(EGLImageKHR eglImage, const Matrix4& imgTransform, int imgWidth, int imgHeight, const Rect& srcRect, SkBitmap* bitmap) { + // If this is a 90 or 270 degree rotation we need to swap width/height + // This is a fuzzy way of checking that. + if (imgTransform[Matrix4::kSkewX] >= 0.5f || imgTransform[Matrix4::kSkewX] <= -0.5f) { + std::swap(imgWidth, imgHeight); + } + Caches& caches = Caches::getInstance(); GLuint sourceTexId; // Create a 2D texture to sample from the EGLImage @@ -278,6 +280,11 @@ CopyResult OpenGLReadbackImpl::copyImageInto(EGLImageKHR eglImage, bool OpenGLReadbackImpl::copyLayerInto(renderthread::RenderThread& renderThread, GlLayer& layer, SkBitmap* bitmap) { + if (!layer.isRenderable()) { + // layer has never been updated by DeferredLayerUpdater, abort copy + return false; + } + return CopyResult::Success == copyTextureInto(Caches::getInstance(), renderThread.renderState(), layer.getTexture(), layer.getTexTransform(), Rect(), bitmap); diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp index 983c17e92266..78c7eb9ad809 100644 --- a/libs/hwui/PatchCache.cpp +++ b/libs/hwui/PatchCache.cpp @@ -32,7 +32,7 @@ namespace uirenderer { PatchCache::PatchCache(RenderState& renderState) : mRenderState(renderState) - , mMaxSize(Properties::patchCacheSize) + , mMaxSize(KB(128)) , mSize(0) , mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity) , mMeshBuffer(0) diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index cc96de71df82..8d4ae1b6622a 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -38,6 +38,8 @@ namespace android { namespace uirenderer { +static constexpr size_t PATH_CACHE_COUNT_LIMIT = 256; + template <class T> static bool compareWidthHeight(const T& lhs, const T& rhs) { return (lhs.mWidth == rhs.mWidth) && (lhs.mHeight == rhs.mHeight); @@ -179,13 +181,9 @@ static sk_sp<Bitmap> drawPath(const SkPath* path, const SkPaint* paint, PathText PathCache::PathCache() : mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity) , mSize(0) - , mMaxSize(Properties::pathCacheSize) { + , mMaxSize(DeviceInfo::multiplyByResolution(4)) { mCache.setOnEntryRemovedListener(this); - - GLint maxTextureSize; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); - mMaxTextureSize = maxTextureSize; - + mMaxTextureSize = DeviceInfo::get()->maxTextureSize(); mDebugEnabled = Properties::debugLevel & kDebugCaches; } @@ -259,12 +257,7 @@ void PathCache::purgeCache(uint32_t width, uint32_t height) { } void PathCache::trim() { - // 25 is just an arbitrary lower bound to ensure we aren't in weird edge cases - // of things like a cap of 0 or 1 as that's going to break things. - // It does not represent a reasonable minimum value - static_assert(DEFAULT_PATH_TEXTURE_CAP > 25, "Path cache texture cap is too small"); - - while (mSize > mMaxSize || mCache.size() > DEFAULT_PATH_TEXTURE_CAP) { + while (mSize > mMaxSize || mCache.size() > PATH_CACHE_COUNT_LIMIT) { LOG_ALWAYS_FATAL_IF(!mCache.size(), "Inconsistent mSize! Ran out of items to remove!" " mSize = %u, mMaxSize = %u", mSize, mMaxSize); mCache.removeOldest(); diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp new file mode 100644 index 000000000000..a295c5debc67 --- /dev/null +++ b/libs/hwui/ProfileData.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2017 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 "ProfileData.h" + +#include <cinttypes> + +namespace android { +namespace uirenderer { + +static const char* JANK_TYPE_NAMES[] = { + "Missed Vsync", + "High input latency", + "Slow UI thread", + "Slow bitmap uploads", + "Slow issue draw commands", +}; + +// The bucketing algorithm controls so to speak +// If a frame is <= to this it goes in bucket 0 +static const uint32_t kBucketMinThreshold = 5; +// If a frame is > this, start counting in increments of 2ms +static const uint32_t kBucket2msIntervals = 32; +// If a frame is > this, start counting in increments of 4ms +static const uint32_t kBucket4msIntervals = 48; + +// The interval of the slow frame histogram +static const uint32_t kSlowFrameBucketIntervalMs = 50; +// The start point of the slow frame bucket in ms +static const uint32_t kSlowFrameBucketStartMs = 150; + +// This will be called every frame, performance sensitive +// Uses bit twiddling to avoid branching while achieving the packing desired +static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) { + uint32_t index = static_cast<uint32_t>(ns2ms(frameTime)); + // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result + // of negating 1 (twos compliment, yaay) else mask will be 0 + uint32_t mask = -(index > kBucketMinThreshold); + // If index > threshold, this will essentially perform: + // amountAboveThreshold = index - threshold; + // index = threshold + (amountAboveThreshold / 2) + // However if index is <= this will do nothing. It will underflow, do + // a right shift by 0 (no-op), then overflow back to the original value + index = ((index - kBucket4msIntervals) >> (index > kBucket4msIntervals)) + + kBucket4msIntervals; + index = ((index - kBucket2msIntervals) >> (index > kBucket2msIntervals)) + + kBucket2msIntervals; + // If index was < minThreshold at the start of all this it's going to + // be a pretty garbage value right now. However, mask is 0 so we'll end + // up with the desired result of 0. + index = (index - kBucketMinThreshold) & mask; + return index; +} + +// Only called when dumping stats, less performance sensitive +uint32_t ProfileData::frameTimeForFrameCountIndex(uint32_t index) { + index = index + kBucketMinThreshold; + if (index > kBucket2msIntervals) { + index += (index - kBucket2msIntervals); + } + if (index > kBucket4msIntervals) { + // This works because it was already doubled by the above if + // 1 is added to shift slightly more towards the middle of the bucket + index += (index - kBucket4msIntervals) + 1; + } + return index; +} + +uint32_t ProfileData::frameTimeForSlowFrameCountIndex(uint32_t index) { + return (index * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; +} + +void ProfileData::mergeWith(const ProfileData& other) { + // Make sure we don't overflow Just In Case + uint32_t divider = 0; + if (mTotalFrameCount > (1 << 24)) { + divider = 4; + } + for (size_t i = 0; i < other.mJankTypeCounts.size(); i++) { + mJankTypeCounts[i] >>= divider; + mJankTypeCounts[i] += other.mJankTypeCounts[i]; + } + for (size_t i = 0; i < other.mFrameCounts.size(); i++) { + mFrameCounts[i] >>= divider; + mFrameCounts[i] += other.mFrameCounts[i]; + } + mJankFrameCount >>= divider; + mJankFrameCount += other.mJankFrameCount; + mTotalFrameCount >>= divider; + mTotalFrameCount += other.mTotalFrameCount; + if (mStatStartTime > other.mStatStartTime + || mStatStartTime == 0) { + mStatStartTime = other.mStatStartTime; + } +} + +void ProfileData::dump(int fd) const { + dprintf(fd, "\nStats since: %" PRIu64 "ns", mStatStartTime); + dprintf(fd, "\nTotal frames rendered: %u", mTotalFrameCount); + dprintf(fd, "\nJanky frames: %u (%.2f%%)", mJankFrameCount, + (float) mJankFrameCount / (float) mTotalFrameCount * 100.0f); + dprintf(fd, "\n50th percentile: %ums", findPercentile(50)); + dprintf(fd, "\n90th percentile: %ums", findPercentile(90)); + dprintf(fd, "\n95th percentile: %ums", findPercentile(95)); + dprintf(fd, "\n99th percentile: %ums", findPercentile(99)); + for (int i = 0; i < NUM_BUCKETS; i++) { + dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], mJankTypeCounts[i]); + } + dprintf(fd, "\nHISTOGRAM:"); + histogramForEach([fd](HistogramEntry entry) { + dprintf(fd, " %ums=%u", entry.renderTimeMs, entry.frameCount); + }); +} + +uint32_t ProfileData::findPercentile(int percentile) const { + int pos = percentile * mTotalFrameCount / 100; + int remaining = mTotalFrameCount - pos; + for (int i = mSlowFrameCounts.size() - 1; i >= 0; i--) { + remaining -= mSlowFrameCounts[i]; + if (remaining <= 0) { + return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; + } + } + for (int i = mFrameCounts.size() - 1; i >= 0; i--) { + remaining -= mFrameCounts[i]; + if (remaining <= 0) { + return frameTimeForFrameCountIndex(i); + } + } + return 0; +} + +void ProfileData::reset() { + mJankTypeCounts.fill(0); + mFrameCounts.fill(0); + mSlowFrameCounts.fill(0); + mTotalFrameCount = 0; + mJankFrameCount = 0; + mStatStartTime = systemTime(CLOCK_MONOTONIC); +} + +void ProfileData::reportFrame(int64_t duration) { + mTotalFrameCount++; + uint32_t framebucket = frameCountIndexForFrameTime(duration); + if (framebucket <= mFrameCounts.size()) { + mFrameCounts[framebucket]++; + } else { + framebucket = (ns2ms(duration) - kSlowFrameBucketStartMs) / kSlowFrameBucketIntervalMs; + framebucket = std::min(framebucket, static_cast<uint32_t>(mSlowFrameCounts.size() - 1)); + mSlowFrameCounts[framebucket]++; + } +} + +void ProfileData::histogramForEach(const std::function<void(HistogramEntry)>& callback) const { + for (size_t i = 0; i < mFrameCounts.size(); i++) { + callback(HistogramEntry{frameTimeForFrameCountIndex(i), mFrameCounts[i]}); + } + for (size_t i = 0; i < mSlowFrameCounts.size(); i++) { + callback(HistogramEntry{frameTimeForSlowFrameCountIndex(i), mSlowFrameCounts[i]}); + } +} + +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h new file mode 100644 index 000000000000..d53ee29511f0 --- /dev/null +++ b/libs/hwui/ProfileData.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 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 "utils/Macros.h" + +#include <utils/Timers.h> + +#include <array> +#include <functional> +#include <tuple> + +namespace android { +namespace uirenderer { + +enum JankType { + kMissedVsync = 0, + kHighInputLatency, + kSlowUI, + kSlowSync, + kSlowRT, + + // must be last + NUM_BUCKETS, +}; + +// For testing +class MockProfileData; + +// Try to keep as small as possible, should match ASHMEM_SIZE in +// GraphicsStatsService.java +class ProfileData { + PREVENT_COPY_AND_ASSIGN(ProfileData); + +public: + ProfileData() { reset(); } + + void reset(); + void mergeWith(const ProfileData& other); + void dump(int fd) const; + uint32_t findPercentile(int percentile) const; + + void reportFrame(int64_t duration); + void reportJank() { mJankFrameCount++; } + void reportJankType(JankType type) { mJankTypeCounts[static_cast<int>(type)]++; } + + uint32_t totalFrameCount() const { return mTotalFrameCount; } + uint32_t jankFrameCount() const { return mJankFrameCount; } + nsecs_t statsStartTime() const { return mStatStartTime; } + uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; } + + struct HistogramEntry { + uint32_t renderTimeMs; + uint32_t frameCount; + }; + void histogramForEach(const std::function<void(HistogramEntry)>& callback) const; + + constexpr static int HistogramSize() { + return std::tuple_size<decltype(ProfileData::mFrameCounts)>::value + + std::tuple_size<decltype(ProfileData::mSlowFrameCounts)>::value; + } + + // Visible for testing + static uint32_t frameTimeForFrameCountIndex(uint32_t index); + static uint32_t frameTimeForSlowFrameCountIndex(uint32_t index); + +private: + // Open our guts up to unit tests + friend class MockProfileData; + + std::array <uint32_t, NUM_BUCKETS> mJankTypeCounts; + // See comments on kBucket* constants for what this holds + std::array<uint32_t, 57> mFrameCounts; + // Holds a histogram of frame times in 50ms increments from 150ms to 5s + std::array<uint16_t, 97> mSlowFrameCounts; + + uint32_t mTotalFrameCount; + uint32_t mJankFrameCount; + nsecs_t mStatStartTime; +}; + +// For testing +class MockProfileData : public ProfileData { +public: + std::array<uint32_t, NUM_BUCKETS>& editJankTypeCounts() { return mJankTypeCounts; } + std::array<uint32_t, 57>& editFrameCounts() { return mFrameCounts; } + std::array<uint16_t, 97>& editSlowFrameCounts() { return mSlowFrameCounts; } + uint32_t& editTotalFrameCount() { return mTotalFrameCount; } + uint32_t& editJankFrameCount() { return mJankFrameCount; } + nsecs_t& editStatStartTime() { return mStatStartTime; } +}; + +} /* namespace uirenderer */ +} /* namespace android */ + diff --git a/libs/hwui/ProfileDataContainer.cpp b/libs/hwui/ProfileDataContainer.cpp new file mode 100644 index 000000000000..cbf3eb390f53 --- /dev/null +++ b/libs/hwui/ProfileDataContainer.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 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 "ProfileDataContainer.h" + +#include <log/log.h> +#include <cutils/ashmem.h> + +#include <sys/mman.h> + +namespace android { +namespace uirenderer { + +void ProfileDataContainer::freeData() { + if (mIsMapped) { + munmap(mData, sizeof(ProfileData)); + } else { + delete mData; + } + mIsMapped = false; + mData = nullptr; +} + +void ProfileDataContainer::rotateStorage() { + // If we are mapped we want to stop using the ashmem backend and switch to malloc + // We are expecting a switchStorageToAshmem call to follow this, but it's not guaranteed + // If we aren't sitting on top of ashmem then just do a reset() as it's functionally + // equivalent do a free, malloc, reset. + if (mIsMapped) { + freeData(); + mData = new ProfileData; + } + mData->reset(); +} + +void ProfileDataContainer::switchStorageToAshmem(int ashmemfd) { + int regionSize = ashmem_get_size_region(ashmemfd); + if (regionSize < 0) { + int err = errno; + ALOGW("Failed to get ashmem region size from fd %d, err %d %s", ashmemfd, err, strerror(err)); + return; + } + if (regionSize < static_cast<int>(sizeof(ProfileData))) { + ALOGW("Ashmem region is too small! Received %d, required %u", + regionSize, static_cast<unsigned int>(sizeof(ProfileData))); + return; + } + ProfileData* newData = reinterpret_cast<ProfileData*>( + mmap(NULL, sizeof(ProfileData), PROT_READ | PROT_WRITE, + MAP_SHARED, ashmemfd, 0)); + if (newData == MAP_FAILED) { + int err = errno; + ALOGW("Failed to move profile data to ashmem fd %d, error = %d", + ashmemfd, err); + return; + } + + newData->mergeWith(*mData); + freeData(); + mData = newData; + mIsMapped = true; +} + +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/ProfileDataContainer.h b/libs/hwui/ProfileDataContainer.h new file mode 100644 index 000000000000..d2de24188c7c --- /dev/null +++ b/libs/hwui/ProfileDataContainer.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 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 "ProfileData.h" +#include "utils/Macros.h" + +namespace android { +namespace uirenderer { + +class ProfileDataContainer { + PREVENT_COPY_AND_ASSIGN(ProfileDataContainer); +public: + explicit ProfileDataContainer() {} + + ~ProfileDataContainer() { freeData(); } + + void rotateStorage(); + void switchStorageToAshmem(int ashmemfd); + + ProfileData* get() { return mData; } + ProfileData* operator->() { return mData; } + +private: + void freeData(); + + // By default this will use malloc memory. It may be moved later to ashmem + // if there is shared space for it and a request comes in to do that. + ProfileData* mData = new ProfileData; + bool mIsMapped = false; +}; + +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index 8cc0aa7b414c..b767046f1a4f 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -505,7 +505,7 @@ const char* gBlendOps[18] = { // Constructors/destructors /////////////////////////////////////////////////////////////////////////////// -ProgramCache::ProgramCache(Extensions& extensions) +ProgramCache::ProgramCache(const Extensions& extensions) : mHasES3(extensions.getMajorGlVersion() >= 3) , mHasLinearBlending(extensions.hasLinearBlending()) { } diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h index cedd854bb48b..ee76f22f35d4 100644 --- a/libs/hwui/ProgramCache.h +++ b/libs/hwui/ProgramCache.h @@ -40,7 +40,7 @@ namespace uirenderer { */ class ProgramCache { public: - explicit ProgramCache(Extensions& extensions); + explicit ProgramCache(const Extensions& extensions); ~ProgramCache(); Program* get(const ProgramDescription& description); diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index b5872485b136..acc75393ebcf 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -16,6 +16,7 @@ #include "Properties.h" #include "Debug.h" +#include "DeviceInfo.h" #include <algorithm> #include <cstdlib> @@ -36,20 +37,6 @@ bool Properties::skipEmptyFrames = true; bool Properties::useBufferAge = true; bool Properties::enablePartialUpdates = true; -float Properties::textGamma = DEFAULT_TEXT_GAMMA; - -int Properties::fboCacheSize = DEFAULT_FBO_CACHE_SIZE; -int Properties::gradientCacheSize = MB(DEFAULT_GRADIENT_CACHE_SIZE); -int Properties::layerPoolSize = MB(DEFAULT_LAYER_CACHE_SIZE); -int Properties::patchCacheSize = KB(DEFAULT_PATCH_CACHE_SIZE); -int Properties::pathCacheSize = MB(DEFAULT_PATH_CACHE_SIZE); -int Properties::renderBufferCacheSize = MB(DEFAULT_RENDER_BUFFER_CACHE_SIZE); -int Properties::tessellationCacheSize = MB(DEFAULT_VERTEX_CACHE_SIZE); -int Properties::textDropShadowCacheSize = MB(DEFAULT_DROP_SHADOW_CACHE_SIZE); -int Properties::textureCacheSize = MB(DEFAULT_TEXTURE_CACHE_SIZE); - -float Properties::textureCacheFlushRate = DEFAULT_TEXTURE_CACHE_FLUSH_RATE; - DebugLevel Properties::debugLevel = kDebugDisabled; OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default; StencilClipDebug Properties::debugStencilClip = StencilClipDebug::Hide; @@ -80,15 +67,6 @@ static int property_get_int(const char* key, int defaultValue) { return defaultValue; } -static float property_get_float(const char* key, float defaultValue) { - char buf[PROPERTY_VALUE_MAX] = {'\0',}; - - if (property_get(key, buf, "") > 0) { - return atof(buf); - } - return defaultValue; -} - bool Properties::load() { char property[PROPERTY_VALUE_MAX]; bool prevDebugLayersUpdates = debugLayersUpdates; @@ -147,20 +125,6 @@ bool Properties::load() { useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true); enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true); - textGamma = property_get_float(PROPERTY_TEXT_GAMMA, DEFAULT_TEXT_GAMMA); - - fboCacheSize = property_get_int(PROPERTY_FBO_CACHE_SIZE, DEFAULT_FBO_CACHE_SIZE); - gradientCacheSize = MB(property_get_float(PROPERTY_GRADIENT_CACHE_SIZE, DEFAULT_GRADIENT_CACHE_SIZE)); - layerPoolSize = MB(property_get_float(PROPERTY_LAYER_CACHE_SIZE, DEFAULT_LAYER_CACHE_SIZE)); - patchCacheSize = KB(property_get_float(PROPERTY_PATCH_CACHE_SIZE, DEFAULT_PATCH_CACHE_SIZE)); - pathCacheSize = MB(property_get_float(PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE)); - renderBufferCacheSize = MB(property_get_float(PROPERTY_RENDER_BUFFER_CACHE_SIZE, DEFAULT_RENDER_BUFFER_CACHE_SIZE)); - tessellationCacheSize = MB(property_get_float(PROPERTY_VERTEX_CACHE_SIZE, DEFAULT_VERTEX_CACHE_SIZE)); - textDropShadowCacheSize = MB(property_get_float(PROPERTY_DROP_SHADOW_CACHE_SIZE, DEFAULT_DROP_SHADOW_CACHE_SIZE)); - textureCacheSize = MB(property_get_float(PROPERTY_TEXTURE_CACHE_SIZE, DEFAULT_TEXTURE_CACHE_SIZE)); - textureCacheFlushRate = std::max(0.0f, std::min(1.0f, - property_get_float(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, DEFAULT_TEXTURE_CACHE_FLUSH_RATE))); - filterOutTestOverhead = property_get_bool(PROPERTY_FILTER_TEST_OVERHEAD, false); return (prevDebugLayersUpdates != debugLayersUpdates) diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 91b4a2d440e2..47ae9e912127 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -153,79 +153,16 @@ enum DebugLevel { #define PROPERTY_FILTER_TEST_OVERHEAD "debug.hwui.filter_test_overhead" /** - * Allows to set rendering pipeline mode to OpenGL (default), Skia OpenGL - * or Vulkan. - */ -#define PROPERTY_RENDERER "debug.hwui.renderer" - -/////////////////////////////////////////////////////////////////////////////// -// Runtime configuration properties -/////////////////////////////////////////////////////////////////////////////// - -/** - * Used to enable/disable scissor optimization. The accepted values are - * "true" and "false". The default value is "false". - * - * When scissor optimization is enabled, libhwui will attempt to - * minimize the use of scissor by selectively enabling and disabling the - * GL scissor test. - * When the optimization is disabled, OpenGLRenderer will keep the GL - * scissor test enabled and change the scissor rect as needed. - * Some GPUs (for instance the SGX 540) perform better when changing - * the scissor rect often than when enabling/disabling the scissor test - * often. - */ -#define PROPERTY_DISABLE_SCISSOR_OPTIMIZATION "ro.hwui.disable_scissor_opt" - -/** * Indicates whether PBOs can be used to back pixel buffers. * Accepted values are "true" and "false". Default is true. */ -#define PROPERTY_ENABLE_GPU_PIXEL_BUFFERS "ro.hwui.use_gpu_pixel_buffers" - -// These properties are defined in mega-bytes -#define PROPERTY_TEXTURE_CACHE_SIZE "ro.hwui.texture_cache_size" -#define PROPERTY_LAYER_CACHE_SIZE "ro.hwui.layer_cache_size" -#define PROPERTY_RENDER_BUFFER_CACHE_SIZE "ro.hwui.r_buffer_cache_size" -#define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size" -#define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size" -#define PROPERTY_VERTEX_CACHE_SIZE "ro.hwui.vertex_cache_size" -#define PROPERTY_PATCH_CACHE_SIZE "ro.hwui.patch_cache_size" -#define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size" -#define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_cache_size" - -// These properties are defined in percentage (range 0..1) -#define PROPERTY_TEXTURE_CACHE_FLUSH_RATE "ro.hwui.texture_cache_flushrate" - -// These properties are defined in pixels -#define PROPERTY_TEXT_SMALL_CACHE_WIDTH "ro.hwui.text_small_cache_width" -#define PROPERTY_TEXT_SMALL_CACHE_HEIGHT "ro.hwui.text_small_cache_height" -#define PROPERTY_TEXT_LARGE_CACHE_WIDTH "ro.hwui.text_large_cache_width" -#define PROPERTY_TEXT_LARGE_CACHE_HEIGHT "ro.hwui.text_large_cache_height" - -// Gamma (>= 1.0, <= 3.0) -#define PROPERTY_TEXT_GAMMA "hwui.text_gamma" - -/////////////////////////////////////////////////////////////////////////////// -// Default property values -/////////////////////////////////////////////////////////////////////////////// - -#define DEFAULT_TEXTURE_CACHE_SIZE 24.0f -#define DEFAULT_LAYER_CACHE_SIZE 16.0f -#define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f -#define DEFAULT_PATH_CACHE_SIZE 4.0f -#define DEFAULT_VERTEX_CACHE_SIZE 1.0f -#define DEFAULT_PATCH_CACHE_SIZE 128.0f // in kB -#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f -#define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f -#define DEFAULT_FBO_CACHE_SIZE 0 - -#define DEFAULT_TEXTURE_CACHE_FLUSH_RATE 0.6f +#define PROPERTY_ENABLE_GPU_PIXEL_BUFFERS "debug.hwui.use_gpu_pixel_buffers" -#define DEFAULT_TEXT_GAMMA 1.45f // Match design tools - -// cap to 256 to limite paths in the path cache -#define DEFAULT_PATH_TEXTURE_CAP 256 +/** + * Allows to set rendering pipeline mode to OpenGL (default), Skia OpenGL + * or Vulkan. + */ +#define PROPERTY_RENDERER "debug.hwui.renderer" /////////////////////////////////////////////////////////////////////////////// // Misc @@ -279,18 +216,8 @@ public: static bool useBufferAge; static bool enablePartialUpdates; - static float textGamma; - - static int fboCacheSize; - static int gradientCacheSize; - static int layerPoolSize; - static int patchCacheSize; - static int pathCacheSize; - static int renderBufferCacheSize; - static int tessellationCacheSize; - static int textDropShadowCacheSize; - static int textureCacheSize; - static float textureCacheFlushRate; + // TODO: Move somewhere else? + static constexpr float textGamma = 1.45f; static DebugLevel debugLevel; static OverdrawColorSet overdrawColorSet; diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp index 1ac57cdbac0c..2f8ddfe6d660 100644 --- a/libs/hwui/RenderBufferCache.cpp +++ b/libs/hwui/RenderBufferCache.cpp @@ -17,6 +17,7 @@ #include "Debug.h" #include "Properties.h" #include "RenderBufferCache.h" +#include "DeviceInfo.h" #include <utils/Log.h> @@ -36,13 +37,20 @@ namespace uirenderer { #define RENDER_BUFFER_LOGD(...) #endif +static uint32_t calculateRboCacheSize() { + // TODO: Do we need to use extensions().has4BitStencil() here? + // The tuning guide recommends it, but all real devices are configured + // with a larger cache than necessary by 4x, so keep the 2x for now regardless + return DeviceInfo::multiplyByResolution(2); +} + /////////////////////////////////////////////////////////////////////////////// // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// RenderBufferCache::RenderBufferCache() : mSize(0) - , mMaxSize(Properties::renderBufferCacheSize) {} + , mMaxSize(calculateRboCacheSize()) {} RenderBufferCache::~RenderBufferCache() { clear(); diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 55eeb7fa2073..61b3876cd095 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -241,7 +241,8 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable()) || CC_UNLIKELY(properties().getWidth() == 0) - || CC_UNLIKELY(properties().getHeight() == 0)) { + || CC_UNLIKELY(properties().getHeight() == 0) + || CC_UNLIKELY(!properties().fitsOnLayer())) { if (CC_UNLIKELY(hasLayer())) { renderthread::CanvasContext::destroyLayer(this); } diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 623b496f5f09..0a642b60d4c7 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -530,33 +530,25 @@ void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, cons // ---------------------------------------------------------------------------- void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) { - SkBitmap skBitmap; - bitmap.getSkBitmap(&skBitmap); - mCanvas->drawBitmap(skBitmap, left, top, paint); + mCanvas->drawImage(bitmap.makeImage(), left, top, paint); } void SkiaCanvas::drawBitmap(Bitmap& hwuiBitmap, const SkMatrix& matrix, const SkPaint* paint) { - SkBitmap bitmap; - hwuiBitmap.getSkBitmap(&bitmap); SkAutoCanvasRestore acr(mCanvas, true); mCanvas->concat(matrix); - mCanvas->drawBitmap(bitmap, 0, 0, paint); + mCanvas->drawImage(hwuiBitmap.makeImage(), 0, 0, paint); } void SkiaCanvas::drawBitmap(Bitmap& hwuiBitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) { - SkBitmap bitmap; - hwuiBitmap.getSkBitmap(&bitmap); SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - mCanvas->drawBitmapRect(bitmap, srcRect, dstRect, paint); + mCanvas->drawImageRect(hwuiBitmap.makeImage(), srcRect, dstRect, paint); } void SkiaCanvas::drawBitmapMesh(Bitmap& hwuiBitmap, int meshWidth, int meshHeight, const float* vertices, const int* colors, const SkPaint* paint) { - SkBitmap bitmap; - hwuiBitmap.getSkBitmap(&bitmap); const int ptCount = (meshWidth + 1) * (meshHeight + 1); const int indexCount = meshWidth * meshHeight * 6; uint32_t flags = SkVertices::kHasTexCoords_BuilderFlag; @@ -573,8 +565,8 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& hwuiBitmap, int meshWidth, int meshHeigh // cons up texture coordinates and indices { - const SkScalar w = SkIntToScalar(bitmap.width()); - const SkScalar h = SkIntToScalar(bitmap.height()); + const SkScalar w = SkIntToScalar(hwuiBitmap.width()); + const SkScalar h = SkIntToScalar(hwuiBitmap.height()); const SkScalar dx = w / meshWidth; const SkScalar dy = h / meshHeight; @@ -635,7 +627,7 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& hwuiBitmap, int meshWidth, int meshHeigh tmpPaint = *paint; } - sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(bitmap, kNever_SkCopyPixelsMode); + sk_sp<SkImage> image = hwuiBitmap.makeImage(); tmpPaint.setShader(image->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode)); mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate, tmpPaint); @@ -644,11 +636,8 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& hwuiBitmap, int meshWidth, int meshHeigh void SkiaCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk, float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) { - SkBitmap bitmap; - hwuiBitmap.getSkBitmap(&bitmap); - SkCanvas::Lattice lattice; - NinePatchUtils::SetLatticeDivs(&lattice, chunk, bitmap.width(), bitmap.height()); + NinePatchUtils::SetLatticeDivs(&lattice, chunk, hwuiBitmap.width(), hwuiBitmap.height()); lattice.fFlags = nullptr; int numFlags = 0; @@ -665,7 +654,7 @@ void SkiaCanvas::drawNinePatch(Bitmap& hwuiBitmap, const Res_png_9patch& chunk, lattice.fBounds = nullptr; SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - mCanvas->drawBitmapLattice(bitmap, lattice, dst, paint); + mCanvas->drawImageLattice(hwuiBitmap.makeImage().get(), lattice, dst, paint); } void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) { diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp index 34dddd169e96..16a19ca4d36a 100644 --- a/libs/hwui/SkiaCanvasProxy.cpp +++ b/libs/hwui/SkiaCanvasProxy.cpp @@ -151,7 +151,8 @@ void SkiaCanvasProxy::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& ce void SkiaCanvasProxy::onDrawImage(const SkImage* image, SkScalar left, SkScalar top, const SkPaint* paint) { SkBitmap skiaBitmap; - if (image->asLegacyBitmap(&skiaBitmap, SkImage::kRO_LegacyBitmapMode)) { + SkPixmap pixmap; + if (image->peekPixels(&pixmap) && skiaBitmap.installPixels(pixmap)) { onDrawBitmap(skiaBitmap, left, top, paint); } } @@ -159,7 +160,8 @@ void SkiaCanvasProxy::onDrawImage(const SkImage* image, SkScalar left, SkScalar void SkiaCanvasProxy::onDrawImageRect(const SkImage* image, const SkRect* srcPtr, const SkRect& dst, const SkPaint* paint, SrcRectConstraint constraint) { SkBitmap skiaBitmap; - if (image->asLegacyBitmap(&skiaBitmap, SkImage::kRO_LegacyBitmapMode)) { + SkPixmap pixmap; + if (image->peekPixels(&pixmap) && skiaBitmap.installPixels(pixmap)) { sk_sp<Bitmap> bitmap = Bitmap::createFrom(skiaBitmap.info(), *skiaBitmap.pixelRef()); SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(image->width(), image->height()); mCanvas->drawBitmap(*bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp index 91e7ac39af90..01582ce8f007 100644 --- a/libs/hwui/TessellationCache.cpp +++ b/libs/hwui/TessellationCache.cpp @@ -290,7 +290,7 @@ public: /////////////////////////////////////////////////////////////////////////////// TessellationCache::TessellationCache() - : mMaxSize(Properties::tessellationCacheSize) + : mMaxSize(MB(1)) , mCache(LruCache<Description, Buffer*>::kUnlimitedCapacity) , mShadowCache(LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*>::kUnlimitedCapacity) { mCache.setOnEntryRemovedListener(&mBufferRemovedListener); diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp index e1f0b2a20172..c521892c69df 100644 --- a/libs/hwui/TextDropShadowCache.cpp +++ b/libs/hwui/TextDropShadowCache.cpp @@ -94,7 +94,7 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) { /////////////////////////////////////////////////////////////////////////////// TextDropShadowCache::TextDropShadowCache() - : TextDropShadowCache(Properties::textDropShadowCacheSize) {} + : TextDropShadowCache(DeviceInfo::multiplyByResolution(2)) {} TextDropShadowCache::TextDropShadowCache(uint32_t maxByteSize) : mCache(LruCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity) diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 4ef31d59271e..b7c1e290370f 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -217,9 +217,8 @@ void Texture::colorTypeToGlFormatAndType(const Caches& caches, SkColorType color *outType = GL_UNSIGNED_SHORT_5_6_5; } break; - // ARGB_4444 and Index_8 are both upconverted to RGBA_8888 + // ARGB_4444 is upconverted to RGBA_8888 case kARGB_4444_SkColorType: - case kIndex_8_SkColorType: case kN32_SkColorType: *outFormat = GL_RGBA; *outInternalFormat = caches.rgbaInternalFormat(needSRGB); @@ -270,7 +269,6 @@ SkBitmap Texture::uploadToN32(const SkBitmap& bitmap, bool hasLinearBlending, bool Texture::hasUnsupportedColorType(const SkImageInfo& info, bool hasLinearBlending) { return info.colorType() == kARGB_4444_SkColorType - || info.colorType() == kIndex_8_SkColorType || (info.colorType() == kRGB_565_SkColorType && hasLinearBlending && info.colorSpace()->isSRGB()) @@ -279,11 +277,6 @@ bool Texture::hasUnsupportedColorType(const SkImageInfo& info, bool hasLinearBle } void Texture::upload(Bitmap& bitmap) { - if (!bitmap.readyToDraw()) { - ALOGE("Cannot generate texture from bitmap"); - return; - } - ATRACE_FORMAT("Upload %ux%u Texture", bitmap.width(), bitmap.height()); // We could also enable mipmapping if both bitmap dimensions are powers diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index 710cdd9286e8..6fe3606a7576 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -24,6 +24,7 @@ #include "Properties.h" #include "utils/TraceUtils.h" #include "hwui/Bitmap.h" +#include "DeviceInfo.h" namespace android { namespace uirenderer { @@ -35,13 +36,10 @@ namespace uirenderer { TextureCache::TextureCache() : mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity) , mSize(0) - , mMaxSize(Properties::textureCacheSize) - , mFlushRate(Properties::textureCacheFlushRate) { + , mMaxSize(DeviceInfo::multiplyByResolution(4 * 6)) // 6 screen-sized RGBA_8888 bitmaps + , mFlushRate(.4f) { mCache.setOnEntryRemovedListener(this); - - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); - INIT_LOGD(" Maximum texture dimension is %d pixels", mMaxTextureSize); - + mMaxTextureSize = DeviceInfo::get()->maxTextureSize(); mDebugEnabled = Properties::debugLevel & kDebugCaches; } diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index f6b2912a6254..ca179c9a25d2 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -22,6 +22,7 @@ #include "SkShader.h" #include <utils/Log.h> #include "utils/Macros.h" +#include "utils/TraceUtils.h" #include "utils/VectorDrawableUtils.h" #include <math.h> @@ -491,47 +492,119 @@ Bitmap& Tree::getBitmapUpdateIfDirty() { return *mCache.bitmap; } -void Tree::updateCache(sk_sp<SkSurface> surface) { - if (surface.get()) { - mCache.surface = surface; +void Tree::updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* context) { + SkRect dst; + sk_sp<SkSurface> surface = mCache.getSurface(&dst); + bool canReuseSurface = surface && dst.width() >= mProperties.getScaledWidth() + && dst.height() >= mProperties.getScaledHeight(); + if (!canReuseSurface) { + int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth()); + int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight()); + auto atlasEntry = atlas->requestNewEntry(scaledWidth, scaledHeight, context); + if (INVALID_ATLAS_KEY != atlasEntry.key) { + dst = atlasEntry.rect; + surface = atlasEntry.surface; + mCache.setAtlas(atlas, atlasEntry.key); + } else { + //don't draw, if we failed to allocate an offscreen buffer + mCache.clear(); + surface.reset(); + } + } + if (!canReuseSurface || mCache.dirty) { + draw(surface.get(), dst); + mCache.dirty = false; } - if (surface.get() || mCache.dirty) { - SkSurface* vdSurface = mCache.surface.get(); - SkCanvas* canvas = vdSurface->getCanvas(); - float scaleX = vdSurface->width() / mProperties.getViewportWidth(); - float scaleY = vdSurface->height() / mProperties.getViewportHeight(); +} + +void Tree::draw(SkSurface* surface, const SkRect& dst) { + if (surface) { + SkCanvas* canvas = surface->getCanvas(); + float scaleX = dst.width() / mProperties.getViewportWidth(); + float scaleY = dst.height() / mProperties.getViewportHeight(); SkAutoCanvasRestore acr(canvas, true); + canvas->translate(dst.fLeft, dst.fTop); + canvas->clipRect(SkRect::MakeWH(dst.width(), dst.height())); canvas->clear(SK_ColorTRANSPARENT); canvas->scale(scaleX, scaleY); mRootNode->draw(canvas, false); - mCache.dirty = false; - canvas->flush(); } } +void Tree::Cache::setAtlas(sp<skiapipeline::VectorDrawableAtlas> newAtlas, + skiapipeline::AtlasKey newAtlasKey) { + LOG_ALWAYS_FATAL_IF(newAtlasKey == INVALID_ATLAS_KEY); + clear(); + mAtlas = newAtlas; + mAtlasKey = newAtlasKey; +} + +sk_sp<SkSurface> Tree::Cache::getSurface(SkRect* bounds) { + sk_sp<SkSurface> surface; + sp<skiapipeline::VectorDrawableAtlas> atlas = mAtlas.promote(); + if (atlas.get() && mAtlasKey != INVALID_ATLAS_KEY) { + auto atlasEntry = atlas->getEntry(mAtlasKey); + *bounds = atlasEntry.rect; + surface = atlasEntry.surface; + mAtlasKey = atlasEntry.key; + } + + return surface; +} + +void Tree::Cache::clear() { + sp<skiapipeline::VectorDrawableAtlas> lockAtlas = mAtlas.promote(); + if (lockAtlas.get()) { + lockAtlas->releaseEntry(mAtlasKey); + } + mAtlas = nullptr; + mAtlasKey = INVALID_ATLAS_KEY; +} + void Tree::draw(SkCanvas* canvas) { - /* - * TODO address the following... - * - * 1) figure out how to set path's as volatile during animation - * 2) if mRoot->getPaint() != null either promote to layer (during - * animation) or cache in SkSurface (for static content) - */ - canvas->drawImageRect(mCache.surface->makeImageSnapshot().get(), - mutateProperties()->getBounds(), getPaint()); + SkRect src; + sk_sp<SkSurface> vdSurface = mCache.getSurface(&src); + if (vdSurface) { + canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src, + mutateProperties()->getBounds(), getPaint()); + } else { + // Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure. + // We render the VD into a temporary standalone buffer and mark the frame as dirty. Next + // frame will be cached into the atlas. + int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth()); + int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight()); + SkRect src = SkRect::MakeWH(scaledWidth, scaledHeight); +#ifndef ANDROID_ENABLE_LINEAR_BLENDING + sk_sp<SkColorSpace> colorSpace = nullptr; +#else + sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); +#endif + SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight, kPremul_SkAlphaType, + colorSpace); + sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(canvas->getGrContext(), + SkBudgeted::kYes, info); + draw(surface.get(), src); + mCache.clear(); + canvas->drawImageRect(surface->makeImageSnapshot().get(), mutateProperties()->getBounds(), + getPaint()); + markDirty(); + } } void Tree::updateBitmapCache(Bitmap& bitmap, bool useStagingData) { SkBitmap outCache; bitmap.getSkBitmap(&outCache); + int cacheWidth = outCache.width(); + int cacheHeight = outCache.height(); + ATRACE_FORMAT("VectorDrawable repaint %dx%d", cacheWidth, cacheHeight); outCache.eraseColor(SK_ColorTRANSPARENT); SkCanvas outCanvas(outCache); float viewportWidth = useStagingData ? mStagingProperties.getViewportWidth() : mProperties.getViewportWidth(); float viewportHeight = useStagingData ? mStagingProperties.getViewportHeight() : mProperties.getViewportHeight(); - float scaleX = outCache.width() / viewportWidth; - float scaleY = outCache.height() / viewportHeight; + float scaleX = cacheWidth / viewportWidth; + float scaleY = cacheHeight / viewportHeight; outCanvas.scale(scaleX, scaleY); mRootNode->draw(&outCanvas, useStagingData); } diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index 22cfe29d2aa5..efbb695a14dd 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -19,6 +19,7 @@ #include "hwui/Canvas.h" #include "hwui/Bitmap.h" +#include "renderthread/CacheManager.h" #include "DisplayList.h" #include <SkBitmap.h> @@ -687,35 +688,61 @@ public: bool getPropertyChangeWillBeConsumed() const { return mWillBeConsumed; } void setPropertyChangeWillBeConsumed(bool willBeConsumed) { mWillBeConsumed = willBeConsumed; } - // Returns true if VD cache surface is big enough. This should always be called from RT and it - // works with Skia pipelines only. - bool canReuseSurface() { - SkSurface* surface = mCache.surface.get(); - return surface && surface->width() >= mProperties.getScaledWidth() - && surface->height() >= mProperties.getScaledHeight(); - } - - // Draws VD cache into a canvas. This should always be called from RT and it works with Skia - // pipelines only. + /** + * Draws VD cache into a canvas. This should always be called from RT and it works with Skia + * pipelines only. + */ void draw(SkCanvas* canvas); - // Draws VD into a GPU backed surface. If canReuseSurface returns false, then "surface" must - // contain a new surface. This should always be called from RT and it works with Skia pipelines - // only. - void updateCache(sk_sp<SkSurface> surface); + /** + * Draws VD into a GPU backed surface. + * This should always be called from RT and it works with Skia pipeline only. + */ + void updateCache(sp<skiapipeline::VectorDrawableAtlas>& atlas, GrContext* context); private: - struct Cache { + class Cache { + public: sk_sp<Bitmap> bitmap; //used by HWUI pipeline and software //TODO: use surface instead of bitmap when drawing in software canvas - sk_sp<SkSurface> surface; //used only by Skia pipelines bool dirty = true; + + // the rest of the code in Cache is used by Skia pipelines only + + ~Cache() { clear(); } + + /** + * Stores a weak pointer to the atlas and a key. + */ + void setAtlas(sp<skiapipeline::VectorDrawableAtlas> atlas, + skiapipeline::AtlasKey newAtlasKey); + + /** + * Gets a surface and bounds from the atlas. + * + * @return nullptr if the altas has been deleted. + */ + sk_sp<SkSurface> getSurface(SkRect* bounds); + + /** + * Releases atlas key from the atlas, which makes it available for reuse. + */ + void clear(); + private: + wp<skiapipeline::VectorDrawableAtlas> mAtlas; + skiapipeline::AtlasKey mAtlasKey = INVALID_ATLAS_KEY; }; SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop); bool allocateBitmapIfNeeded(Cache& cache, int width, int height); bool canReuseBitmap(Bitmap*, int width, int height); void updateBitmapCache(Bitmap& outCache, bool useStagingData); + + /** + * Draws the root node into "surface" at a given "dst" position. + */ + void draw(SkSurface* surface, const SkRect& dst); + // Cap the bitmap size, such that it won't hurt the performance too much // and it won't crash due to a very large scale. // The drawable will look blurry above this size. diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp index e2844ad0649a..73beba9297c4 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -91,6 +91,9 @@ CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) CacheBlock* prevBlock = blockToRemove->mPrev; if (prevBlock) { + // If this doesn't hold, we have a use-after-free below. + LOG_ALWAYS_FATAL_IF(head == blockToRemove, + "removeBlock: head should not have a previous block"); prevBlock->mNext = nextBlock; } else { newHead = nextBlock; diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h index 07e8b34ac66f..d4b4ff949f5a 100644 --- a/libs/hwui/font/FontUtil.h +++ b/libs/hwui/font/FontUtil.h @@ -25,11 +25,6 @@ // Defines /////////////////////////////////////////////////////////////////////////////// -#define DEFAULT_TEXT_SMALL_CACHE_WIDTH 1024 -#define DEFAULT_TEXT_SMALL_CACHE_HEIGHT 512 -#define DEFAULT_TEXT_LARGE_CACHE_WIDTH 2048 -#define DEFAULT_TEXT_LARGE_CACHE_HEIGHT 512 - #ifdef TEXTURE_BORDER_SIZE #if TEXTURE_BORDER_SIZE != 1 #error TEXTURE_BORDER_SIZE other than 1 is not currently supported diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 6dde005c2fc4..75b6d233643d 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -46,10 +46,10 @@ static bool computeAllocationSize(size_t rowBytes, int height, size_t* size) { return true; } -typedef sk_sp<Bitmap> (*AllocPixeRef)(size_t allocSize, const SkImageInfo& info, size_t rowBytes, - sk_sp<SkColorTable> ctable); +typedef sk_sp<Bitmap> (*AllocPixelRef)(size_t allocSize, const SkImageInfo& info, + size_t rowBytes); -static sk_sp<Bitmap> allocateBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable, AllocPixeRef alloc) { +static sk_sp<Bitmap> allocateBitmap(SkBitmap* bitmap, AllocPixelRef alloc) { const SkImageInfo& info = bitmap->info(); if (info.colorType() == kUnknown_SkColorType) { LOG_ALWAYS_FATAL("unknown bitmap configuration"); @@ -65,32 +65,31 @@ static sk_sp<Bitmap> allocateBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable return nullptr; } - auto wrapper = alloc(size, info, rowBytes, std::move(ctable)); + auto wrapper = alloc(size, info, rowBytes); if (wrapper) { wrapper->getSkBitmap(bitmap); } return wrapper; } -sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable) { - return allocateBitmap(bitmap, std::move(ctable), &Bitmap::allocateAshmemBitmap); +sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(SkBitmap* bitmap) { + return allocateBitmap(bitmap, &Bitmap::allocateAshmemBitmap); } -static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes, - sk_sp<SkColorTable> ctable) { +static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, size_t rowBytes) { void* addr = calloc(size, 1); if (!addr) { return nullptr; } - return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes, std::move(ctable))); + return sk_sp<Bitmap>(new Bitmap(addr, size, info, rowBytes)); } sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) { return uirenderer::renderthread::RenderProxy::allocateHardwareBitmap(bitmap); } -sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable) { - return allocateBitmap(bitmap, std::move(ctable), &android::allocateHeapBitmap); +sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap) { + return allocateBitmap(bitmap, &android::allocateHeapBitmap); } sk_sp<Bitmap> Bitmap::allocateHeapBitmap(const SkImageInfo& info) { @@ -99,11 +98,11 @@ sk_sp<Bitmap> Bitmap::allocateHeapBitmap(const SkImageInfo& info) { LOG_ALWAYS_FATAL("trying to allocate too large bitmap"); return nullptr; } - return android::allocateHeapBitmap(size, info, info.minRowBytes(), nullptr); + return android::allocateHeapBitmap(size, info, info.minRowBytes()); } sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, - size_t rowBytes, sk_sp<SkColorTable> ctable) { + size_t rowBytes) { // Create new ashmem region with read/write priv int fd = ashmem_create_region("bitmap", size); if (fd < 0) { @@ -121,7 +120,7 @@ sk_sp<Bitmap> Bitmap::allocateAshmemBitmap(size_t size, const SkImageInfo& info, close(fd); return nullptr; } - return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes, std::move(ctable))); + return sk_sp<Bitmap>(new Bitmap(addr, fd, size, info, rowBytes)); } void FreePixelRef(void* addr, void* context) { @@ -132,7 +131,7 @@ void FreePixelRef(void* addr, void* context) { sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) { pixelRef.ref(); return sk_sp<Bitmap>(new Bitmap((void*) pixelRef.pixels(), (void*) &pixelRef, FreePixelRef, - info, pixelRef.rowBytes(), sk_ref_sp(pixelRef.colorTable()))); + info, pixelRef.rowBytes())); } sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer) { @@ -161,11 +160,7 @@ static SkImageInfo validateAlpha(const SkImageInfo& info) { return info.makeAlphaType(alphaType); } -void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes, sk_sp<SkColorTable> ctable) { - if (kIndex_8_SkColorType != newInfo.colorType()) { - ctable = nullptr; - } - +void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes) { mInfo = validateAlpha(newInfo); // Dirty hack is dirty @@ -173,20 +168,11 @@ void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes, sk_sp<SkCo // really hard to work with. Skia really, really wants immutable objects, // but with the nested-ref-count hackery going on that's just not // feasible without going insane trying to figure it out - this->android_only_reset(mInfo.width(), mInfo.height(), rowBytes, std::move(ctable)); + this->android_only_reset(mInfo.width(), mInfo.height(), rowBytes); } -static sk_sp<SkColorTable> sanitize(const SkImageInfo& info, sk_sp<SkColorTable> ctable) { - if (info.colorType() == kIndex_8_SkColorType) { - SkASSERT(ctable); - return ctable; - } - return nullptr; // drop the ctable if we're not indexed -} -Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBytes, - sk_sp<SkColorTable> ctable) - : SkPixelRef(info.width(), info.height(), address, rowBytes, - sanitize(info, std::move(ctable))) +Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBytes) + : SkPixelRef(info.width(), info.height(), address, rowBytes) , mInfo(validateAlpha(info)) , mPixelStorageType(PixelStorageType::Heap) { mPixelStorage.heap.address = address; @@ -194,9 +180,8 @@ Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBy } Bitmap::Bitmap(void* address, void* context, FreeFunc freeFunc, - const SkImageInfo& info, size_t rowBytes, sk_sp<SkColorTable> ctable) - : SkPixelRef(info.width(), info.height(), address, rowBytes, - sanitize(info, std::move(ctable))) + const SkImageInfo& info, size_t rowBytes) + : SkPixelRef(info.width(), info.height(), address, rowBytes) , mInfo(validateAlpha(info)) , mPixelStorageType(PixelStorageType::External) { mPixelStorage.external.address = address; @@ -205,9 +190,8 @@ Bitmap::Bitmap(void* address, void* context, FreeFunc freeFunc, } Bitmap::Bitmap(void* address, int fd, size_t mappedSize, - const SkImageInfo& info, size_t rowBytes, sk_sp<SkColorTable> ctable) - : SkPixelRef(info.width(), info.height(), address, rowBytes, - sanitize(info, std::move(ctable))) + const SkImageInfo& info, size_t rowBytes) + : SkPixelRef(info.width(), info.height(), address, rowBytes) , mInfo(validateAlpha(info)) , mPixelStorageType(PixelStorageType::Ashmem) { mPixelStorage.ashmem.address = address; @@ -217,15 +201,16 @@ Bitmap::Bitmap(void* address, int fd, size_t mappedSize, Bitmap::Bitmap(GraphicBuffer* buffer, const SkImageInfo& info) : SkPixelRef(info.width(), info.height(), nullptr, - bytesPerPixel(buffer->getPixelFormat()) * buffer->getStride(), - nullptr) + bytesPerPixel(buffer->getPixelFormat()) * buffer->getStride()) , mInfo(validateAlpha(info)) , mPixelStorageType(PixelStorageType::Hardware) { mPixelStorage.hardware.buffer = buffer; buffer->incStrong(buffer); setImmutable(); // HW bitmaps are always immutable if (uirenderer::Properties::isSkiaEnabled()) { - // TODO: add color correctness for Skia pipeline - pass null color space for now + // GraphicBuffer should be in the display color space (Bitmap::createFrom is always + // passing SRGB). The code that uploads into a GraphicBuffer should do color conversion if + // needed. mImage = SkImage::MakeFromAHardwareBuffer(reinterpret_cast<AHardwareBuffer*>(buffer), mInfo.alphaType(), nullptr); } @@ -295,7 +280,7 @@ size_t Bitmap::getAllocationByteCount() const { } void Bitmap::reconfigure(const SkImageInfo& info) { - reconfigure(info, info.minRowBytes(), nullptr); + reconfigure(info, info.minRowBytes()); } void Bitmap::setAlphaType(SkAlphaType alphaType) { @@ -310,7 +295,6 @@ void Bitmap::getSkBitmap(SkBitmap* outBitmap) { outBitmap->setHasHardwareMipMap(mHasHardwareMipMap); if (isHardware()) { if (uirenderer::Properties::isSkiaEnabled()) { - // TODO: add color correctness for Skia pipeline - pass null color space for now outBitmap->allocPixels(SkImageInfo::Make(info().width(), info().height(), info().colorType(), info().alphaType(), nullptr)); } else { @@ -346,7 +330,12 @@ sk_sp<SkImage> Bitmap::makeImage() { // Note we don't cache in this case, because the raster image holds a pointer to this Bitmap // internally and ~Bitmap won't be invoked. // TODO: refactor Bitmap to not derive from SkPixelRef, which would allow caching here. - image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode); + if (uirenderer::Properties::isSkiaEnabled()) { + image = SkMakeImageInColorSpace(skiaBitmap, SkColorSpace::MakeSRGB(), + skiaBitmap.getGenerationID(), kNever_SkCopyPixelsMode); + } else { + image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode); + } } return image; } diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 3642406709f9..634e76450c38 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -17,7 +17,6 @@ #include <SkBitmap.h> #include <SkColorSpace.h> -#include <SkColorTable.h> #include <SkImage.h> #include <SkImageInfo.h> #include <SkPixelRef.h> @@ -46,32 +45,31 @@ typedef void (*FreeFunc)(void* addr, void* context); class ANDROID_API Bitmap : public SkPixelRef { public: - static sk_sp<Bitmap> allocateHeapBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable); + static sk_sp<Bitmap> allocateHeapBitmap(SkBitmap* bitmap); static sk_sp<Bitmap> allocateHeapBitmap(const SkImageInfo& info); static sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& bitmap); - static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap, sk_sp<SkColorTable> ctable); + static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap); static sk_sp<Bitmap> allocateAshmemBitmap(size_t allocSize, const SkImageInfo& info, - size_t rowBytes, sk_sp<SkColorTable> ctable); + size_t rowBytes); static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer); static sk_sp<Bitmap> createFrom(const SkImageInfo&, SkPixelRef&); - Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes, - sk_sp<SkColorTable> ctable); + Bitmap(void* address, size_t allocSize, const SkImageInfo& info, size_t rowBytes); Bitmap(void* address, void* context, FreeFunc freeFunc, - const SkImageInfo& info, size_t rowBytes, sk_sp<SkColorTable> ctable); + const SkImageInfo& info, size_t rowBytes); Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, - size_t rowBytes, sk_sp<SkColorTable> ctable); + size_t rowBytes); Bitmap(GraphicBuffer* buffer, const SkImageInfo& info); int rowBytesAsPixels() const { return rowBytes() >> SkColorTypeShiftPerPixel(mInfo.colorType()); } - void reconfigure(const SkImageInfo& info, size_t rowBytes, sk_sp<SkColorTable> ctable); + void reconfigure(const SkImageInfo& info, size_t rowBytes); void reconfigure(const SkImageInfo& info); void setColorSpace(sk_sp<SkColorSpace> colorSpace); void setAlphaType(SkAlphaType alphaType); @@ -92,10 +90,6 @@ public: void getBounds(SkRect* bounds) const; - bool readyToDraw() const { - return this->colorType() != kIndex_8_SkColorType || this->colorTable(); - } - bool isHardware() const { return mPixelStorageType == PixelStorageType::Hardware; } diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 679cb5021e27..e754daf7c42e 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -35,6 +35,13 @@ Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::Rende return new uirenderer::RecordingCanvas(width, height); } +static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness, + const SkPaint& paint, Canvas* canvas) { + const SkScalar strokeWidth = fmax(thickness, 1.0f); + const SkScalar bottom = top + strokeWidth; + canvas->drawRect(left, top, right, bottom, paint); +} + void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) { uint32_t flags; SkDrawFilter* drawFilter = getDrawFilter(); @@ -46,24 +53,28 @@ void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& flags = paint.getFlags(); } if (flags & (SkPaint::kUnderlineText_ReserveFlag | SkPaint::kStrikeThruText_ReserveFlag)) { - // Same values used by Skia - const float kStdStrikeThru_Offset = (-6.0f / 21.0f); - const float kStdUnderline_Offset = (1.0f / 9.0f); - const float kStdUnderline_Thickness = (1.0f / 18.0f); - - SkScalar left = x; - SkScalar right = x + length; - float textSize = paint.getTextSize(); - float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f); + const SkScalar left = x; + const SkScalar right = x + length; if (flags & SkPaint::kUnderlineText_ReserveFlag) { - SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth; - SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth; - drawRect(left, top, right, bottom, paint); + Paint::FontMetrics metrics; + paint.getFontMetrics(&metrics); + SkScalar position; + if (!metrics.hasUnderlinePosition(&position)) { + position = paint.getTextSize() * Paint::kStdUnderline_Top; + } + SkScalar thickness; + if (!metrics.hasUnderlineThickness(&thickness)) { + thickness = paint.getTextSize() * Paint::kStdUnderline_Thickness; + } + const SkScalar top = y + position; + drawStroke(left, right, top, thickness, paint, this); } if (flags & SkPaint::kStrikeThruText_ReserveFlag) { - SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth; - SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth; - drawRect(left, top, right, bottom, paint); + const float textSize = paint.getTextSize(); + const float position = textSize * Paint::kStdStrikeThru_Top; + const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; + const SkScalar top = y + position; + drawStroke(left, right, top, thickness, paint, this); } } } diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index c9b5f0031a7b..a5d83a0ca018 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -28,6 +28,19 @@ namespace android { class ANDROID_API Paint : public SkPaint { public: + // Default values for underlined and strikethrough text, + // as defined by Skia in SkTextFormatParams.h. + constexpr static float kStdStrikeThru_Offset = (-6.0f / 21.0f); + constexpr static float kStdUnderline_Offset = (1.0f / 9.0f); + constexpr static float kStdUnderline_Thickness = (1.0f / 18.0f); + + constexpr static float kStdUnderline_Top = + kStdUnderline_Offset - 0.5f * kStdUnderline_Thickness; + + constexpr static float kStdStrikeThru_Thickness = kStdUnderline_Thickness; + constexpr static float kStdStrikeThru_Top = + kStdStrikeThru_Offset - 0.5f * kStdStrikeThru_Thickness; + Paint(); Paint(const Paint& paint); Paint(const SkPaint& paint); // NOLINT(implicit) diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp index 2ebfbcc1f18e..4feeb2d6facb 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.cpp +++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp @@ -18,6 +18,7 @@ #include "LayerDrawable.h" #include "VkLayer.h" +#include "GrBackendSurface.h" #include "SkColorFilter.h" #include "SkSurface.h" #include "gl/GrGLTypes.h" @@ -46,13 +47,10 @@ bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer GrGLTextureInfo externalTexture; externalTexture.fTarget = glLayer->getRenderTarget(); externalTexture.fID = glLayer->getTextureId(); - GrBackendTextureDesc textureDescription; - textureDescription.fWidth = glLayer->getWidth(); - textureDescription.fHeight = glLayer->getHeight(); - textureDescription.fConfig = kRGBA_8888_GrPixelConfig; - textureDescription.fOrigin = kTopLeft_GrSurfaceOrigin; - textureDescription.fTextureHandle = reinterpret_cast<GrBackendObject>(&externalTexture); - layerImage = SkImage::MakeFromTexture(context, textureDescription); + GrBackendTexture backendTexture(glLayer->getWidth(), glLayer->getHeight(), + kRGBA_8888_GrPixelConfig, externalTexture); + layerImage = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin, + kPremul_SkAlphaType, nullptr); } else { SkASSERT(layer->getApi() == Layer::Api::Vulkan); VkLayer* vkLayer = static_cast<VkLayer*>(layer); diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp index 975f849c7071..374d364787de 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp @@ -51,8 +51,6 @@ void StartReorderBarrierDrawable::onDraw(SkCanvas* canvas) { return aZValue < bZValue; }); - SkASSERT(!mChildren.empty()); - size_t drawIndex = 0; const size_t endIndex = mChildren.size(); while (drawIndex < endIndex) { @@ -76,7 +74,6 @@ EndReorderBarrierDrawable::EndReorderBarrierDrawable(StartReorderBarrierDrawable void EndReorderBarrierDrawable::onDraw(SkCanvas* canvas) { auto& zChildren = mStartBarrier->mChildren; - SkASSERT(!zChildren.empty()); /** * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 6d5ef1d4ff1f..925db303461f 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -27,6 +27,8 @@ #include "SkiaProfileRenderer.h" #include "utils/TraceUtils.h" +#include <GrBackendSurface.h> + #include <cutils/properties.h> #include <strings.h> @@ -69,20 +71,17 @@ bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, mEglManager.damageFrame(frame, dirty); // setup surface for fbo0 - GrBackendRenderTargetDesc renderTargetDesc; - renderTargetDesc.fWidth = frame.width(); - renderTargetDesc.fHeight = frame.height(); - renderTargetDesc.fConfig = kRGBA_8888_GrPixelConfig; - renderTargetDesc.fOrigin = kBottomLeft_GrSurfaceOrigin; - renderTargetDesc.fSampleCnt = 0; - renderTargetDesc.fStencilBits = STENCIL_BUFFER_SIZE; - renderTargetDesc.fRenderTargetHandle = 0; + GrGLFramebufferInfo fboInfo; + fboInfo.fFBOID = 0; + + GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, + kRGBA_8888_GrPixelConfig, fboInfo); SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget( - mRenderThread.getGrContext(), renderTargetDesc, &props)); + mRenderThread.getGrContext(), backendRT, kBottomLeft_GrSurfaceOrigin, nullptr, &props)); SkiaPipeline::updateLighting(lightGeometry, lightInfo); renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut, @@ -270,8 +269,7 @@ sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThr switch (info.colorType()) { case kRGBA_8888_SkColorType: isSupported = true; - // ARGB_4444 and Index_8 are both upconverted to RGBA_8888 - case kIndex_8_SkColorType: + // ARGB_4444 is upconverted to RGBA_8888 case kARGB_4444_SkColorType: pixelFormat = PIXEL_FORMAT_RGBA_8888; format = GL_RGBA; @@ -305,6 +303,13 @@ sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThr return nullptr; } + auto colorSpace = info.colorSpace(); + bool convertToSRGB = false; + if (colorSpace && (!colorSpace->isSRGB())) { + isSupported = false; + convertToSRGB = true; + } + SkBitmap bitmap; if (isSupported) { bitmap = skBitmap; @@ -312,7 +317,7 @@ sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThr bitmap.allocPixels(SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(), nullptr)); bitmap.eraseColor(0); - if (info.colorType() == kRGBA_F16_SkColorType) { + if (info.colorType() == kRGBA_F16_SkColorType || convertToSRGB) { // Drawing RGBA_F16 onto ARGB_8888 is not supported skBitmap.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()), bitmap.getPixels(), bitmap.rowBytes(), 0, 0); diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp index 89697d7445c6..311419dd2b65 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp @@ -20,6 +20,7 @@ #include "Properties.h" #include <SkCanvas.h> #include <SkSurface.h> +#include <GrBackendSurface.h> #include <gl/GrGLInterface.h> #include <gl/GrGLTypes.h> #include <GLES2/gl2.h> @@ -53,50 +54,61 @@ CopyResult SkiaOpenGLReadback::copyImageInto(EGLImageKHR eglImage, const Matrix4 externalTexture.fTarget = GL_TEXTURE_EXTERNAL_OES; externalTexture.fID = sourceTexId; - GrBackendTextureDesc textureDescription; - textureDescription.fWidth = imgWidth; - textureDescription.fHeight = imgHeight; - textureDescription.fConfig = kRGBA_8888_GrPixelConfig; - textureDescription.fOrigin = kTopLeft_GrSurfaceOrigin; - textureDescription.fTextureHandle = reinterpret_cast<GrBackendObject>(&externalTexture); + GrBackendTexture backendTexture(imgWidth, imgHeight, kRGBA_8888_GrPixelConfig, externalTexture); CopyResult copyResult = CopyResult::UnknownError; - sk_sp<SkImage> image(SkImage::MakeFromAdoptedTexture(grContext.get(), textureDescription)); + sk_sp<SkImage> image(SkImage::MakeFromAdoptedTexture(grContext.get(), backendTexture, + kTopLeft_GrSurfaceOrigin)); if (image) { - // convert to Skia data structures - const SkRect bufferRect = SkRect::MakeIWH(imgWidth, imgHeight); - SkRect skiaSrcRect = srcRect.toSkRect(); SkMatrix textureMatrix; imgTransform.copyTo(textureMatrix); - // remove the y-flip applied to the matrix so that we can scale the srcRect. - // This flip is not needed as we specify the origin of the texture when we - // wrap it as an SkImage. + // remove the y-flip applied to the matrix SkMatrix yFlip = SkMatrix::MakeScale(1, -1); yFlip.postTranslate(0,1); textureMatrix.preConcat(yFlip); - // copy the entire src if the rect is empty - if (skiaSrcRect.isEmpty()) { - skiaSrcRect = bufferRect; - } + // multiply by image size, because textureMatrix maps to [0..1] range + textureMatrix[SkMatrix::kMTransX] *= imgWidth; + textureMatrix[SkMatrix::kMTransY] *= imgHeight; - // since the y-flip has been removed we can simply scale & translate - // the source rectangle - textureMatrix.mapRect(&skiaSrcRect); + // swap rotation and translation part of the matrix, because we convert from + // right-handed Cartesian to left-handed coordinate system. + std::swap(textureMatrix[SkMatrix::kMTransX], textureMatrix[SkMatrix::kMTransY]); + std::swap(textureMatrix[SkMatrix::kMSkewX], textureMatrix[SkMatrix::kMSkewY]); - if (skiaSrcRect.intersect(bufferRect)) { + // convert to Skia data structures + SkRect skiaSrcRect = srcRect.toSkRect(); + SkMatrix textureMatrixInv; + SkRect skiaDestRect = SkRect::MakeWH(bitmap->width(), bitmap->height()); + bool srcNotEmpty = false; + if (textureMatrix.invert(&textureMatrixInv)) { + if (skiaSrcRect.isEmpty()) { + skiaSrcRect = SkRect::MakeIWH(imgWidth, imgHeight); + srcNotEmpty = !skiaSrcRect.isEmpty(); + } else { + // src and dest rectangles need to be converted into texture coordinates before the + // rotation matrix is applied (because drawImageRect preconcat its matrix). + textureMatrixInv.mapRect(&skiaSrcRect); + srcNotEmpty = skiaSrcRect.intersect(SkRect::MakeIWH(imgWidth, imgHeight)); + } + textureMatrixInv.mapRect(&skiaDestRect); + } + + if (srcNotEmpty) { // we render in an offscreen buffer to scale and to avoid an issue b/62262733 // with reading incorrect data from EGLImage backed SkImage (likely a driver bug) sk_sp<SkSurface> scaledSurface = SkSurface::MakeRenderTarget( grContext.get(), SkBudgeted::kYes, bitmap->info()); SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); - scaledSurface->getCanvas()->drawImageRect(image, skiaSrcRect, - SkRect::MakeWH(bitmap->width(), bitmap->height()), &paint); + scaledSurface->getCanvas()->concat(textureMatrix); + scaledSurface->getCanvas()->drawImageRect(image, skiaSrcRect, skiaDestRect, &paint); + image = scaledSurface->makeImageSnapshot(); if (image->readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) { + bitmap->notifyPixelsChanged(); copyResult = CopyResult::Success; } } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 0bab7932432c..03792e0ed291 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -95,7 +95,7 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, SkASSERT(layerNode->getDisplayList()->isSkiaDL()); SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList(); if (!displayList || displayList->isEmpty()) { - SkDEBUGF(("%p drawLayers(%s) : missing drawable", this, layerNode->getName())); + SkDEBUGF(("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName())); return; } @@ -183,26 +183,16 @@ public: }; void SkiaPipeline::renderVectorDrawableCache() { - //render VectorDrawables into offscreen buffers - for (auto vd : mVectorDrawables) { - sk_sp<SkSurface> surface; - if (!vd->canReuseSurface()) { -#ifndef ANDROID_ENABLE_LINEAR_BLENDING - sk_sp<SkColorSpace> colorSpace = nullptr; -#else - sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); -#endif - int scaledWidth = SkScalarCeilToInt(vd->properties().getScaledWidth()); - int scaledHeight = SkScalarCeilToInt(vd->properties().getScaledHeight()); - SkImageInfo info = SkImageInfo::MakeN32(scaledWidth, scaledHeight, - kPremul_SkAlphaType, colorSpace); - SkASSERT(mRenderThread.getGrContext() != nullptr); - surface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, - info); + if (!mVectorDrawables.empty()) { + sp<VectorDrawableAtlas> atlas = mRenderThread.cacheManager().acquireVectorDrawableAtlas(); + auto grContext = mRenderThread.getGrContext(); + atlas->prepareForDraw(grContext); + for (auto vd : mVectorDrawables) { + vd->updateCache(atlas, grContext); } - vd->updateCache(surface); + grContext->flush(); + mVectorDrawables.clear(); } - mVectorDrawables.clear(); } void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, diff --git a/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp new file mode 100644 index 000000000000..437653a8dfa8 --- /dev/null +++ b/libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2017 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 "VectorDrawableAtlas.h" + +#include <GrRectanizer_pow2.h> +#include <SkCanvas.h> +#include <cmath> +#include "utils/TraceUtils.h" +#include "renderthread/RenderProxy.h" + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +VectorDrawableAtlas::VectorDrawableAtlas(size_t surfaceArea, StorageMode storageMode) + : mWidth((int)std::sqrt(surfaceArea)) + , mHeight((int)std::sqrt(surfaceArea)) + , mStorageMode(storageMode) { +} + +void VectorDrawableAtlas::prepareForDraw(GrContext* context) { + if (StorageMode::allowSharedSurface == mStorageMode) { + if (!mSurface) { + mSurface = createSurface(mWidth, mHeight, context); + mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight); + mPixelUsedByVDs = 0; + mPixelAllocated = 0; + mConsecutiveFailures = 0; + mFreeRects.clear(); + } else { + if (isFragmented()) { + // Invoke repack outside renderFrame to avoid jank. + renderthread::RenderProxy::repackVectorDrawableAtlas(); + } + } + } +} + +#define MAX_CONSECUTIVE_FAILURES 5 +#define MAX_UNUSED_RATIO 2.0f + +bool VectorDrawableAtlas::isFragmented() { + return mConsecutiveFailures > MAX_CONSECUTIVE_FAILURES + && mPixelUsedByVDs*MAX_UNUSED_RATIO < mPixelAllocated; +} + +void VectorDrawableAtlas::repackIfNeeded(GrContext* context) { + // We repackage when atlas failed to allocate space MAX_CONSECUTIVE_FAILURES consecutive + // times and the atlas allocated pixels are at least MAX_UNUSED_RATIO times higher than pixels + // used by atlas VDs. + if (isFragmented() && mSurface) { + repack(context); + } +} + +// compare to CacheEntry objects based on VD area. +bool VectorDrawableAtlas::compareCacheEntry(const CacheEntry& first, const CacheEntry& second) +{ + return first.VDrect.width()*first.VDrect.height() < second.VDrect.width()*second.VDrect.height(); +} + +void VectorDrawableAtlas::repack(GrContext* context) { + ATRACE_CALL(); + sk_sp<SkSurface> newSurface; + SkCanvas* canvas = nullptr; + if (StorageMode::allowSharedSurface == mStorageMode) { + newSurface = createSurface(mWidth, mHeight, context); + if (!newSurface) { + return; + } + canvas = newSurface->getCanvas(); + canvas->clear(SK_ColorTRANSPARENT); + mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight); + } else { + if (!mSurface) { + return; //nothing to repack + } + mRectanizer.reset(); + } + mFreeRects.clear(); + SkImage* sourceImageAtlas = nullptr; + if (mSurface) { + sourceImageAtlas = mSurface->makeImageSnapshot().get(); + } + + // Sort the list by VD size, which allows for the smallest VDs to get first in the atlas. + // Sorting is safe, because it does not affect iterator validity. + if (mRects.size() <= 100) { + mRects.sort(compareCacheEntry); + } + + for (CacheEntry& entry : mRects) { + SkRect currentVDRect = entry.VDrect; + SkImage* sourceImage; //copy either from the atlas or from a standalone surface + if (entry.surface) { + if (!fitInAtlas(currentVDRect.width(), currentVDRect.height())) { + continue; //don't even try to repack huge VD + } + sourceImage = entry.surface->makeImageSnapshot().get(); + } else { + sourceImage = sourceImageAtlas; + } + size_t VDRectArea = currentVDRect.width()*currentVDRect.height(); + SkIPoint16 pos; + if (canvas && mRectanizer->addRect(currentVDRect.width(), currentVDRect.height(), &pos)) { + SkRect newRect = SkRect::MakeXYWH(pos.fX, pos.fY, currentVDRect.width(), + currentVDRect.height()); + canvas->drawImageRect(sourceImage, currentVDRect, newRect, nullptr); + entry.VDrect = newRect; + entry.rect = newRect; + if (entry.surface) { + // A rectangle moved from a standalone surface to the atlas. + entry.surface = nullptr; + mPixelUsedByVDs += VDRectArea; + } + } else { + // Repack failed for this item. If it is not already, store it in a standalone + // surface. + if (!entry.surface) { + // A rectangle moved from an atlas to a standalone surface. + mPixelUsedByVDs -= VDRectArea; + SkRect newRect = SkRect::MakeWH(currentVDRect.width(), + currentVDRect.height()); + entry.surface = createSurface(newRect.width(), newRect.height(), context); + auto tempCanvas = entry.surface->getCanvas(); + tempCanvas->clear(SK_ColorTRANSPARENT); + tempCanvas->drawImageRect(sourceImageAtlas, currentVDRect, newRect, nullptr); + entry.VDrect = newRect; + entry.rect = newRect; + } + } + } + mPixelAllocated = mPixelUsedByVDs; + context->flush(); + mSurface = newSurface; + mConsecutiveFailures = 0; +} + +AtlasEntry VectorDrawableAtlas::requestNewEntry(int width, int height, GrContext* context) { + AtlasEntry result; + if (width <= 0 || height <= 0) { + return result; + } + + if (mSurface) { + const size_t area = width*height; + + // Use a rectanizer to allocate unused space from the atlas surface. + bool notTooBig = fitInAtlas(width, height); + SkIPoint16 pos; + if (notTooBig && mRectanizer->addRect(width, height, &pos)) { + mPixelUsedByVDs += area; + mPixelAllocated += area; + result.rect = SkRect::MakeXYWH(pos.fX, pos.fY, width, height); + result.surface = mSurface; + auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, nullptr); + CacheEntry* entry = &(*eraseIt); + entry->eraseIt = eraseIt; + result.key = reinterpret_cast<AtlasKey>(entry); + mConsecutiveFailures = 0; + return result; + } + + // Try to reuse atlas memory from rectangles freed by "releaseEntry". + auto freeRectIt = mFreeRects.lower_bound(area); + while (freeRectIt != mFreeRects.end()) { + SkRect& freeRect = freeRectIt->second; + if (freeRect.width() >= width && freeRect.height() >= height) { + result.rect = SkRect::MakeXYWH(freeRect.fLeft, freeRect.fTop, width, height); + result.surface = mSurface; + auto eraseIt = mRects.emplace(mRects.end(), result.rect, freeRect, nullptr); + CacheEntry* entry = &(*eraseIt); + entry->eraseIt = eraseIt; + result.key = reinterpret_cast<AtlasKey>(entry); + mPixelUsedByVDs += area; + mFreeRects.erase(freeRectIt); + mConsecutiveFailures = 0; + return result; + } + freeRectIt++; + } + + if (notTooBig && mConsecutiveFailures <= MAX_CONSECUTIVE_FAILURES) { + mConsecutiveFailures++; + } + } + + // Allocate a surface for a rectangle that is too big or if atlas is full. + if (nullptr != context) { + result.rect = SkRect::MakeWH(width, height); + result.surface = createSurface(width, height, context); + auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, result.surface); + CacheEntry* entry = &(*eraseIt); + entry->eraseIt = eraseIt; + result.key = reinterpret_cast<AtlasKey>(entry); + } + + return result; +} + +AtlasEntry VectorDrawableAtlas::getEntry(AtlasKey atlasKey) { + AtlasEntry result; + if (INVALID_ATLAS_KEY != atlasKey) { + CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey); + result.rect = entry->VDrect; + result.surface = entry->surface; + if (!result.surface) { + result.surface = mSurface; + } + result.key = atlasKey; + } + return result; +} + +void VectorDrawableAtlas::releaseEntry(AtlasKey atlasKey) { + if (INVALID_ATLAS_KEY != atlasKey) { + CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey); + if (!entry->surface) { + // Store freed atlas rectangles in "mFreeRects" and try to reuse them later, when atlas + // is full. + SkRect& removedRect = entry->rect; + size_t rectArea = removedRect.width()*removedRect.height(); + mFreeRects.emplace(rectArea, removedRect); + SkRect& removedVDRect = entry->VDrect; + size_t VDRectArea = removedVDRect.width()*removedVDRect.height(); + mPixelUsedByVDs -= VDRectArea; + mConsecutiveFailures = 0; + } + auto eraseIt = entry->eraseIt; + mRects.erase(eraseIt); + } +} + +sk_sp<SkSurface> VectorDrawableAtlas::createSurface(int width, int height, GrContext* context) { +#ifndef ANDROID_ENABLE_LINEAR_BLENDING + sk_sp<SkColorSpace> colorSpace = nullptr; +#else + sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); +#endif + SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace); + return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info); +} + +void VectorDrawableAtlas::setStorageMode(StorageMode mode) { + mStorageMode = mode; + if (StorageMode::disallowSharedSurface == mStorageMode && mSurface) { + mSurface.reset(); + mRectanizer.reset(); + mFreeRects.clear(); + } +} + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/VectorDrawableAtlas.h b/libs/hwui/pipeline/skia/VectorDrawableAtlas.h new file mode 100644 index 000000000000..496c55742748 --- /dev/null +++ b/libs/hwui/pipeline/skia/VectorDrawableAtlas.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2017 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 <map> +#include <SkSurface.h> +#include <utils/FatVector.h> +#include <utils/RefBase.h> +#include <list> + +class GrRectanizer; + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +typedef uintptr_t AtlasKey; + +#define INVALID_ATLAS_KEY 0 + +struct AtlasEntry { + sk_sp<SkSurface> surface; + SkRect rect; + AtlasKey key = INVALID_ATLAS_KEY; +}; + +/** + * VectorDrawableAtlas provides offscreen buffers used to draw VD and AnimatedVD. + * VectorDrawableAtlas can allocate a standalone surface or provide a subrect from a shared surface. + * VectorDrawableAtlas is owned by the CacheManager and weak pointers are kept by each + * VectorDrawable that is using it. VectorDrawableAtlas and its surface can be deleted at any time, + * except during a renderFrame call. VectorDrawable does not contain a pointer to atlas SkSurface + * nor any coordinates into the atlas, but instead holds a rectangle "id", which is resolved only + * when drawing. This design makes VectorDrawableAtlas free to move the data internally. + * At draw time a VectorDrawable may find, that its atlas has been deleted, which will make it + * draw in a standalone cache surface not part of an atlas. In this case VD won't use + * VectorDrawableAtlas until the next frame. + * VectorDrawableAtlas tries to fit VDs in the atlas SkSurface. If there is not enough space in + * the atlas, VectorDrawableAtlas creates a standalone surface for each VD. + * When a VectorDrawable is deleted, it invokes VectorDrawableAtlas::releaseEntry, which is keeping + * track of free spaces and allow to reuse the surface for another VD. + */ + //TODO: Check if not using atlas for AnimatedVD is more efficient. + //TODO: For low memory situations, when there are no paint effects in VD, we may render without an + //TODO: offscreen surface. +class VectorDrawableAtlas : public virtual RefBase { +public: + enum class StorageMode { + allowSharedSurface, + disallowSharedSurface + }; + + VectorDrawableAtlas(size_t surfaceArea, + StorageMode storageMode = StorageMode::allowSharedSurface); + + /** + * "prepareForDraw" may allocate a new surface if needed. It may schedule to repack the + * atlas at a later time. + */ + void prepareForDraw(GrContext* context); + + /** + * Repack the atlas if needed, by moving used rectangles into a new atlas surface. + * The goal of repacking is to fix a fragmented atlas. + */ + void repackIfNeeded(GrContext* context); + + /** + * Returns true if atlas is fragmented and repack is needed. + */ + bool isFragmented(); + + /** + * "requestNewEntry" is called by VectorDrawable to allocate a new rectangle area from the atlas + * or create a standalone surface if atlas is full. + * On success it returns a non-negative unique id, which can be used later with "getEntry" and + * "releaseEntry". + */ + AtlasEntry requestNewEntry(int width, int height, GrContext* context); + + /** + * "getEntry" extracts coordinates and surface of a previously created rectangle. + * "atlasKey" is an unique id created by "requestNewEntry". Passing a non-existing "atlasKey" is + * causing an undefined behaviour. + * On success it returns a rectangle Id -> may be same or different from "atlasKey" if + * implementation decides to move the record internally. + */ + AtlasEntry getEntry(AtlasKey atlasKey); + + /** + * "releaseEntry" is invoked when a VectorDrawable is deleted. Passing a non-existing "atlasKey" + * is causing an undefined behaviour. + */ + void releaseEntry(AtlasKey atlasKey); + + void setStorageMode(StorageMode mode); + +private: + struct CacheEntry { + CacheEntry(const SkRect& newVDrect, const SkRect& newRect, + const sk_sp<SkSurface>& newSurface) + : VDrect(newVDrect) + , rect(newRect) + , surface(newSurface) { } + + /** + * size and position of VectorDrawable into the atlas or in "this.surface" + */ + SkRect VDrect; + + /** + * rect allocated in atlas surface or "this.surface". It may be bigger than "VDrect" + */ + SkRect rect; + + /** + * this surface is used if atlas is full or VD is too big + */ + sk_sp<SkSurface> surface; + + /** + * iterator is used to delete self with a constant complexity (without traversing the list) + */ + std::list<CacheEntry>::iterator eraseIt; + }; + + /** + * atlas surface shared by all VDs + */ + sk_sp<SkSurface> mSurface; + + std::unique_ptr<GrRectanizer> mRectanizer; + const int mWidth; + const int mHeight; + + /** + * "mRects" keeps records only for rectangles used by VDs. List has nice properties: constant + * complexity to insert and erase and references are not invalidated by insert/erase. + */ + std::list<CacheEntry> mRects; + + /** + * Rectangles freed by "releaseEntry" are removed from "mRects" and added to "mFreeRects". + * "mFreeRects" is using for an index the rectangle area. There could be more than one free + * rectangle with the same area, which is the reason to use "multimap" instead of "map". + */ + std::multimap<size_t, SkRect> mFreeRects; + + /** + * area in atlas used by VectorDrawables (area in standalone surface not counted) + */ + int mPixelUsedByVDs = 0; + + /** + * area allocated in mRectanizer + */ + int mPixelAllocated = 0; + + /** + * Consecutive times we had to allocate standalone surfaces, because atlas was full. + */ + int mConsecutiveFailures = 0; + + /** + * mStorageMode allows using a shared surface to store small vector drawables. + * Using a shared surface can boost the performance by allowing GL ops to be batched, but may + * consume more memory. + */ + StorageMode mStorageMode; + + sk_sp<SkSurface> createSurface(int width, int height, GrContext* context); + + inline bool fitInAtlas(int width, int height) { + return 2*width < mWidth && 2*height < mHeight; + } + + void repack(GrContext* context); + + static bool compareCacheEntry(const CacheEntry& first, const CacheEntry& second); +}; + +} /* namespace skiapipeline */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp index 8865c6efce8c..b1ca4a248a80 100644 --- a/libs/hwui/renderstate/Blend.cpp +++ b/libs/hwui/renderstate/Blend.cpp @@ -118,7 +118,7 @@ void Blend::getFactors(SkBlendMode mode, ModeOrderSwap modeUsage, GLenum* outSrc } void Blend::setFactors(GLenum srcMode, GLenum dstMode) { - if (srcMode == GL_ZERO && dstMode == GL_ZERO) { + if ((srcMode == GL_ZERO || srcMode == GL_ONE) && dstMode == GL_ZERO) { // disable blending if (mEnabled) { glDisable(GL_BLEND); diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h index ec0e114c998f..a9de24631340 100644 --- a/libs/hwui/renderstate/Blend.h +++ b/libs/hwui/renderstate/Blend.h @@ -40,6 +40,14 @@ public: GLenum* outSrc, GLenum* outDst); void setFactors(GLenum src, GLenum dst); + bool getEnabled() { + return mEnabled; + } + void getFactors(GLenum* src, GLenum* dst) { + *src = mSrcMode; + *dst = mDstMode; + } + void dump(); private: Blend(); diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp index 90b27c8d8fb0..ea292d678c67 100644 --- a/libs/hwui/renderstate/OffscreenBufferPool.cpp +++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp @@ -17,7 +17,6 @@ #include "OffscreenBufferPool.h" #include "Caches.h" -#include "Properties.h" #include "renderstate/RenderState.h" #include "utils/FatVector.h" #include "utils/TraceUtils.h" @@ -118,7 +117,8 @@ OffscreenBuffer::~OffscreenBuffer() { /////////////////////////////////////////////////////////////////////////////// OffscreenBufferPool::OffscreenBufferPool() - : mMaxSize(Properties::layerPoolSize) { + // 4 screen-sized RGBA_8888 textures + : mMaxSize(DeviceInfo::multiplyByResolution(4 * 4)) { } OffscreenBufferPool::~OffscreenBufferPool() { @@ -179,8 +179,9 @@ OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer, layer->region.clear(); return layer; } + bool wideColorGamut = layer->wideColorGamut; putOrDelete(layer); - return get(renderState, width, height, layer->wideColorGamut); + return get(renderState, width, height, wideColorGamut); } void OffscreenBufferPool::dump() { diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index 2c92924cc12c..5fc5cb275741 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -53,6 +53,11 @@ void RenderState::onGLContextCreated() { mScissor = new Scissor(); mStencil = new Stencil(); + // Deferred because creation needs GL context for texture limits + if (!mLayerPool) { + mLayerPool = new OffscreenBufferPool(); + } + // This is delayed because the first access of Caches makes GL calls if (!mCaches) { mCaches = &Caches::createInstance(*this); @@ -67,7 +72,7 @@ static void layerLostGlContext(Layer* layer) { } void RenderState::onGLContextDestroyed() { - mLayerPool.clear(); + mLayerPool->clear(); // TODO: reset all cached state in state objects std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext); @@ -100,7 +105,7 @@ static void layerDestroyedVkContext(Layer* layer) { } void RenderState::onVkContextDestroyed() { - mLayerPool.clear(); + mLayerPool->clear(); std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerDestroyedVkContext); GpuMemoryTracker::onGpuContextDestroyed(); } @@ -116,10 +121,10 @@ void RenderState::flush(Caches::FlushMode mode) { case Caches::FlushMode::Moderate: // fall through case Caches::FlushMode::Layers: - mLayerPool.clear(); + if (mLayerPool) mLayerPool->clear(); break; } - mCaches->flush(mode); + if (mCaches) mCaches->flush(mode); } void RenderState::onBitmapDestroyed(uint32_t pixelRefId) { @@ -257,7 +262,8 @@ void RenderState::postDecStrong(VirtualLightRefBase* object) { // Render /////////////////////////////////////////////////////////////////////////////// -void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix) { +void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix, + bool overrideDisableBlending) { const Glop::Mesh& mesh = glop.mesh; const Glop::Mesh::Vertices& vertices = mesh.vertices; const Glop::Mesh::Indices& indices = mesh.indices; @@ -412,7 +418,11 @@ void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix) { // ------------------------------------ // ---------- GL state setup ---------- // ------------------------------------ - blend().setFactors(glop.blend.src, glop.blend.dst); + if (CC_UNLIKELY(overrideDisableBlending)) { + blend().setFactors(GL_ZERO, GL_ZERO); + } else { + blend().setFactors(glop.blend.src, glop.blend.dst); + } GL_CHECKPOINT(MODERATE); diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index 4b7a86580621..315fa2db6878 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -106,14 +106,14 @@ public: // more thinking... void postDecStrong(VirtualLightRefBase* object); - void render(const Glop& glop, const Matrix4& orthoMatrix); + void render(const Glop& glop, const Matrix4& orthoMatrix, bool overrideDisableBlending); Blend& blend() { return *mBlend; } MeshState& meshState() { return *mMeshState; } Scissor& scissor() { return *mScissor; } Stencil& stencil() { return *mStencil; } - OffscreenBufferPool& layerPool() { return mLayerPool; } + OffscreenBufferPool& layerPool() { return *mLayerPool; } GrContext* getGrContext() const; @@ -136,7 +136,7 @@ private: Scissor* mScissor = nullptr; Stencil* mStencil = nullptr; - OffscreenBufferPool mLayerPool; + OffscreenBufferPool* mLayerPool = nullptr; std::set<Layer*> mActiveLayers; std::set<DeferredLayerUpdater*> mActiveLayerUpdaters; diff --git a/libs/hwui/renderstate/Stencil.cpp b/libs/hwui/renderstate/Stencil.cpp index d25ad514e892..f59442196af1 100644 --- a/libs/hwui/renderstate/Stencil.cpp +++ b/libs/hwui/renderstate/Stencil.cpp @@ -47,7 +47,7 @@ uint8_t Stencil::getStencilSize() { */ GLenum Stencil::getLayerStencilFormat() { #if !DEBUG_STENCIL - const Extensions& extensions = Caches::getInstance().extensions(); + const Extensions& extensions = DeviceInfo::get()->extensions(); if (extensions.has4BitStencil()) { return GL_STENCIL_INDEX4_OES; } diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index f0d6b3860938..55694d046c2f 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -43,7 +43,8 @@ namespace renderthread { CacheManager::CacheManager(const DisplayInfo& display) : mMaxSurfaceArea(display.w * display.h) { - mVectorDrawableAtlas.reset(new VectorDrawableAtlas); + mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas(mMaxSurfaceArea/2, + skiapipeline::VectorDrawableAtlas::StorageMode::allowSharedSurface); } void CacheManager::reset(GrContext* context) { @@ -61,7 +62,7 @@ void CacheManager::reset(GrContext* context) { void CacheManager::destroy() { // cleanup any caches here as the GrContext is about to go away... mGrContext.reset(nullptr); - mVectorDrawableAtlas.reset(new VectorDrawableAtlas); + mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas(mMaxSurfaceArea/2); } void CacheManager::updateContextCacheSizes() { @@ -104,7 +105,7 @@ void CacheManager::trimMemory(TrimMemoryMode mode) { switch (mode) { case TrimMemoryMode::Complete: - mVectorDrawableAtlas.reset(new VectorDrawableAtlas); + mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas(mMaxSurfaceArea/2); mGrContext->freeGpuResources(); break; case TrimMemoryMode::UiHidden: @@ -121,24 +122,14 @@ void CacheManager::trimStaleResources() { mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30)); } -VectorDrawableAtlas* CacheManager::acquireVectorDrawableAtlas() { +sp<skiapipeline::VectorDrawableAtlas> CacheManager::acquireVectorDrawableAtlas() { LOG_ALWAYS_FATAL_IF(mVectorDrawableAtlas.get() == nullptr); LOG_ALWAYS_FATAL_IF(mGrContext == nullptr); /** - * TODO LIST: - * 1) compute the atlas based on the surfaceArea surface - * 2) identify a way to reuse cache entries - * 3) add ability to repack the cache? - * 4) define memory conditions where we clear the cache (e.g. surface->reset()) + * TODO: define memory conditions where we clear the cache (e.g. surface->reset()) */ - - return mVectorDrawableAtlas.release(); -} -void CacheManager::releaseVectorDrawableAtlas(VectorDrawableAtlas* atlas) { - LOG_ALWAYS_FATAL_IF(mVectorDrawableAtlas.get() != nullptr); - mVectorDrawableAtlas.reset(atlas); - mVectorDrawableAtlas->isNewAtlas = false; + return mVectorDrawableAtlas; } void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) { diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index 43d58f2d58a8..90362f33358d 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -22,6 +22,7 @@ #include <ui/DisplayInfo.h> #include <utils/String8.h> #include <vector> +#include "pipeline/skia/VectorDrawableAtlas.h" namespace android { @@ -36,11 +37,6 @@ namespace renderthread { class IRenderPipeline; class RenderThread; -struct VectorDrawableAtlas { - sk_sp<SkSurface> surface; - bool isNewAtlas = true; -}; - class CacheManager { public: enum class TrimMemoryMode { @@ -53,8 +49,7 @@ public: void trimStaleResources(); void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr); - VectorDrawableAtlas* acquireVectorDrawableAtlas(); - void releaseVectorDrawableAtlas(VectorDrawableAtlas*); + sp<skiapipeline::VectorDrawableAtlas> acquireVectorDrawableAtlas(); size_t getCacheSize() const { return mMaxResourceBytes; } size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; } @@ -81,7 +76,7 @@ private: size_t surfaceArea = 0; }; - std::unique_ptr<VectorDrawableAtlas> mVectorDrawableAtlas; + sp<skiapipeline::VectorDrawableAtlas> mVectorDrawableAtlas; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 779924883016..5d7f5948b0ec 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -142,8 +142,8 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, : mRenderThread(thread) , mOpaque(!translucent) , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) - , mJankTracker(thread.mainDisplayInfo()) - , mProfiler(mFrames) + , mJankTracker(&thread.globalProfileData(), thread.mainDisplayInfo()) + , mProfiler(mJankTracker.frames()) , mContentDrawBounds(0, 0, 0, 0) , mRenderPipeline(std::move(renderPipeline)) { rootRenderNode->makeRoot(); @@ -321,7 +321,7 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, // If the previous frame was dropped we don't need to hold onto it, so // just keep using the previous frame's structure instead if (!wasSkipped(mCurrentFrameInfo)) { - mCurrentFrameInfo = &mFrames.next(); + mCurrentFrameInfo = mJankTracker.startFrame(); } mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo); mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued; @@ -485,8 +485,7 @@ void CanvasContext::draw() { } #endif - mJankTracker.addFrame(*mCurrentFrameInfo); - mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo); + mJankTracker.finishFrame(*mCurrentFrameInfo); if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) { mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data()); } @@ -625,30 +624,12 @@ DeferredLayerUpdater* CanvasContext::createTextureLayer() { } void CanvasContext::dumpFrames(int fd) { - mJankTracker.dump(fd); - FILE* file = fdopen(fd, "a"); - fprintf(file, "\n\n---PROFILEDATA---\n"); - for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) { - fprintf(file, "%s", FrameInfoNames[i].c_str()); - fprintf(file, ","); - } - for (size_t i = 0; i < mFrames.size(); i++) { - FrameInfo& frame = mFrames[i]; - if (frame[FrameInfoIndex::SyncStart] == 0) { - continue; - } - fprintf(file, "\n"); - for (int i = 0; i < static_cast<int>(FrameInfoIndex::NumIndexes); i++) { - fprintf(file, "%" PRId64 ",", frame[i]); - } - } - fprintf(file, "\n---PROFILEDATA---\n\n"); - fflush(file); + mJankTracker.dumpStats(fd); + mJankTracker.dumpFrames(fd); } void CanvasContext::resetFrameStats() { - mFrames.clear(); - mRenderThread.jankTracker().reset(); + mJankTracker.reset(); } void CanvasContext::setName(const std::string&& name) { diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index b1f405040a59..4a5b2c72b02a 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -29,7 +29,6 @@ #include "RenderNode.h" #include "thread/Task.h" #include "thread/TaskProcessor.h" -#include "utils/RingBuffer.h" #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" @@ -163,8 +162,8 @@ public: void addRenderNode(RenderNode* node, bool placeFront); void removeRenderNode(RenderNode* node); - void setContentDrawBounds(int left, int top, int right, int bottom) { - mContentDrawBounds.set(left, top, right, bottom); + void setContentDrawBounds(const Rect& bounds) { + mContentDrawBounds = bounds; } RenderState& getRenderState() { @@ -253,8 +252,6 @@ private: std::vector< sp<RenderNode> > mRenderNodes; FrameInfo* mCurrentFrameInfo = nullptr; - // Ring buffer large enough for 2 seconds worth of frames - RingBuffer<FrameInfo, 120> mFrames; std::string mName; JankTracker mJankTracker; FrameInfoVisualizer mProfiler; diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 7d641d3ac7c7..a097272df359 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -32,6 +32,7 @@ namespace renderthread { DrawFrameTask::DrawFrameTask() : mRenderThread(nullptr) , mContext(nullptr) + , mContentDrawBounds(0, 0, 0, 0) , mSyncResult(SyncResult::OK) { } @@ -123,6 +124,7 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { mLayers[i]->apply(); } mLayers.clear(); + mContext->setContentDrawBounds(mContentDrawBounds); mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode); // This is after the prepareTree so that any pending operations diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index fb480626d421..83ecb98f548f 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -61,6 +61,9 @@ public: virtual ~DrawFrameTask(); void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode); + void setContentDrawBounds(int left, int top, int right, int bottom) { + mContentDrawBounds.set(left, top, right, bottom); + } void pushLayerUpdate(DeferredLayerUpdater* layer); void removeLayerUpdate(DeferredLayerUpdater* layer); @@ -82,6 +85,7 @@ private: RenderThread* mRenderThread; CanvasContext* mContext; RenderNode* mTargetNode = nullptr; + Rect mContentDrawBounds; /********************************************* * Single frame data diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 0e676ee4f9db..16d77364942e 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -114,7 +114,7 @@ void EglManager::initialize() { // An Adreno driver bug is causing rendering problems for SkiaGL with // buffer age swap behavior (b/31957043). To temporarily workaround, // we will use preserved swap behavior. - if (Properties::useBufferAge && EglExtensions.bufferAge && !Properties::isSkiaEnabled()) { + if (Properties::useBufferAge && EglExtensions.bufferAge) { mSwapBehavior = SwapBehavior::BufferAge; } else { mSwapBehavior = SwapBehavior::Preserved; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 80c2955400d8..9048bd14b35c 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -21,6 +21,7 @@ #include "Properties.h" #include "Readback.h" #include "Rect.h" +#include "pipeline/skia/VectorDrawableAtlas.h" #include "renderthread/CanvasContext.h" #include "renderthread/EglManager.h" #include "renderthread/RenderTask.h" @@ -427,12 +428,12 @@ CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread, if (args->dumpFlags & DumpFlags::FrameStats) { args->context->dumpFrames(args->fd); } + if (args->dumpFlags & DumpFlags::JankStats) { + args->thread->globalProfileData()->dump(args->fd); + } if (args->dumpFlags & DumpFlags::Reset) { args->context->resetFrameStats(); } - if (args->dumpFlags & DumpFlags::JankStats) { - args->thread->jankTracker().dump(args->fd); - } return nullptr; } @@ -458,7 +459,7 @@ void RenderProxy::resetProfileInfo() { CREATE_BRIDGE2(frameTimePercentile, RenderThread* thread, int percentile) { return reinterpret_cast<void*>(static_cast<uintptr_t>( - args->thread->jankTracker().findPercentile(args->percentile))); + args->thread->globalProfileData()->findPercentile(args->percentile))); } uint32_t RenderProxy::frameTimePercentile(int p) { @@ -483,7 +484,7 @@ void RenderProxy::dumpGraphicsMemory(int fd) { } CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) { - args->thread->jankTracker().switchStorageToAshmem(args->fd); + args->thread->globalProfileData().switchStorageToAshmem(args->fd); close(args->fd); return nullptr; } @@ -497,7 +498,7 @@ void RenderProxy::setProcessStatsBuffer(int fd) { } CREATE_BRIDGE1(rotateProcessStatsBuffer, RenderThread* thread) { - args->thread->jankTracker().rotateStorage(); + args->thread->globalProfileData().rotateStorage(); return nullptr; } @@ -550,20 +551,8 @@ void RenderProxy::drawRenderNode(RenderNode* node) { staticPostAndWait(task); } -CREATE_BRIDGE5(setContentDrawBounds, CanvasContext* context, int left, int top, - int right, int bottom) { - args->context->setContentDrawBounds(args->left, args->top, args->right, args->bottom); - return nullptr; -} - void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) { - SETUP_TASK(setContentDrawBounds); - args->context = mContext; - args->left = left; - args->top = top; - args->right = right; - args->bottom = bottom; - staticPostAndWait(task); + mDrawFrameTask.setContentDrawBounds(left, top, right, bottom); } CREATE_BRIDGE1(serializeDisplayListTree, CanvasContext* context) { @@ -718,6 +707,19 @@ void RenderProxy::post(RenderTask* task) { mRenderThread.queue(task); } +CREATE_BRIDGE1(repackVectorDrawableAtlas, RenderThread* thread) { + args->thread->cacheManager().acquireVectorDrawableAtlas()->repackIfNeeded( + args->thread->getGrContext()); + return nullptr; +} + +void RenderProxy::repackVectorDrawableAtlas() { + RenderThread& thread = RenderThread::getInstance(); + SETUP_TASK(repackVectorDrawableAtlas); + args->thread = &thread; + thread.queue(task); +} + void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) { void* retval; task->setReturnPtr(&retval); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 31f0ce67e1a7..06eaebd066ee 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -140,6 +140,9 @@ public: static void onBitmapDestroyed(uint32_t pixelRefId); ANDROID_API static void disableVsync(); + + static void repackVectorDrawableAtlas(); + private: RenderThread& mRenderThread; CanvasContext* mContext; diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 13af2c4d15e8..72a428f1c70c 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -200,13 +200,12 @@ void RenderThread::initThreadLocals() { initializeDisplayEventReceiver(); mEglManager = new EglManager(*this); mRenderState = new RenderState(*this); - mJankTracker = new JankTracker(mDisplayInfo); mVkManager = new VulkanManager(*this); mCacheManager = new CacheManager(mDisplayInfo); } void RenderThread::dumpGraphicsMemory(int fd) { - jankTracker().dump(fd); + globalProfileData()->dump(fd); String8 cachesOutput; String8 pipeline; diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index d9842572d7cd..bef47b3e27c5 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -97,7 +97,7 @@ public: TimeLord& timeLord() { return mTimeLord; } RenderState& renderState() const { return *mRenderState; } EglManager& eglManager() const { return *mEglManager; } - JankTracker& jankTracker() { return *mJankTracker; } + ProfileDataContainer& globalProfileData() { return mGlobalProfileData; } Readback& readback(); const DisplayInfo& mainDisplayInfo() { return mDisplayInfo; } @@ -160,7 +160,7 @@ private: RenderState* mRenderState; EglManager* mEglManager; - JankTracker* mJankTracker = nullptr; + ProfileDataContainer mGlobalProfileData; Readback* mReadback = nullptr; sk_sp<GrContext> mGrContext; diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index a745320ca884..2195143658d2 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -22,6 +22,7 @@ #include "renderstate/RenderState.h" #include "utils/FatVector.h" +#include <GrBackendSurface.h> #include <GrContext.h> #include <GrTypes.h> #include <vk/GrVkTypes.h> @@ -297,13 +298,9 @@ void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExt SkSurfaceProps props(0, kUnknown_SkPixelGeometry); - bool wantSRGB = VK_FORMAT_R8G8B8A8_SRGB == format; - GrPixelConfig config = wantSRGB ? kSRGBA_8888_GrPixelConfig : kRGBA_8888_GrPixelConfig; - // set up initial image layouts and create surfaces surface->mImageInfos = new VulkanSurface::ImageInfo[surface->mImageCount]; for (uint32_t i = 0; i < surface->mImageCount; ++i) { - GrBackendRenderTargetDesc desc; GrVkImageInfo info; info.fImage = surface->mImages[i]; info.fAlloc = { VK_NULL_HANDLE, 0, 0, 0 }; @@ -312,17 +309,11 @@ void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExt info.fFormat = format; info.fLevelCount = 1; - desc.fWidth = extent.width; - desc.fHeight = extent.height; - desc.fConfig = config; - desc.fOrigin = kTopLeft_GrSurfaceOrigin; - desc.fSampleCnt = 0; - desc.fStencilBits = 0; - desc.fRenderTargetHandle = (GrBackendObject) &info; + GrBackendRenderTarget backendRT(extent.width, extent.height, 0, 0, info); VulkanSurface::ImageInfo& imageInfo = surface->mImageInfos[i]; imageInfo.mSurface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), - desc, &props); + backendRT, kTopLeft_GrSurfaceOrigin, nullptr, &props); } SkASSERT(mCommandPool != VK_NULL_HANDLE); diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp index 87eaa6add459..afb1193657e1 100644 --- a/libs/hwui/service/GraphicsStatsService.cpp +++ b/libs/hwui/service/GraphicsStatsService.cpp @@ -38,11 +38,9 @@ constexpr int32_t sCurrentFileVersion = 1; constexpr int32_t sHeaderSize = 4; static_assert(sizeof(sCurrentFileVersion) == sHeaderSize, "Header size is wrong"); -constexpr int sHistogramSize = - std::tuple_size<decltype(ProfileData::frameCounts)>::value + - std::tuple_size<decltype(ProfileData::slowFrameCounts)>::value; +constexpr int sHistogramSize = ProfileData::HistogramSize(); -static void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, +static bool mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package, int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data); static void dumpAsTextToFd(service::GraphicsStatsProto* proto, int outFd); @@ -161,7 +159,7 @@ bool GraphicsStatsService::parseFromFile(const std::string& path, service::Graph return success; } -void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package, +bool mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::string& package, int versionCode, int64_t startTime, int64_t endTime, const ProfileData* data) { if (proto->stats_start() == 0 || proto->stats_start() > startTime) { proto->set_stats_start(startTime); @@ -172,54 +170,49 @@ void mergeProfileDataIntoProto(service::GraphicsStatsProto* proto, const std::st proto->set_package_name(package); proto->set_version_code(versionCode); auto summary = proto->mutable_summary(); - summary->set_total_frames(summary->total_frames() + data->totalFrameCount); - summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount); + summary->set_total_frames(summary->total_frames() + data->totalFrameCount()); + summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount()); summary->set_missed_vsync_count( - summary->missed_vsync_count() + data->jankTypeCounts[kMissedVsync]); + summary->missed_vsync_count() + data->jankTypeCount(kMissedVsync)); summary->set_high_input_latency_count( - summary->high_input_latency_count() + data->jankTypeCounts[kHighInputLatency]); + summary->high_input_latency_count() + data->jankTypeCount(kHighInputLatency)); summary->set_slow_ui_thread_count( - summary->slow_ui_thread_count() + data->jankTypeCounts[kSlowUI]); + summary->slow_ui_thread_count() + data->jankTypeCount(kSlowUI)); summary->set_slow_bitmap_upload_count( - summary->slow_bitmap_upload_count() + data->jankTypeCounts[kSlowSync]); + summary->slow_bitmap_upload_count() + data->jankTypeCount(kSlowSync)); summary->set_slow_draw_count( - summary->slow_draw_count() + data->jankTypeCounts[kSlowRT]); + summary->slow_draw_count() + data->jankTypeCount(kSlowRT)); bool creatingHistogram = false; if (proto->histogram_size() == 0) { proto->mutable_histogram()->Reserve(sHistogramSize); creatingHistogram = true; } else if (proto->histogram_size() != sHistogramSize) { - LOG_ALWAYS_FATAL("Histogram size mismatch, proto is %d expected %d", + ALOGE("Histogram size mismatch, proto is %d expected %d", proto->histogram_size(), sHistogramSize); + return false; } - for (size_t i = 0; i < data->frameCounts.size(); i++) { - service::GraphicsStatsHistogramBucketProto* bucket; - int32_t renderTime = JankTracker::frameTimeForFrameCountIndex(i); - if (creatingHistogram) { - bucket = proto->add_histogram(); - bucket->set_render_millis(renderTime); - } else { - bucket = proto->mutable_histogram(i); - LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime, - "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime); - } - bucket->set_frame_count(bucket->frame_count() + data->frameCounts[i]); - } - for (size_t i = 0; i < data->slowFrameCounts.size(); i++) { + int index = 0; + bool hitMergeError = false; + data->histogramForEach([&](ProfileData::HistogramEntry entry) { + if (hitMergeError) return; + service::GraphicsStatsHistogramBucketProto* bucket; - int32_t renderTime = JankTracker::frameTimeForSlowFrameCountIndex(i); if (creatingHistogram) { bucket = proto->add_histogram(); - bucket->set_render_millis(renderTime); + bucket->set_render_millis(entry.renderTimeMs); } else { - constexpr int offset = std::tuple_size<decltype(ProfileData::frameCounts)>::value; - bucket = proto->mutable_histogram(offset + i); - LOG_ALWAYS_FATAL_IF(bucket->render_millis() != renderTime, - "Frame time mistmatch %d vs. %d", bucket->render_millis(), renderTime); + bucket = proto->mutable_histogram(index); + if (bucket->render_millis() != static_cast<int32_t>(entry.renderTimeMs)) { + ALOGW("Frame time mistmatch %d vs. %u", bucket->render_millis(), entry.renderTimeMs); + hitMergeError = true; + return; + } } - bucket->set_frame_count(bucket->frame_count() + data->slowFrameCounts[i]); - } + bucket->set_frame_count(bucket->frame_count() + entry.frameCount); + index++; + }); + return !hitMergeError; } static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile) { @@ -236,9 +229,11 @@ static int32_t findPercentile(service::GraphicsStatsProto* proto, int percentile void dumpAsTextToFd(service::GraphicsStatsProto* proto, int fd) { // This isn't a full validation, just enough that we can deref at will - LOG_ALWAYS_FATAL_IF(proto->package_name().empty() - || !proto->has_summary(), "package_name() '%s' summary %d", - proto->package_name().c_str(), proto->has_summary()); + if (proto->package_name().empty() || !proto->has_summary()) { + ALOGW("Skipping dump, invalid package_name() '%s' or summary %d", + proto->package_name().c_str(), proto->has_summary()); + return; + } dprintf(fd, "\nPackage: %s", proto->package_name().c_str()); dprintf(fd, "\nVersion: %d", proto->version_code()); dprintf(fd, "\nStats since: %lldns", proto->stats_start()); @@ -269,14 +264,20 @@ void GraphicsStatsService::saveBuffer(const std::string& path, const std::string if (!parseFromFile(path, &statsProto)) { statsProto.Clear(); } - mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data); + if (!mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data)) { + return; + } // Although we might not have read any data from the file, merging the existing data // should always fully-initialize the proto - LOG_ALWAYS_FATAL_IF(!statsProto.IsInitialized(), "%s", - statsProto.InitializationErrorString().c_str()); - LOG_ALWAYS_FATAL_IF(statsProto.package_name().empty() - || !statsProto.has_summary(), "package_name() '%s' summary %d", - statsProto.package_name().c_str(), statsProto.has_summary()); + if (!statsProto.IsInitialized()) { + ALOGE("proto initialization error %s", statsProto.InitializationErrorString().c_str()); + return; + } + if (statsProto.package_name().empty() || !statsProto.has_summary()) { + ALOGE("missing package_name() '%s' summary %d", + statsProto.package_name().c_str(), statsProto.has_summary()); + return; + } int outFd = open(path.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0660); if (outFd <= 0) { int err = errno; @@ -327,8 +328,9 @@ void GraphicsStatsService::addToDump(Dump* dump, const std::string& path, const if (!path.empty() && !parseFromFile(path, &statsProto)) { statsProto.Clear(); } - if (data) { - mergeProfileDataIntoProto(&statsProto, package, versionCode, startTime, endTime, data); + if (data && !mergeProfileDataIntoProto( + &statsProto, package, versionCode, startTime, endTime, data)) { + return; } if (!statsProto.IsInitialized()) { ALOGW("Failed to load profile data from path '%s' and data %p", diff --git a/libs/hwui/tests/common/BitmapAllocationTestUtils.h b/libs/hwui/tests/common/BitmapAllocationTestUtils.h index 4892179f0d48..2988979cc266 100644 --- a/libs/hwui/tests/common/BitmapAllocationTestUtils.h +++ b/libs/hwui/tests/common/BitmapAllocationTestUtils.h @@ -41,7 +41,7 @@ public: SkBitmap skBitmap; SkImageInfo info = SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType); skBitmap.setInfo(info); - sk_sp<Bitmap> heapBitmap(Bitmap::allocateHeapBitmap(&skBitmap, nullptr)); + sk_sp<Bitmap> heapBitmap(Bitmap::allocateHeapBitmap(&skBitmap)); setup(skBitmap); return Bitmap::allocateHardwareBitmap(skBitmap); } @@ -73,4 +73,4 @@ public: } // namespace test } // namespace uirenderer -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/libs/hwui/tests/common/LeakChecker.cpp b/libs/hwui/tests/common/LeakChecker.cpp index d935382cc9a4..fe38ec958527 100644 --- a/libs/hwui/tests/common/LeakChecker.cpp +++ b/libs/hwui/tests/common/LeakChecker.cpp @@ -58,9 +58,8 @@ static void logUnreachable(initializer_list<UnreachableMemoryInfo> infolist) { if (merged.num_leaks) { cout << endl << "Leaked memory!" << endl; if (!merged.leaks[0].backtrace.num_frames) { - cout << "Re-run with 'setprop libc.debug.malloc.program hwui_unit_test'" - << endl << "and 'setprop libc.debug.malloc.options backtrace=8'" - << " to get backtraces" << endl; + cout << "Re-run with 'export LIBC_DEBUG_MALLOC_OPTIONS=backtrace' to get backtraces" + << endl; } cout << merged.ToString(false); } diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 98d5fb3af09a..f29363131030 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -189,7 +189,7 @@ public: static sk_sp<Bitmap> createBitmap(int width, int height, SkBitmap* outBitmap) { SkImageInfo info = SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType); outBitmap->setInfo(info); - return Bitmap::allocateHeapBitmap(outBitmap, nullptr); + return Bitmap::allocateHeapBitmap(outBitmap); } static sp<DeferredLayerUpdater> createTextureLayerUpdater( diff --git a/libs/hwui/tests/common/scenes/TvApp.cpp b/libs/hwui/tests/common/scenes/TvApp.cpp new file mode 100644 index 000000000000..04fc2d46f946 --- /dev/null +++ b/libs/hwui/tests/common/scenes/TvApp.cpp @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2017 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 "TestSceneBase.h" +#include "tests/common/BitmapAllocationTestUtils.h" +#include "SkBlendMode.h" + +class TvApp; +class TvAppNoRoundedCorner; +class TvAppColorFilter; +class TvAppNoRoundedCornerColorFilter; + +static bool _TvApp( + BitmapAllocationTestUtils::registerBitmapAllocationScene<TvApp>( + "tvapp", "A dense grid of cards:" + "with rounded corner, using overlay RenderNode for dimming.")); + +static bool _TvAppNoRoundedCorner( + BitmapAllocationTestUtils::registerBitmapAllocationScene<TvAppNoRoundedCorner>( + "tvapp_norc", "A dense grid of cards:" + "no rounded corner, using overlay RenderNode for dimming")); + +static bool _TvAppColorFilter( + BitmapAllocationTestUtils::registerBitmapAllocationScene<TvAppColorFilter>( + "tvapp_cf", "A dense grid of cards:" + "with rounded corner, using ColorFilter for dimming")); + +static bool _TvAppNoRoundedCornerColorFilter( + BitmapAllocationTestUtils::registerBitmapAllocationScene<TvAppNoRoundedCornerColorFilter>( + "tvapp_norc_cf", "A dense grid of cards:" + "no rounded corner, using ColorFilter for dimming")); + +class TvApp : public TestScene { +public: + TvApp(BitmapAllocationTestUtils::BitmapAllocator allocator) + : TestScene() + , mAllocator(allocator) { } + + sp<RenderNode> mBg; + std::vector<sp<RenderNode>> mCards; + std::vector<sp<RenderNode>> mInfoAreas; + std::vector<sp<RenderNode>> mImages; + std::vector<sp<RenderNode>> mOverlays; + std::vector<sk_sp<Bitmap>> mCachedBitmaps; + BitmapAllocationTestUtils::BitmapAllocator mAllocator; + sk_sp<Bitmap> mSingleBitmap; + int mSeed = 0; + int mSeed2 = 0; + + void createContent(int width, int height, Canvas& canvas) override { + mBg = createBitmapNode(canvas, 0xFF9C27B0, 0, 0, width, height); + canvas.drawRenderNode(mBg.get()); + + canvas.insertReorderBarrier(true); + mSingleBitmap = mAllocator(dp(160), dp(120), kRGBA_8888_SkColorType, + [](SkBitmap& skBitmap) { + skBitmap.eraseColor(0xFF0000FF); + }); + + for (int y = dp(18) - dp(178); y < height - dp(18); y += dp(178)) { + bool isFirstCard = true; + for (int x = dp(18); x < width - dp(18); x += dp(178)) { + sp<RenderNode> card = createCard(x, y, dp(160), dp(160), isFirstCard); + isFirstCard = false; + canvas.drawRenderNode(card.get()); + mCards.push_back(card); + } + } + canvas.insertReorderBarrier(false); + } + + void doFrame(int frameNr) override { + size_t numCards = mCards.size(); + for (size_t ci = 0; ci < numCards; ci++) { + updateCard(ci, frameNr); + } + } + +private: + sp<RenderNode> createBitmapNode(Canvas& canvas, SkColor color, int left, int top, + int width, int height) { + return TestUtils::createNode(left, top, left + width , top + height, + [this, width, height, color](RenderProperties& props, Canvas& canvas) { + sk_sp<Bitmap> bitmap = mAllocator(width, height, kRGBA_8888_SkColorType, + [color](SkBitmap& skBitmap) { + skBitmap.eraseColor(color); + }); + canvas.drawBitmap(*bitmap, 0, 0, nullptr); + }); + } + + sp<RenderNode> createSharedBitmapNode(Canvas& canvas, int left, int top, + int width, int height, sk_sp<Bitmap>bitmap) { + return TestUtils::createNode(left, top, left + width , top + height, + [bitmap](RenderProperties& props, Canvas& canvas) { + canvas.drawBitmap(*bitmap, 0, 0, nullptr); + }); + } + + sp<RenderNode> createInfoNode(Canvas& canvas, int left, int top, + int width, int height, const char* text, const char* text2) { + return TestUtils::createNode(left, top, left + width , top + height, + [text, text2](RenderProperties& props, Canvas& canvas) { + canvas.drawColor(0xFFFFEEEE, SkBlendMode::kSrcOver); + + SkPaint paint; + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setAntiAlias(true); + paint.setTextSize(24); + + paint.setColor(Color::Black); + TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 10, 30); + paint.setTextSize(20); + TestUtils::drawUtf8ToCanvas(&canvas, text2, paint, 10, 54); + + }); + } + + sp<RenderNode> createColorNode(Canvas& canvas, int left, int top, + int width, int height, SkColor color) { + return TestUtils::createNode(left, top, left + width , top + height, + [color](RenderProperties& props, Canvas& canvas) { + canvas.drawColor(color, SkBlendMode::kSrcOver); + }); + } + + virtual bool useSingleBitmap() { + return false; + } + + virtual float roundedCornerRadius() { + return dp(2); + } + + // when true, use overlay RenderNode for dimming, otherwise apply a ColorFilter to dim image + virtual bool useOverlay() { + return true; + } + + sp<RenderNode> createCard(int x, int y, int width, int height, bool selected) { + return TestUtils::createNode(x, y, x + width, y + height, + [width, height, selected, this](RenderProperties& props, Canvas& canvas) { + if (selected) { + props.setElevation(dp(16)); + props.setScaleX(1.2); + props.setScaleY(1.2); + } + props.mutableOutline().setRoundRect(0, 0, width, height, roundedCornerRadius(), 1); + props.mutableOutline().setShouldClip(true); + + sk_sp<Bitmap> bitmap = useSingleBitmap() ? mSingleBitmap + : mAllocator(width, dp(120), kRGBA_8888_SkColorType, [this](SkBitmap& skBitmap) { + skBitmap.eraseColor(0xFF000000 | ((mSeed << 3) & 0xFF)); + }); + sp<RenderNode> cardImage = createSharedBitmapNode(canvas, 0, 0, width, dp(120), + bitmap); + canvas.drawRenderNode(cardImage.get()); + mCachedBitmaps.push_back(bitmap); + mImages.push_back(cardImage); + + char buffer[128]; + sprintf(buffer, "Video %d-%d", mSeed, mSeed + 1); + mSeed++; + char buffer2[128]; + sprintf(buffer2, "Studio %d", mSeed2++); + sp<RenderNode> infoArea = createInfoNode(canvas, 0, dp(120), width, height, buffer, buffer2); + canvas.drawRenderNode(infoArea.get()); + mInfoAreas.push_back(infoArea); + + if (useOverlay()) { + sp<RenderNode> overlayColor = createColorNode(canvas, 0, 0, width, height, 0x00000000); + canvas.drawRenderNode(overlayColor.get()); + mOverlays.push_back(overlayColor); + } + }); + } + + void updateCard(int ci, int curFrame) { + // updating card's translation Y + sp<RenderNode> card = mCards[ci]; + card->setPropertyFieldsDirty(RenderNode::Y); + card->mutateStagingProperties().setTranslationY(curFrame % 150); + + // re-recording card's canvas, not necessary but to add some burden to CPU + std::unique_ptr<Canvas> cardcanvas(Canvas::create_recording_canvas( + card->stagingProperties().getWidth(), + card->stagingProperties().getHeight())); + sp<RenderNode> image = mImages[ci]; + sp<RenderNode> infoArea = mInfoAreas[ci]; + cardcanvas->drawRenderNode(infoArea.get()); + + if (useOverlay()) { + cardcanvas->drawRenderNode(image.get()); + // re-recording card overlay's canvas, animating overlay color alpha + sp<RenderNode> overlay = mOverlays[ci]; + std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas( + overlay->stagingProperties().getWidth(), + overlay->stagingProperties().getHeight())); + canvas->drawColor((curFrame % 150) << 24, SkBlendMode::kSrcOver); + overlay->setStagingDisplayList(canvas->finishRecording()); + cardcanvas->drawRenderNode(overlay.get()); + } else { + // re-recording image node's canvas, animating ColorFilter + std::unique_ptr<Canvas> canvas(Canvas::create_recording_canvas( + image->stagingProperties().getWidth(), + image->stagingProperties().getHeight())); + SkPaint paint; + sk_sp<SkColorFilter> filter(SkColorFilter::MakeModeFilter((curFrame % 150) << 24, + SkBlendMode::kSrcATop)); + paint.setColorFilter(filter); + sk_sp<Bitmap> bitmap = mCachedBitmaps[ci]; + canvas->drawBitmap(*bitmap, 0, 0, &paint); + image->setStagingDisplayList(canvas->finishRecording()); + cardcanvas->drawRenderNode(image.get()); + } + + card->setStagingDisplayList(cardcanvas->finishRecording()); + } +}; + +class TvAppNoRoundedCorner : public TvApp { +public: + TvAppNoRoundedCorner(BitmapAllocationTestUtils::BitmapAllocator allocator) + : TvApp(allocator) { } + +private: + + virtual float roundedCornerRadius() override { + return dp(0); + } +}; + +class TvAppColorFilter : public TvApp { +public: + TvAppColorFilter(BitmapAllocationTestUtils::BitmapAllocator allocator) + : TvApp(allocator) { } + +private: + + virtual bool useOverlay() override { + return false; + } +}; + +class TvAppNoRoundedCornerColorFilter : public TvApp { +public: + TvAppNoRoundedCornerColorFilter(BitmapAllocationTestUtils::BitmapAllocator allocator) + : TvApp(allocator) { } + +private: + + virtual float roundedCornerRadius() override { + return dp(0); + } + + virtual bool useOverlay() override { + return false; + } +}; + + diff --git a/libs/hwui/tests/unit/BakedOpRendererTests.cpp b/libs/hwui/tests/unit/BakedOpRendererTests.cpp index 603599ceb88a..38e106a8ab77 100644 --- a/libs/hwui/tests/unit/BakedOpRendererTests.cpp +++ b/libs/hwui/tests/unit/BakedOpRendererTests.cpp @@ -17,6 +17,7 @@ #include <gtest/gtest.h> #include <BakedOpRenderer.h> +#include <GlopBuilder.h> #include <tests/common/TestUtils.h> using namespace android::uirenderer; @@ -53,3 +54,53 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, startRepaintLayer_clear) { renderer.endLayer(); } } + +static void drawFirstOp(RenderState& renderState, int color, SkBlendMode mode) { + BakedOpRenderer renderer(Caches::getInstance(), renderState, true, false, sLightInfo); + + renderer.startFrame(100, 100, Rect(100, 100)); + SkPaint paint; + paint.setColor(color); + paint.setBlendMode(mode); + + Rect dest(0, 0, 100, 100); + Glop glop; + GlopBuilder(renderState, Caches::getInstance(), &glop) + .setRoundRectClipState(nullptr) + .setMeshUnitQuad() + .setFillPaint(paint, 1.0f) + .setTransform(Matrix4::identity(), TransformFlags::None) + .setModelViewMapUnitToRectSnap(dest) + .build(); + renderer.renderGlop(nullptr, nullptr, glop); + renderer.endFrame(Rect(100, 100)); +} + +static void verifyBlend(RenderState& renderState, GLenum expectedSrc, GLenum expectedDst) { + EXPECT_TRUE(renderState.blend().getEnabled()); + GLenum src; + GLenum dst; + renderState.blend().getFactors(&src, &dst); + EXPECT_EQ(expectedSrc, src); + EXPECT_EQ(expectedDst, dst); +} + +static void verifyBlendDisabled(RenderState& renderState) { + EXPECT_FALSE(renderState.blend().getEnabled()); +} + +RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, firstDrawBlend_clear) { + // initialize blend state to nonsense value + renderThread.renderState().blend().setFactors(GL_ONE, GL_ONE); + + drawFirstOp(renderThread.renderState(), 0xfeff0000, SkBlendMode::kClear); + verifyBlend(renderThread.renderState(), GL_ZERO, GL_ONE_MINUS_SRC_ALPHA); +} + +RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, firstDrawBlend_srcover) { + // initialize blend state to nonsense value + renderThread.renderState().blend().setFactors(GL_ONE, GL_ONE); + + drawFirstOp(renderThread.renderState(), 0xfeff0000, SkBlendMode::kSrcOver); + verifyBlendDisabled(renderThread.renderState()); +} diff --git a/libs/hwui/tests/unit/BitmapTests.cpp b/libs/hwui/tests/unit/BitmapTests.cpp deleted file mode 100644 index ed689bd6f174..000000000000 --- a/libs/hwui/tests/unit/BitmapTests.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 <gtest/gtest.h> - -#include "hwui/Bitmap.h" - -#include <SkBitmap.h> -#include <SkColorTable.h> -#include <SkImageInfo.h> - -#include <tests/common/TestUtils.h> - -using namespace android; -using namespace android::uirenderer; - -TEST(Bitmap, colorTableRefCounting) { - const SkPMColor c[] = { SkPackARGB32(0x80, 0x80, 0, 0) }; - sk_sp<SkColorTable> ctable = SkColorTable::Make(c, SK_ARRAY_COUNT(c)); - - SkBitmap* bm = new SkBitmap(); - bm->allocPixels(SkImageInfo::Make(1, 1, kIndex_8_SkColorType, kPremul_SkAlphaType), - ctable); - sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(bm, ctable); - EXPECT_FALSE(ctable->unique()); - delete bm; - bitmap.reset(); - EXPECT_TRUE(ctable->unique()); -} - diff --git a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp index f6f73377e147..fda3a79a69da 100644 --- a/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp +++ b/libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp @@ -61,17 +61,17 @@ TEST(GraphicsStats, findRootPath) { TEST(GraphicsStats, saveLoad) { std::string path = findRootPath() + "/test_saveLoad"; std::string packageName = "com.test.saveLoad"; - ProfileData mockData; - mockData.jankFrameCount = 20; - mockData.totalFrameCount = 100; - mockData.statStartTime = 10000; + MockProfileData mockData; + mockData.editJankFrameCount() = 20; + mockData.editTotalFrameCount() = 100; + mockData.editStatStartTime() = 10000; // Fill with patterned data we can recognize but which won't map to a // memset or basic for iteration count - for (size_t i = 0; i < mockData.frameCounts.size(); i++) { - mockData.frameCounts[i] = ((i % 10) + 1) * 2; + for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) { + mockData.editFrameCounts()[i] = ((i % 10) + 1) * 2; } - for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) { - mockData.slowFrameCounts[i] = (i % 5) + 1; + for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) { + mockData.editSlowFrameCounts()[i] = (i % 5) + 1; } GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData); service::GraphicsStatsProto loadedProto; @@ -87,17 +87,17 @@ TEST(GraphicsStats, saveLoad) { ASSERT_TRUE(loadedProto.has_summary()); EXPECT_EQ(20, loadedProto.summary().janky_frames()); EXPECT_EQ(100, loadedProto.summary().total_frames()); - EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(), + EXPECT_EQ(mockData.editFrameCounts().size() + mockData.editSlowFrameCounts().size(), (size_t) loadedProto.histogram_size()); for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) { int expectedCount, expectedBucket; - if (i < mockData.frameCounts.size()) { + if (i < mockData.editFrameCounts().size()) { expectedCount = ((i % 10) + 1) * 2; - expectedBucket = JankTracker::frameTimeForFrameCountIndex(i); + expectedBucket = ProfileData::frameTimeForFrameCountIndex(i); } else { - int temp = i - mockData.frameCounts.size(); + int temp = i - mockData.editFrameCounts().size(); expectedCount = (temp % 5) + 1; - expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp); + expectedBucket = ProfileData::frameTimeForSlowFrameCountIndex(temp); } EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count()); EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis()); @@ -107,26 +107,26 @@ TEST(GraphicsStats, saveLoad) { TEST(GraphicsStats, merge) { std::string path = findRootPath() + "/test_merge"; std::string packageName = "com.test.merge"; - ProfileData mockData; - mockData.jankFrameCount = 20; - mockData.totalFrameCount = 100; - mockData.statStartTime = 10000; + MockProfileData mockData; + mockData.editJankFrameCount() = 20; + mockData.editTotalFrameCount() = 100; + mockData.editStatStartTime() = 10000; // Fill with patterned data we can recognize but which won't map to a // memset or basic for iteration count - for (size_t i = 0; i < mockData.frameCounts.size(); i++) { - mockData.frameCounts[i] = ((i % 10) + 1) * 2; + for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) { + mockData.editFrameCounts()[i] = ((i % 10) + 1) * 2; } - for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) { - mockData.slowFrameCounts[i] = (i % 5) + 1; + for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) { + mockData.editSlowFrameCounts()[i] = (i % 5) + 1; } GraphicsStatsService::saveBuffer(path, packageName, 5, 3000, 7000, &mockData); - mockData.jankFrameCount = 50; - mockData.totalFrameCount = 500; - for (size_t i = 0; i < mockData.frameCounts.size(); i++) { - mockData.frameCounts[i] = (i % 5) + 1; + mockData.editJankFrameCount() = 50; + mockData.editTotalFrameCount() = 500; + for (size_t i = 0; i < mockData.editFrameCounts().size(); i++) { + mockData.editFrameCounts()[i] = (i % 5) + 1; } - for (size_t i = 0; i < mockData.slowFrameCounts.size(); i++) { - mockData.slowFrameCounts[i] = ((i % 10) + 1) * 2; + for (size_t i = 0; i < mockData.editSlowFrameCounts().size(); i++) { + mockData.editSlowFrameCounts()[i] = ((i % 10) + 1) * 2; } GraphicsStatsService::saveBuffer(path, packageName, 5, 7050, 10000, &mockData); @@ -143,19 +143,19 @@ TEST(GraphicsStats, merge) { ASSERT_TRUE(loadedProto.has_summary()); EXPECT_EQ(20 + 50, loadedProto.summary().janky_frames()); EXPECT_EQ(100 + 500, loadedProto.summary().total_frames()); - EXPECT_EQ(mockData.frameCounts.size() + mockData.slowFrameCounts.size(), + EXPECT_EQ(mockData.editFrameCounts().size() + mockData.editSlowFrameCounts().size(), (size_t) loadedProto.histogram_size()); for (size_t i = 0; i < (size_t) loadedProto.histogram_size(); i++) { int expectedCount, expectedBucket; - if (i < mockData.frameCounts.size()) { + if (i < mockData.editFrameCounts().size()) { expectedCount = ((i % 10) + 1) * 2; expectedCount += (i % 5) + 1; - expectedBucket = JankTracker::frameTimeForFrameCountIndex(i); + expectedBucket = ProfileData::frameTimeForFrameCountIndex(i); } else { - int temp = i - mockData.frameCounts.size(); + int temp = i - mockData.editFrameCounts().size(); expectedCount = (temp % 5) + 1; expectedCount += ((temp % 10) + 1) * 2; - expectedBucket = JankTracker::frameTimeForSlowFrameCountIndex(temp); + expectedBucket = ProfileData::frameTimeForSlowFrameCountIndex(temp); } EXPECT_EQ(expectedCount, loadedProto.histogram().Get(i).frame_count()); EXPECT_EQ(expectedBucket, loadedProto.histogram().Get(i).render_millis()); diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp index 919852f6b2d7..308fef303740 100644 --- a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp +++ b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp @@ -74,8 +74,8 @@ RENDERTHREAD_TEST(OffscreenBufferPool, construct) { OffscreenBufferPool pool; EXPECT_EQ(0u, pool.getCount()) << "pool must be created empty"; EXPECT_EQ(0u, pool.getSize()) << "pool must be created empty"; - EXPECT_EQ((uint32_t) Properties::layerPoolSize, pool.getMaxSize()) - << "pool must read size from Properties"; + // TODO: Does this really make sense as a test? + EXPECT_EQ(DeviceInfo::multiplyByResolution(4 * 4), pool.getMaxSize()); } RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, getPutClear) { diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp index a3d5079c6ce9..85b12ba79a8c 100644 --- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp +++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp @@ -51,8 +51,6 @@ TEST(SkiaBehavior, CreateBitmapShader1x1) { SkShader::TileMode xy[2]; ASSERT_TRUE(s->isABitmap(&bitmap, nullptr, xy)) << "1x1 bitmap shader must query as bitmap shader"; - EXPECT_EQ(SkShader::kClamp_TileMode, xy[0]); - EXPECT_EQ(SkShader::kRepeat_TileMode, xy[1]); EXPECT_EQ(origBitmap.pixelRef(), bitmap.pixelRef()); } diff --git a/libs/hwui/tests/unit/VectorDrawableAtlasTests.cpp b/libs/hwui/tests/unit/VectorDrawableAtlasTests.cpp new file mode 100644 index 000000000000..17f8176bcfdc --- /dev/null +++ b/libs/hwui/tests/unit/VectorDrawableAtlasTests.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2017 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 <gtest/gtest.h> + +#include "tests/common/TestUtils.h" +#include <GrRectanizer.h> +#include "pipeline/skia/VectorDrawableAtlas.h" + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::skiapipeline; + +RENDERTHREAD_SKIA_PIPELINE_TEST(VectorDrawableAtlas, addGetRemove) { + VectorDrawableAtlas atlas(100*100); + atlas.prepareForDraw(renderThread.getGrContext()); + //create 150 rects 10x10, which won't fit in the atlas (atlas can fit no more than 100 rects) + const int MAX_RECTS = 150; + AtlasEntry VDRects[MAX_RECTS]; + + sk_sp<SkSurface> atlasSurface; + + //check we are able to allocate new rects + //check that rects in the atlas do not intersect + for (uint32_t i = 0; i < MAX_RECTS; i++) { + VDRects[i] = atlas.requestNewEntry(10, 10, renderThread.getGrContext()); + if (0 == i) { + atlasSurface = VDRects[0].surface; + } + ASSERT_TRUE(VDRects[i].key != INVALID_ATLAS_KEY); + ASSERT_TRUE(VDRects[i].surface.get() != nullptr); + ASSERT_TRUE(VDRects[i].rect.width() == 10 && VDRects[i].rect.height() == 10); + + //nothing in the atlas should intersect + if (atlasSurface.get() == VDRects[i].surface.get()) { + for (uint32_t j = 0; j < i; j++) { + if (atlasSurface.get() == VDRects[j].surface.get()) { + ASSERT_FALSE(VDRects[i].rect.intersect(VDRects[j].rect)); + } + } + } + } + + //first 1/3 rects should all be in the same surface + for (uint32_t i = 1; i < MAX_RECTS/3; i++) { + ASSERT_NE(VDRects[i].key, VDRects[0].key); + ASSERT_EQ(VDRects[i].surface.get(), atlasSurface.get()); + } + + //first rect is using atlas and last is a standalone surface + ASSERT_NE(VDRects[0].surface.get(), VDRects[MAX_RECTS-1].surface.get()); + + //check getEntry returns the same surfaces that we had created + for (uint32_t i = 0; i < MAX_RECTS; i++) { + auto VDRect = atlas.getEntry(VDRects[i].key); + ASSERT_TRUE(VDRect.key != INVALID_ATLAS_KEY); + ASSERT_EQ(VDRects[i].key, VDRect.key); + ASSERT_EQ(VDRects[i].surface.get(), VDRect.surface.get()); + ASSERT_EQ(VDRects[i].rect, VDRect.rect); + atlas.releaseEntry(VDRect.key); + } + + //check that any new rects will be allocated in the atlas, even that rectanizer is full. + //rects in the atlas should not intersect. + for (uint32_t i = 0; i < MAX_RECTS/3; i++) { + VDRects[i] = atlas.requestNewEntry(10, 10, renderThread.getGrContext()); + ASSERT_TRUE(VDRects[i].key != INVALID_ATLAS_KEY); + ASSERT_EQ(VDRects[i].surface.get(), atlasSurface.get()); + ASSERT_TRUE(VDRects[i].rect.width() == 10 && VDRects[i].rect.height() == 10); + for (uint32_t j = 0; j < i; j++) { + ASSERT_FALSE(VDRects[i].rect.intersect(VDRects[j].rect)); + } + } +} + + +RENDERTHREAD_SKIA_PIPELINE_TEST(VectorDrawableAtlas, disallowSharedSurface) { + VectorDrawableAtlas atlas(100*100); + //don't allow to use a shared surface + atlas.setStorageMode(VectorDrawableAtlas::StorageMode::disallowSharedSurface); + atlas.prepareForDraw(renderThread.getGrContext()); + //create 150 rects 10x10, which won't fit in the atlas (atlas can fit no more than 100 rects) + const int MAX_RECTS = 150; + AtlasEntry VDRects[MAX_RECTS]; + + //check we are able to allocate new rects + //check that rects in the atlas use unique surfaces + for (uint32_t i = 0; i < MAX_RECTS; i++) { + VDRects[i] = atlas.requestNewEntry(10, 10, renderThread.getGrContext()); + ASSERT_TRUE(VDRects[i].key != INVALID_ATLAS_KEY); + ASSERT_TRUE(VDRects[i].surface.get() != nullptr); + ASSERT_TRUE(VDRects[i].rect.width() == 10 && VDRects[i].rect.height() == 10); + + //nothing in the atlas should use the same surface + for (uint32_t j = 0; j < i; j++) { + ASSERT_NE(VDRects[i].surface.get(), VDRects[j].surface.get()); + } + } +} + +RENDERTHREAD_SKIA_PIPELINE_TEST(VectorDrawableAtlas, repack) { + VectorDrawableAtlas atlas(100*100); + ASSERT_FALSE(atlas.isFragmented()); + atlas.prepareForDraw(renderThread.getGrContext()); + ASSERT_FALSE(atlas.isFragmented()); + //create 150 rects 10x10, which won't fit in the atlas (atlas can fit no more than 100 rects) + const int MAX_RECTS = 150; + AtlasEntry VDRects[MAX_RECTS]; + + sk_sp<SkSurface> atlasSurface; + + //fill the atlas with check we are able to allocate new rects + for (uint32_t i = 0; i < MAX_RECTS; i++) { + VDRects[i] = atlas.requestNewEntry(10, 10, renderThread.getGrContext()); + if (0 == i) { + atlasSurface = VDRects[0].surface; + } + ASSERT_TRUE(VDRects[i].key != INVALID_ATLAS_KEY); + } + + ASSERT_FALSE(atlas.isFragmented()); + + //first 1/3 rects should all be in the same surface + for (uint32_t i = 1; i < MAX_RECTS/3; i++) { + ASSERT_NE(VDRects[i].key, VDRects[0].key); + ASSERT_EQ(VDRects[i].surface.get(), atlasSurface.get()); + } + + //release all entries + for (uint32_t i = 0; i < MAX_RECTS; i++) { + auto VDRect = atlas.getEntry(VDRects[i].key); + ASSERT_TRUE(VDRect.key != INVALID_ATLAS_KEY); + atlas.releaseEntry(VDRect.key); + } + + ASSERT_FALSE(atlas.isFragmented()); + + //allocate 4x4 rects, which will fragment the atlas badly, because each entry occupies a 10x10 + //area + for (uint32_t i = 0; i < 4*MAX_RECTS; i++) { + AtlasEntry entry = atlas.requestNewEntry(4, 4, renderThread.getGrContext()); + ASSERT_TRUE(entry.key != INVALID_ATLAS_KEY); + } + + ASSERT_TRUE(atlas.isFragmented()); + + atlas.repackIfNeeded(renderThread.getGrContext()); + + ASSERT_FALSE(atlas.isFragmented()); +}
\ No newline at end of file |