summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/hwui/Android.bp6
-rw-r--r--libs/hwui/BakedOpRenderer.cpp17
-rw-r--r--libs/hwui/Caches.cpp10
-rw-r--r--libs/hwui/Caches.h7
-rw-r--r--libs/hwui/DeviceInfo.cpp12
-rw-r--r--libs/hwui/DeviceInfo.h13
-rw-r--r--libs/hwui/FboCache.cpp2
-rw-r--r--libs/hwui/FontRenderer.cpp30
-rw-r--r--libs/hwui/GradientCache.cpp7
-rw-r--r--libs/hwui/GradientCache.h2
-rw-r--r--libs/hwui/JankTracker.cpp239
-rw-r--r--libs/hwui/JankTracker.h54
-rw-r--r--libs/hwui/OpenGLReadback.cpp19
-rw-r--r--libs/hwui/PatchCache.cpp2
-rw-r--r--libs/hwui/PathCache.cpp17
-rw-r--r--libs/hwui/ProfileData.cpp177
-rw-r--r--libs/hwui/ProfileData.h109
-rw-r--r--libs/hwui/ProfileDataContainer.cpp78
-rw-r--r--libs/hwui/ProfileDataContainer.h48
-rw-r--r--libs/hwui/ProgramCache.cpp2
-rw-r--r--libs/hwui/ProgramCache.h2
-rw-r--r--libs/hwui/Properties.cpp38
-rw-r--r--libs/hwui/Properties.h89
-rw-r--r--libs/hwui/RenderBufferCache.cpp10
-rw-r--r--libs/hwui/RenderNode.cpp3
-rw-r--r--libs/hwui/SkiaCanvas.cpp27
-rw-r--r--libs/hwui/SkiaCanvasProxy.cpp6
-rw-r--r--libs/hwui/TessellationCache.cpp2
-rw-r--r--libs/hwui/TextDropShadowCache.cpp2
-rw-r--r--libs/hwui/Texture.cpp9
-rw-r--r--libs/hwui/TextureCache.cpp10
-rw-r--r--libs/hwui/VectorDrawable.cpp115
-rw-r--r--libs/hwui/VectorDrawable.h59
-rw-r--r--libs/hwui/font/CacheTexture.cpp3
-rw-r--r--libs/hwui/font/FontUtil.h5
-rw-r--r--libs/hwui/hwui/Bitmap.cpp77
-rw-r--r--libs/hwui/hwui/Bitmap.h20
-rw-r--r--libs/hwui/hwui/Canvas.cpp41
-rw-r--r--libs/hwui/hwui/Paint.h13
-rw-r--r--libs/hwui/pipeline/skia/LayerDrawable.cpp12
-rw-r--r--libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp3
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp29
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp58
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp28
-rw-r--r--libs/hwui/pipeline/skia/VectorDrawableAtlas.cpp269
-rw-r--r--libs/hwui/pipeline/skia/VectorDrawableAtlas.h198
-rw-r--r--libs/hwui/renderstate/Blend.cpp2
-rw-r--r--libs/hwui/renderstate/Blend.h8
-rw-r--r--libs/hwui/renderstate/OffscreenBufferPool.cpp7
-rw-r--r--libs/hwui/renderstate/RenderState.cpp22
-rw-r--r--libs/hwui/renderstate/RenderState.h6
-rw-r--r--libs/hwui/renderstate/Stencil.cpp2
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp23
-rw-r--r--libs/hwui/renderthread/CacheManager.h11
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp33
-rw-r--r--libs/hwui/renderthread/CanvasContext.h7
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp2
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.h4
-rw-r--r--libs/hwui/renderthread/EglManager.cpp2
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp40
-rw-r--r--libs/hwui/renderthread/RenderProxy.h3
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp3
-rw-r--r--libs/hwui/renderthread/RenderThread.h4
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp15
-rw-r--r--libs/hwui/service/GraphicsStatsService.cpp94
-rw-r--r--libs/hwui/tests/common/BitmapAllocationTestUtils.h4
-rw-r--r--libs/hwui/tests/common/LeakChecker.cpp5
-rw-r--r--libs/hwui/tests/common/TestUtils.h2
-rw-r--r--libs/hwui/tests/common/scenes/TvApp.cpp274
-rw-r--r--libs/hwui/tests/unit/BakedOpRendererTests.cpp51
-rw-r--r--libs/hwui/tests/unit/BitmapTests.cpp43
-rw-r--r--libs/hwui/tests/unit/GraphicsStatsServiceTests.cpp64
-rw-r--r--libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp4
-rw-r--r--libs/hwui/tests/unit/SkiaBehaviorTests.cpp2
-rw-r--r--libs/hwui/tests/unit/VectorDrawableAtlasTests.cpp164
75 files changed, 2009 insertions, 871 deletions
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/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 9adf0538203e..63bf7bc443e0 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