Track texture memory globally
Also mostly consolidates texture creation
Change-Id: Ifea01303afda531dcec99b8fe2a0f64cf2f24420
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index bbfc022..cdd891f 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -53,6 +53,7 @@
FrameInfoVisualizer.cpp \
GammaFontRenderer.cpp \
GlopBuilder.cpp \
+ GpuMemoryTracker.cpp \
GradientCache.cpp \
Image.cpp \
Interpolator.cpp \
@@ -228,6 +229,7 @@
tests/unit/DamageAccumulatorTests.cpp \
tests/unit/DeviceInfoTests.cpp \
tests/unit/FatVectorTests.cpp \
+ tests/unit/GpuMemoryTrackerTests.cpp \
tests/unit/LayerUpdateQueueTests.cpp \
tests/unit/LinearAllocatorTests.cpp \
tests/unit/VectorDrawableTests.cpp \
diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp
index 41411a9..6afff1b 100644
--- a/libs/hwui/AssetAtlas.cpp
+++ b/libs/hwui/AssetAtlas.cpp
@@ -39,40 +39,22 @@
if (!mTexture) {
Caches& caches = Caches::getInstance();
mTexture = new Texture(caches);
- mTexture->width = buffer->getWidth();
- mTexture->height = buffer->getHeight();
+ mTexture->wrap(mImage->getTexture(),
+ buffer->getWidth(), buffer->getHeight(), GL_RGBA);
createEntries(caches, map, count);
}
} else {
ALOGW("Could not create atlas image");
- delete mImage;
- mImage = nullptr;
+ terminate();
}
-
- updateTextureId();
}
void AssetAtlas::terminate() {
- if (mImage) {
- delete mImage;
- mImage = nullptr;
- updateTextureId();
- }
-}
-
-
-void AssetAtlas::updateTextureId() {
- mTexture->id = mImage ? mImage->getTexture() : 0;
- if (mTexture->id) {
- // Texture ID changed, force-set to defaults to sync the wrapper & GL
- // state objects
- mTexture->setWrap(GL_CLAMP_TO_EDGE, false, true);
- mTexture->setFilter(GL_NEAREST, false, true);
- }
- for (size_t i = 0; i < mEntries.size(); i++) {
- AssetAtlas::Entry* entry = mEntries.valueAt(i);
- entry->texture->id = mTexture->id;
- }
+ delete mImage;
+ mImage = nullptr;
+ delete mTexture;
+ mTexture = nullptr;
+ mEntries.clear();
}
///////////////////////////////////////////////////////////////////////////////
@@ -80,13 +62,13 @@
///////////////////////////////////////////////////////////////////////////////
AssetAtlas::Entry* AssetAtlas::getEntry(const SkPixelRef* pixelRef) const {
- ssize_t index = mEntries.indexOfKey(pixelRef);
- return index >= 0 ? mEntries.valueAt(index) : nullptr;
+ auto result = mEntries.find(pixelRef);
+ return result != mEntries.end() ? result->second.get() : nullptr;
}
Texture* AssetAtlas::getEntryTexture(const SkPixelRef* pixelRef) const {
- ssize_t index = mEntries.indexOfKey(pixelRef);
- return index >= 0 ? mEntries.valueAt(index)->texture : nullptr;
+ auto result = mEntries.find(pixelRef);
+ return result != mEntries.end() ? result->second->texture : nullptr;
}
/**
@@ -94,7 +76,8 @@
* instead of applying the changes to the virtual textures.
*/
struct DelegateTexture: public Texture {
- DelegateTexture(Caches& caches, Texture* delegate): Texture(caches), mDelegate(delegate) { }
+ DelegateTexture(Caches& caches, Texture* delegate)
+ : Texture(caches), mDelegate(delegate) { }
virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false,
bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override {
@@ -111,8 +94,8 @@
}; // struct DelegateTexture
void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) {
- const float width = float(mTexture->width);
- const float height = float(mTexture->height);
+ const float width = float(mTexture->width());
+ const float height = float(mTexture->height());
for (int i = 0; i < count; ) {
SkPixelRef* pixelRef = reinterpret_cast<SkPixelRef*>(map[i++]);
@@ -133,13 +116,13 @@
Texture* texture = new DelegateTexture(caches, mTexture);
texture->blend = !SkAlphaTypeIsOpaque(pixelRef->info().alphaType());
- texture->width = pixelRef->info().width();
- texture->height = pixelRef->info().height();
+ texture->wrap(mTexture->id(), pixelRef->info().width(),
+ pixelRef->info().height(), mTexture->format());
- Entry* entry = new Entry(pixelRef, texture, mapper, *this);
+ std::unique_ptr<Entry> entry(new Entry(pixelRef, texture, mapper, *this));
texture->uvMapper = &entry->uvMapper;
- mEntries.add(entry->pixelRef, entry);
+ mEntries.emplace(entry->pixelRef, std::move(entry));
}
}
diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h
index a037725..75400ff 100644
--- a/libs/hwui/AssetAtlas.h
+++ b/libs/hwui/AssetAtlas.h
@@ -17,19 +17,17 @@
#ifndef ANDROID_HWUI_ASSET_ATLAS_H
#define ANDROID_HWUI_ASSET_ATLAS_H
-#include <GLES2/gl2.h>
-
-#include <ui/GraphicBuffer.h>
-
-#include <utils/KeyedVector.h>
-
-#include <cutils/compiler.h>
-
-#include <SkBitmap.h>
-
#include "Texture.h"
#include "UvMapper.h"
+#include <cutils/compiler.h>
+#include <GLES2/gl2.h>
+#include <ui/GraphicBuffer.h>
+#include <SkBitmap.h>
+
+#include <memory>
+#include <unordered_map>
+
namespace android {
namespace uirenderer {
@@ -71,6 +69,10 @@
return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey;
}
+ ~Entry() {
+ delete texture;
+ }
+
private:
/**
* The pixel ref that generated this atlas entry.
@@ -90,10 +92,6 @@
, atlas(atlas) {
}
- ~Entry() {
- delete texture;
- }
-
friend class AssetAtlas;
};
@@ -127,7 +125,7 @@
* Can return 0 if the atlas is not initialized.
*/
uint32_t getWidth() const {
- return mTexture ? mTexture->width : 0;
+ return mTexture ? mTexture->width() : 0;
}
/**
@@ -135,7 +133,7 @@
* Can return 0 if the atlas is not initialized.
*/
uint32_t getHeight() const {
- return mTexture ? mTexture->height : 0;
+ return mTexture ? mTexture->height() : 0;
}
/**
@@ -143,7 +141,7 @@
* Can return 0 if the atlas is not initialized.
*/
GLuint getTexture() const {
- return mTexture ? mTexture->id : 0;
+ return mTexture ? mTexture->id() : 0;
}
/**
@@ -160,7 +158,6 @@
private:
void createEntries(Caches& caches, int64_t* map, int count);
- void updateTextureId();
Texture* mTexture;
Image* mImage;
@@ -168,7 +165,7 @@
const bool mBlendKey;
const bool mOpaqueKey;
- KeyedVector<const SkPixelRef*, Entry*> mEntries;
+ std::unordered_map<const SkPixelRef*, std::unique_ptr<Entry>> mEntries;
}; // class AssetAtlas
}; // namespace uirenderer
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 5b34f6b..6b0c149 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -217,7 +217,7 @@
.setMeshTexturedUnitQuad(nullptr)
.setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+ .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height()))
.build();
renderer.renderGlop(state, glop);
}
@@ -337,7 +337,7 @@
static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state,
PathTexture& texture, const RecordedOp& op) {
- Rect dest(texture.width, texture.height);
+ Rect dest(texture.width(), texture.height());
dest.translate(texture.left - texture.offset,
texture.top - texture.offset);
Glop glop;
@@ -399,7 +399,7 @@
.setMeshTexturedUnitQuad(texture->uvMapper)
.setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha)
.setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewMapUnitToRectSnap(Rect(texture->width, texture->height))
+ .setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height()))
.build();
renderer.renderGlop(state, glop);
}
@@ -483,10 +483,10 @@
if (!texture) return;
const AutoTexture autoCleanup(texture);
- Rect uv(std::max(0.0f, op.src.left / texture->width),
- std::max(0.0f, op.src.top / texture->height),
- std::min(1.0f, op.src.right / texture->width),
- std::min(1.0f, op.src.bottom / texture->height));
+ Rect uv(std::max(0.0f, op.src.left / texture->width()),
+ std::max(0.0f, op.src.top / texture->height()),
+ std::min(1.0f, op.src.right / texture->width()),
+ std::min(1.0f, op.src.bottom / texture->height()));
const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType)
? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp
index 42fb66f..4fbff0d 100644
--- a/libs/hwui/BakedOpRenderer.cpp
+++ b/libs/hwui/BakedOpRenderer.cpp
@@ -48,7 +48,7 @@
// attach the texture to the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
- offscreenBuffer->texture.id, 0);
+ offscreenBuffer->texture.id(), 0);
LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED");
LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE,
"framebuffer incomplete!");
@@ -84,7 +84,7 @@
area.getWidth(), area.getHeight());
if (!area.isEmpty()) {
mCaches.textureState().activateTexture(0);
- mCaches.textureState().bindTexture(buffer->texture.id);
+ mCaches.textureState().bindTexture(buffer->texture.id());
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
area.left, mRenderTarget.viewportHeight - area.bottom,
@@ -272,7 +272,7 @@
OffscreenBuffer* layer = mRenderTarget.offscreenBuffer;
mRenderTarget.stencil = mCaches.renderBufferCache.get(
Stencil::getLayerStencilFormat(),
- layer->texture.width, layer->texture.height);
+ layer->texture.width(), layer->texture.height());
// stencil is bound + allocated - associate it with current FBO
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
GL_RENDERBUFFER, mRenderTarget.stencil->getName());
diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp
new file mode 100644
index 0000000..4fb5701
--- /dev/null
+++ b/libs/hwui/GpuMemoryTracker.cpp
@@ -0,0 +1,140 @@
+/*
+ * 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 "utils/StringUtils.h"
+#include "Texture.h"
+
+#include <cutils/compiler.h>
+#include <GpuMemoryTracker.h>
+#include <utils/Trace.h>
+#include <array>
+#include <sstream>
+#include <unordered_set>
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+pthread_t gGpuThread = 0;
+
+#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount)
+
+const char* TYPE_NAMES[] = {
+ "Texture",
+ "OffscreenBuffer",
+ "Layer",
+};
+
+struct TypeStats {
+ int totalSize = 0;
+ int count = 0;
+};
+
+static std::array<TypeStats, NUM_TYPES> gObjectStats;
+static std::unordered_set<GpuMemoryTracker*> gObjectSet;
+
+void GpuMemoryTracker::notifySizeChanged(int newSize) {
+ int delta = newSize - mSize;
+ mSize = newSize;
+ gObjectStats[static_cast<int>(mType)].totalSize += delta;
+}
+
+void GpuMemoryTracker::startTrackingObject() {
+ auto result = gObjectSet.insert(this);
+ LOG_ALWAYS_FATAL_IF(!result.second,
+ "startTrackingObject() on %p failed, already being tracked!", this);
+ gObjectStats[static_cast<int>(mType)].count++;
+}
+
+void GpuMemoryTracker::stopTrackingObject() {
+ size_t removed = gObjectSet.erase(this);
+ LOG_ALWAYS_FATAL_IF(removed != 1,
+ "stopTrackingObject removed %zd, is %p not being tracked?",
+ removed, this);
+ gObjectStats[static_cast<int>(mType)].count--;
+}
+
+void GpuMemoryTracker::onGLContextCreated() {
+ LOG_ALWAYS_FATAL_IF(gGpuThread != 0, "We already have a GL thread? "
+ "current = %lu, gl thread = %lu", pthread_self(), gGpuThread);
+ gGpuThread = pthread_self();
+}
+
+void GpuMemoryTracker::onGLContextDestroyed() {
+ gGpuThread = 0;
+ if (CC_UNLIKELY(gObjectSet.size() > 0)) {
+ std::stringstream os;
+ dump(os);
+ ALOGE("%s", os.str().c_str());
+ LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size());
+ }
+}
+
+void GpuMemoryTracker::dump() {
+ std::stringstream strout;
+ dump(strout);
+ ALOGD("%s", strout.str().c_str());
+}
+
+void GpuMemoryTracker::dump(std::ostream& stream) {
+ for (int type = 0; type < NUM_TYPES; type++) {
+ const TypeStats& stats = gObjectStats[type];
+ stream << TYPE_NAMES[type];
+ stream << " is using " << SizePrinter{stats.totalSize};
+ stream << ", count = " << stats.count;
+ stream << std::endl;
+ }
+}
+
+int GpuMemoryTracker::getInstanceCount(GpuObjectType type) {
+ return gObjectStats[static_cast<int>(type)].count;
+}
+
+int GpuMemoryTracker::getTotalSize(GpuObjectType type) {
+ return gObjectStats[static_cast<int>(type)].totalSize;
+}
+
+void GpuMemoryTracker::onFrameCompleted() {
+ if (ATRACE_ENABLED()) {
+ char buf[128];
+ for (int type = 0; type < NUM_TYPES; type++) {
+ snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]);
+ const TypeStats& stats = gObjectStats[type];
+ ATRACE_INT(buf, stats.totalSize);
+ snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]);
+ ATRACE_INT(buf, stats.count);
+ }
+ }
+
+ std::vector<const Texture*> freeList;
+ for (const auto& obj : gObjectSet) {
+ if (obj->objectType() == GpuObjectType::Texture) {
+ const Texture* texture = static_cast<Texture*>(obj);
+ if (texture->cleanup) {
+ ALOGE("Leaked texture marked for cleanup! id=%u, size %ux%u",
+ texture->id(), texture->width(), texture->height());
+ freeList.push_back(texture);
+ }
+ }
+ }
+ for (auto& texture : freeList) {
+ const_cast<Texture*>(texture)->deleteTexture();
+ delete texture;
+ }
+}
+
+} // namespace uirenderer
+} // namespace android;
diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h
new file mode 100644
index 0000000..851aeae
--- /dev/null
+++ b/libs/hwui/GpuMemoryTracker.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <cutils/log.h>
+#include <pthread.h>
+#include <ostream>
+
+namespace android {
+namespace uirenderer {
+
+extern pthread_t gGpuThread;
+
+#define ASSERT_GPU_THREAD() LOG_ALWAYS_FATAL_IF( \
+ !pthread_equal(gGpuThread, pthread_self()), \
+ "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \
+ "!= gpu thread %lu", this, static_cast<int>(mType), mSize, \
+ pthread_self(), gGpuThread)
+
+enum class GpuObjectType {
+ Texture = 0,
+ OffscreenBuffer,
+ Layer,
+
+ TypeCount,
+};
+
+class GpuMemoryTracker {
+public:
+ GpuObjectType objectType() { return mType; }
+ int objectSize() { return mSize; }
+
+ static void onGLContextCreated();
+ static void onGLContextDestroyed();
+ static void dump();
+ static void dump(std::ostream& stream);
+ static int getInstanceCount(GpuObjectType type);
+ static int getTotalSize(GpuObjectType type);
+ static void onFrameCompleted();
+
+protected:
+ GpuMemoryTracker(GpuObjectType type) : mType(type) {
+ ASSERT_GPU_THREAD();
+ startTrackingObject();
+ }
+
+ ~GpuMemoryTracker() {
+ notifySizeChanged(0);
+ stopTrackingObject();
+ }
+
+ void notifySizeChanged(int newSize);
+
+private:
+ void startTrackingObject();
+ void stopTrackingObject();
+
+ int mSize = 0;
+ GpuObjectType mType;
+};
+
+} // namespace uirenderer
+} // namespace android;
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 8c46450..522aa96 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -110,9 +110,7 @@
void GradientCache::operator()(GradientCacheEntry&, Texture*& texture) {
if (texture) {
- const uint32_t size = texture->width * texture->height * bytesPerPixel();
- mSize -= size;
-
+ mSize -= texture->objectSize();
texture->deleteTexture();
delete texture;
}
@@ -167,18 +165,16 @@
getGradientInfo(colors, count, info);
Texture* texture = new Texture(Caches::getInstance());
- texture->width = info.width;
- texture->height = 2;
texture->blend = info.hasAlpha;
texture->generation = 1;
// Asume the cache is always big enough
- const uint32_t size = texture->width * texture->height * bytesPerPixel();
+ const uint32_t size = info.width * 2 * bytesPerPixel();
while (getSize() + size > mMaxSize) {
mCache.removeOldest();
}
- generateTexture(colors, positions, texture);
+ generateTexture(colors, positions, info.width, 2, texture);
mSize += size;
mCache.put(gradient, texture);
@@ -231,10 +227,10 @@
dst += 4 * sizeof(float);
}
-void GradientCache::generateTexture(uint32_t* colors, float* positions, Texture* texture) {
- const uint32_t width = texture->width;
+void GradientCache::generateTexture(uint32_t* colors, float* positions,
+ const uint32_t width, const uint32_t height, Texture* texture) {
const GLsizei rowBytes = width * bytesPerPixel();
- uint8_t pixels[rowBytes * texture->height];
+ uint8_t pixels[rowBytes * height];
static ChannelSplitter gSplitters[] = {
&android::uirenderer::GradientCache::splitToBytes,
@@ -277,17 +273,13 @@
memcpy(pixels + rowBytes, pixels, rowBytes);
- glGenTextures(1, &texture->id);
- Caches::getInstance().textureState().bindTexture(texture->id);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
if (mUseFloatTexture) {
// We have to use GL_RGBA16F because GL_RGBA32F does not support filtering
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, texture->height, 0,
- GL_RGBA, GL_FLOAT, pixels);
+ texture->upload(width, height, GL_RGBA16F, GL_RGBA, GL_FLOAT, pixels);
} else {
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0,
- GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+ texture->upload(width, height, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
}
texture->setFilter(GL_LINEAR);
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 7534c5d..b762ca7 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -143,7 +143,8 @@
Texture* addLinearGradient(GradientCacheEntry& gradient,
uint32_t* colors, float* positions, int count);
- void generateTexture(uint32_t* colors, float* positions, Texture* texture);
+ void generateTexture(uint32_t* colors, float* positions,
+ const uint32_t width, const uint32_t height, Texture* texture);
struct GradientInfo {
uint32_t width;
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 0fe20ad..7a74b98 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -36,7 +36,8 @@
namespace uirenderer {
Layer::Layer(Type layerType, RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight)
- : state(State::Uncached)
+ : GpuMemoryTracker(GpuObjectType::Layer)
+ , state(State::Uncached)
, caches(Caches::getInstance())
, renderState(renderState)
, texture(caches)
@@ -45,8 +46,8 @@
// preserves the old inc/dec ref locations. This should be changed...
incStrong(nullptr);
renderTarget = GL_TEXTURE_2D;
- texture.width = layerWidth;
- texture.height = layerHeight;
+ texture.mWidth = layerWidth;
+ texture.mHeight = layerHeight;
renderState.registerLayer(this);
}
@@ -54,10 +55,10 @@
renderState.unregisterLayer(this);
SkSafeUnref(colorFilter);
- if (stencil || fbo || texture.id) {
+ if (stencil || fbo || texture.mId) {
renderState.requireGLContext();
removeFbo();
- deleteTexture();
+ texture.deleteTexture();
}
delete[] mesh;
@@ -65,7 +66,7 @@
void Layer::onGlContextLost() {
removeFbo();
- deleteTexture();
+ texture.deleteTexture();
}
uint32_t Layer::computeIdealWidth(uint32_t layerWidth) {
@@ -179,8 +180,8 @@
}
void Layer::bindTexture() const {
- if (texture.id) {
- caches.textureState().bindTexture(renderTarget, texture.id);
+ if (texture.mId) {
+ caches.textureState().bindTexture(renderTarget, texture.mId);
}
}
@@ -191,28 +192,22 @@
}
void Layer::generateTexture() {
- if (!texture.id) {
- glGenTextures(1, &texture.id);
- }
-}
-
-void Layer::deleteTexture() {
- if (texture.id) {
- texture.deleteTexture();
- texture.id = 0;
+ if (!texture.mId) {
+ glGenTextures(1, &texture.mId);
}
}
void Layer::clearTexture() {
- caches.textureState().unbindTexture(texture.id);
- texture.id = 0;
+ caches.textureState().unbindTexture(texture.mId);
+ texture.mId = 0;
}
void Layer::allocateTexture() {
#if DEBUG_LAYERS
ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight());
#endif
- if (texture.id) {
+ if (texture.mId) {
+ texture.updateSize(getWidth(), getHeight(), GL_RGBA);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0,
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index e90f055..e00ae66 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -24,6 +24,7 @@
#include <memory>
#include <GLES2/gl2.h>
+#include <GpuMemoryTracker.h>
#include <ui/Region.h>
@@ -54,7 +55,7 @@
/**
* A layer has dimensions and is backed by an OpenGL texture or FBO.
*/
-class Layer : public VirtualLightRefBase {
+class Layer : public VirtualLightRefBase, GpuMemoryTracker {
public:
enum class Type {
Texture,
@@ -94,8 +95,8 @@
regionRect.set(bounds.leftTop().x, bounds.leftTop().y,
bounds.rightBottom().x, bounds.rightBottom().y);
- const float texX = 1.0f / float(texture.width);
- const float texY = 1.0f / float(texture.height);
+ const float texX = 1.0f / float(texture.mWidth);
+ const float texY = 1.0f / float(texture.mHeight);
const float height = layer.getHeight();
texCoords.set(
regionRect.left * texX, (height - regionRect.top) * texY,
@@ -112,11 +113,11 @@
void updateDeferred(RenderNode* renderNode, int left, int top, int right, int bottom);
inline uint32_t getWidth() const {
- return texture.width;
+ return texture.mWidth;
}
inline uint32_t getHeight() const {
- return texture.height;
+ return texture.mHeight;
}
/**
@@ -131,8 +132,7 @@
bool resize(const uint32_t width, const uint32_t height);
void setSize(uint32_t width, uint32_t height) {
- texture.width = width;
- texture.height = height;
+ texture.updateSize(width, height, texture.format());
}
ANDROID_API void setPaint(const SkPaint* paint);
@@ -201,7 +201,7 @@
}
inline GLuint getTextureId() const {
- return texture.id;
+ return texture.id();
}
inline Texture& getTexture() {
@@ -263,7 +263,6 @@
void bindTexture() const;
void generateTexture();
void allocateTexture();
- void deleteTexture();
/**
* When the caller frees the texture itself, the caller
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index b117754..f5681ce 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -112,7 +112,6 @@
layer->bindTexture();
layer->setFilter(GL_NEAREST);
layer->setWrap(GL_CLAMP_TO_EDGE, false);
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
#if DEBUG_LAYERS
dump();
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 92b758d..0cd763d 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <GpuMemoryTracker.h>
#include "OpenGLRenderer.h"
#include "DeferredDisplayList.h"
@@ -200,6 +201,7 @@
#if DEBUG_MEMORY_USAGE
mCaches.dumpMemoryUsage();
+ GPUMemoryTracker::dump();
#else
if (Properties::debugLevel & kDebugMemory) {
mCaches.dumpMemoryUsage();
@@ -1497,7 +1499,7 @@
.setMeshTexturedUnitQuad(texture->uvMapper)
.setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha)
.setTransform(*currentSnapshot(), TransformFlags::None)
- .setModelViewMapUnitToRectSnap(Rect(texture->width, texture->height))
+ .setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height()))
.build();
renderGlop(glop);
}
@@ -1601,10 +1603,10 @@
if (!texture) return;
const AutoTexture autoCleanup(texture);
- Rect uv(std::max(0.0f, src.left / texture->width),
- std::max(0.0f, src.top / texture->height),
- std::min(1.0f, src.right / texture->width),
- std::min(1.0f, src.bottom / texture->height));
+ Rect uv(std::max(0.0f, src.left / texture->width()),
+ std::max(0.0f, src.top / texture->height()),
+ std::min(1.0f, src.right / texture->width()),
+ std::min(1.0f, src.bottom / texture->height()));
const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType)
? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None;
@@ -1977,7 +1979,7 @@
.setMeshTexturedUnitQuad(nullptr)
.setFillShadowTexturePaint(*texture, textShadow.color, *paint, currentSnapshot()->alpha)
.setTransform(*currentSnapshot(), TransformFlags::None)
- .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height))
+ .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height()))
.build();
renderGlop(glop);
}
@@ -2316,7 +2318,7 @@
void OpenGLRenderer::drawPathTexture(PathTexture* texture, float x, float y,
const SkPaint* paint) {
- if (quickRejectSetupScissor(x, y, x + texture->width, y + texture->height)) {
+ if (quickRejectSetupScissor(x, y, x + texture->width(), y + texture->height())) {
return;
}
@@ -2326,7 +2328,7 @@
.setMeshTexturedUnitQuad(nullptr)
.setFillPathTexturePaint(*texture, *paint, currentSnapshot()->alpha)
.setTransform(*currentSnapshot(), TransformFlags::None)
- .setModelViewMapUnitToRect(Rect(x, y, x + texture->width, y + texture->height))
+ .setModelViewMapUnitToRect(Rect(x, y, x + texture->width(), y + texture->height()))
.build();
renderGlop(glop);
}
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 06ea55a..bfabc1d 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -185,7 +185,7 @@
void PathCache::removeTexture(PathTexture* texture) {
if (texture) {
- const uint32_t size = texture->width * texture->height;
+ const uint32_t size = texture->width() * texture->height();
// If there is a pending task we must wait for it to return
// before attempting our cleanup
@@ -209,9 +209,7 @@
ALOGD("Shape deleted, size = %d", size);
}
- if (texture->id) {
- Caches::getInstance().textureState().deleteTexture(texture->id);
- }
+ texture->deleteTexture();
delete texture;
}
}
@@ -248,8 +246,7 @@
drawPath(path, paint, bitmap, left, top, offset, width, height);
PathTexture* texture = new PathTexture(Caches::getInstance(),
- left, top, offset, width, height,
- path->getGenerationID());
+ left, top, offset, path->getGenerationID());
generateTexture(entry, &bitmap, texture);
return texture;
@@ -262,7 +259,7 @@
// Note here that we upload to a texture even if it's bigger than mMaxSize.
// Such an entry in mCache will only be temporary, since it will be evicted
// immediately on trim, or on any other Path entering the cache.
- uint32_t size = texture->width * texture->height;
+ uint32_t size = texture->width() * texture->height();
mSize += size;
PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d",
texture->id, size, mSize);
@@ -280,24 +277,8 @@
void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) {
ATRACE_NAME("Upload Path Texture");
- SkAutoLockPixels alp(bitmap);
- if (!bitmap.readyToDraw()) {
- ALOGE("Cannot generate texture from bitmap");
- return;
- }
-
- glGenTextures(1, &texture->id);
-
- Caches::getInstance().textureState().bindTexture(texture->id);
- // Textures are Alpha8
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-
- texture->blend = true;
- glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
- GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
-
+ texture->upload(bitmap);
texture->setFilter(GL_LINEAR);
- texture->setWrap(GL_CLAMP_TO_EDGE);
}
///////////////////////////////////////////////////////////////////////////////
@@ -320,16 +301,12 @@
texture->left = left;
texture->top = top;
texture->offset = offset;
- texture->width = width;
- texture->height = height;
if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
SkBitmap* bitmap = new SkBitmap();
drawPath(&t->path, &t->paint, *bitmap, left, top, offset, width, height);
t->setResult(bitmap);
} else {
- texture->width = 0;
- texture->height = 0;
t->setResult(nullptr);
}
}
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 302e9f8..18f380f 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -61,13 +61,11 @@
*/
struct PathTexture: public Texture {
PathTexture(Caches& caches, float left, float top,
- float offset, int width, int height, int generation)
+ float offset, int generation)
: Texture(caches)
, left(left)
, top(top)
, offset(offset) {
- this->width = width;
- this->height = height;
this->generation = generation;
}
PathTexture(Caches& caches, int generation)
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 83652c6..6f4a683 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -57,7 +57,7 @@
}
static inline void bindTexture(Caches* caches, Texture* texture, GLenum wrapS, GLenum wrapT) {
- caches->textureState().bindTexture(texture->id);
+ caches->textureState().bindTexture(texture->id());
texture->setWrapST(wrapS, wrapT);
}
@@ -219,8 +219,8 @@
outData->bitmapSampler = (*textureUnit)++;
- const float width = outData->bitmapTexture->width;
- const float height = outData->bitmapTexture->height;
+ const float width = outData->bitmapTexture->width();
+ const float height = outData->bitmapTexture->height();
description->hasBitmap = true;
if (!caches.extensions().hasNPot()
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
index 51f1652..f1e28b7 100644
--- a/libs/hwui/TextDropShadowCache.cpp
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -187,13 +187,10 @@
texture = new ShadowTexture(caches);
texture->left = shadow.penX;
texture->top = shadow.penY;
- texture->width = shadow.width;
- texture->height = shadow.height;
texture->generation = 0;
texture->blend = true;
const uint32_t size = shadow.width * shadow.height;
- texture->bitmapSize = size;
// Don't even try to cache a bitmap that's bigger than the cache
if (size < mMaxSize) {
@@ -202,15 +199,11 @@
}
}
- glGenTextures(1, &texture->id);
-
- caches.textureState().bindTexture(texture->id);
// Textures are Alpha8
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
+ texture->upload(GL_ALPHA, shadow.width, shadow.height,
GL_ALPHA, GL_UNSIGNED_BYTE, shadow.image);
-
texture->setFilter(GL_LINEAR);
texture->setWrap(GL_CLAMP_TO_EDGE);
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 5195b45..8a6b28d 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -14,14 +14,29 @@
* limitations under the License.
*/
-#include <utils/Log.h>
-
#include "Caches.h"
#include "Texture.h"
+#include "utils/TraceUtils.h"
+
+#include <utils/Log.h>
+
+#include <SkCanvas.h>
namespace android {
namespace uirenderer {
+static int bytesPerPixel(GLint glFormat) {
+ switch (glFormat) {
+ case GL_ALPHA:
+ return 1;
+ case GL_RGB:
+ return 3;
+ case GL_RGBA:
+ default:
+ return 4;
+ }
+}
+
void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force,
GLenum renderTarget) {
@@ -32,7 +47,7 @@
mWrapT = wrapT;
if (bindTexture) {
- mCaches.textureState().bindTexture(renderTarget, id);
+ mCaches.textureState().bindTexture(renderTarget, mId);
}
glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS);
@@ -50,7 +65,7 @@
mMagFilter = mag;
if (bindTexture) {
- mCaches.textureState().bindTexture(renderTarget, id);
+ mCaches.textureState().bindTexture(renderTarget, mId);
}
if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR;
@@ -60,8 +75,189 @@
}
}
-void Texture::deleteTexture() const {
- mCaches.textureState().deleteTexture(id);
+void Texture::deleteTexture() {
+ mCaches.textureState().deleteTexture(mId);
+ mId = 0;
+}
+
+bool Texture::updateSize(uint32_t width, uint32_t height, GLint format) {
+ if (mWidth == width && mHeight == height && mFormat == format) {
+ return false;
+ }
+ mWidth = width;
+ mHeight = height;
+ mFormat = format;
+ notifySizeChanged(mWidth * mHeight * bytesPerPixel(mFormat));
+ return true;
+}
+
+void Texture::upload(GLint internalformat, uint32_t width, uint32_t height,
+ GLenum format, GLenum type, const void* pixels) {
+ bool needsAlloc = updateSize(width, height, internalformat);
+ if (!needsAlloc && !pixels) {
+ return;
+ }
+ mCaches.textureState().activateTexture(0);
+ if (!mId) {
+ glGenTextures(1, &mId);
+ needsAlloc = true;
+ }
+ mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId);
+ if (needsAlloc) {
+ glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+ format, type, pixels);
+ } else {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0,
+ format, type, pixels);
+ }
+}
+
+static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp,
+ GLsizei width, GLsizei height, const GLvoid * data) {
+
+ glPixelStorei(GL_UNPACK_ALIGNMENT, bpp);
+ const bool useStride = stride != width
+ && Caches::getInstance().extensions().hasUnpackRowLength();
+ if ((stride == width) || useStride) {
+ if (useStride) {
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
+ }
+
+ if (resize) {
+ glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
+ } else {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
+ }
+
+ if (useStride) {
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ }
+ } else {
+ // With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer
+ // if the stride doesn't match the width
+
+ GLvoid * temp = (GLvoid *) malloc(width * height * bpp);
+ if (!temp) return;
+
+ uint8_t * pDst = (uint8_t *)temp;
+ uint8_t * pSrc = (uint8_t *)data;
+ for (GLsizei i = 0; i < height; i++) {
+ memcpy(pDst, pSrc, width * bpp);
+ pDst += width * bpp;
+ pSrc += stride * bpp;
+ }
+
+ if (resize) {
+ glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp);
+ } else {
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp);
+ }
+
+ free(temp);
+ }
+}
+
+static void uploadSkBitmapToTexture(const SkBitmap& bitmap,
+ bool resize, GLenum format, GLenum type) {
+ uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(),
+ bitmap.width(), bitmap.height(), bitmap.getPixels());
+}
+
+static void colorTypeToGlFormatAndType(SkColorType colorType,
+ GLint* outFormat, GLint* outType) {
+ switch (colorType) {
+ case kAlpha_8_SkColorType:
+ *outFormat = GL_ALPHA;
+ *outType = GL_UNSIGNED_BYTE;
+ break;
+ case kRGB_565_SkColorType:
+ *outFormat = GL_RGB;
+ *outType = GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ // ARGB_4444 and Index_8 are both upconverted to RGBA_8888
+ case kARGB_4444_SkColorType:
+ case kIndex_8_SkColorType:
+ case kN32_SkColorType:
+ *outFormat = GL_RGBA;
+ *outType = GL_UNSIGNED_BYTE;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType);
+ break;
+ }
+}
+
+void Texture::upload(const SkBitmap& bitmap) {
+ SkAutoLockPixels alp(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
+ // of 2 but we'd have to deal with size changes. Let's keep this simple
+ const bool canMipMap = mCaches.extensions().hasNPot();
+
+ // If the texture had mipmap enabled but not anymore,
+ // force a glTexImage2D to discard the mipmap levels
+ bool needsAlloc = canMipMap && mipMap && !bitmap.hasHardwareMipMap();
+
+ if (!mId) {
+ glGenTextures(1, &mId);
+ needsAlloc = true;
+ }
+
+ GLint format, type;
+ colorTypeToGlFormatAndType(bitmap.colorType(), &format, &type);
+
+ if (updateSize(bitmap.width(), bitmap.height(), format)) {
+ needsAlloc = true;
+ }
+
+ blend = !bitmap.isOpaque();
+ mCaches.textureState().bindTexture(mId);
+
+ if (CC_UNLIKELY(bitmap.colorType() == kARGB_4444_SkColorType
+ || bitmap.colorType() == kIndex_8_SkColorType)) {
+ SkBitmap rgbaBitmap;
+ rgbaBitmap.allocPixels(SkImageInfo::MakeN32(mWidth, mHeight,
+ bitmap.alphaType()));
+ rgbaBitmap.eraseColor(0);
+
+ SkCanvas canvas(rgbaBitmap);
+ canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr);
+
+ uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, format, type);
+ } else {
+ uploadSkBitmapToTexture(bitmap, needsAlloc, format, type);
+ }
+
+ if (canMipMap) {
+ mipMap = bitmap.hasHardwareMipMap();
+ if (mipMap) {
+ glGenerateMipmap(GL_TEXTURE_2D);
+ }
+ }
+
+ if (mFirstFilter) {
+ setFilter(GL_NEAREST);
+ }
+
+ if (mFirstWrap) {
+ setWrap(GL_CLAMP_TO_EDGE);
+ }
+}
+
+void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint format) {
+ mId = id;
+ mWidth = width;
+ mHeight = height;
+ mFormat = format;
+ // We're wrapping an existing texture, so don't double count this memory
+ notifySizeChanged(0);
}
}; // namespace uirenderer
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 1c544b9..4e8e6dc 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -17,20 +17,27 @@
#ifndef ANDROID_HWUI_TEXTURE_H
#define ANDROID_HWUI_TEXTURE_H
+#include "GpuMemoryTracker.h"
+
#include <GLES2/gl2.h>
+#include <SkBitmap.h>
namespace android {
namespace uirenderer {
class Caches;
class UvMapper;
+class Layer;
/**
* Represents an OpenGL texture.
*/
-class Texture {
+class Texture : public GpuMemoryTracker {
public:
- Texture(Caches& caches) : mCaches(caches) { }
+ Texture(Caches& caches)
+ : GpuMemoryTracker(GpuObjectType::Texture)
+ , mCaches(caches)
+ { }
virtual ~Texture() { }
@@ -53,12 +60,55 @@
/**
* Convenience method to call glDeleteTextures() on this texture's id.
*/
- void deleteTexture() const;
+ void deleteTexture();
/**
- * Name of the texture.
+ * Sets the width, height, and format of the texture along with allocating
+ * the texture ID. Does nothing if the width, height, and format are already
+ * the requested values.
+ *
+ * The image data is undefined after calling this.
*/
- GLuint id = 0;
+ void resize(uint32_t width, uint32_t height, GLint format) {
+ upload(format, width, height, format, GL_UNSIGNED_BYTE, nullptr);
+ }
+
+ /**
+ * Updates this Texture with the contents of the provided SkBitmap,
+ * also setting the appropriate width, height, and format. It is not necessary
+ * to call resize() prior to this.
+ *
+ * Note this does not set the generation from the SkBitmap.
+ */
+ void upload(const SkBitmap& source);
+
+ /**
+ * Basically glTexImage2D/glTexSubImage2D.
+ */
+ void upload(GLint internalformat, uint32_t width, uint32_t height,
+ GLenum format, GLenum type, const void* pixels);
+
+ /**
+ * Wraps an existing texture.
+ */
+ void wrap(GLuint id, uint32_t width, uint32_t height, GLint format);
+
+ GLuint id() const {
+ return mId;
+ }
+
+ uint32_t width() const {
+ return mWidth;
+ }
+
+ uint32_t height() const {
+ return mHeight;
+ }
+
+ GLint format() const {
+ return mFormat;
+ }
+
/**
* Generation of the backing bitmap,
*/
@@ -68,14 +118,6 @@
*/
bool blend = false;
/**
- * Width of the backing bitmap.
- */
- uint32_t width = 0;
- /**
- * Height of the backing bitmap.
- */
- uint32_t height = 0;
- /**
* Indicates whether this texture should be cleaned up after use.
*/
bool cleanup = false;
@@ -100,6 +142,19 @@
void* isInUse = nullptr;
private:
+ // TODO: Temporarily grant private access to Layer, remove once
+ // Layer can be de-tangled from being a dual-purpose render target
+ // and external texture wrapper
+ friend class Layer;
+
+ // Returns true if the size changed, false if it was the same
+ bool updateSize(uint32_t width, uint32_t height, GLint format);
+
+ GLuint mId = 0;
+ uint32_t mWidth = 0;
+ uint32_t mHeight = 0;
+ GLint mFormat = 0;
+
/**
* Last wrap modes set on this texture.
*/
@@ -120,7 +175,7 @@
class AutoTexture {
public:
- AutoTexture(const Texture* texture)
+ AutoTexture(Texture* texture)
: texture(texture) {}
~AutoTexture() {
if (texture && texture->cleanup) {
@@ -129,7 +184,7 @@
}
}
- const Texture *const texture;
+ Texture* const texture;
}; // class AutoTexture
}; // namespace uirenderer
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 21901cf..31bfa3a 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -166,7 +166,8 @@
if (canCache) {
texture = new Texture(Caches::getInstance());
texture->bitmapSize = size;
- Caches::getInstance().textureState().generateTexture(bitmap, texture, false);
+ texture->generation = bitmap->getGenerationID();
+ texture->upload(*bitmap);
mSize += size;
TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
@@ -179,7 +180,8 @@
} else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) {
// Texture was in the cache but is dirty, re-upload
// TODO: Re-adjust the cache size if the bitmap's dimensions have changed
- Caches::getInstance().textureState().generateTexture(bitmap, texture, true);
+ texture->upload(*bitmap);
+ texture->generation = bitmap->getGenerationID();
}
return texture;
@@ -204,7 +206,8 @@
const uint32_t size = bitmap->rowBytes() * bitmap->height();
texture = new Texture(Caches::getInstance());
texture->bitmapSize = size;
- Caches::getInstance().textureState().generateTexture(bitmap, texture, false);
+ texture->upload(*bitmap);
+ texture->generation = bitmap->getGenerationID();
texture->cleanup = true;
}
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 191c8a8..463450c 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -25,6 +25,7 @@
#include "Debug.h"
#include <vector>
+#include <unordered_map>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index d2685da..8ba4761 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -111,11 +111,11 @@
CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount)
: mTexture(Caches::getInstance())
+ , mWidth(width)
+ , mHeight(height)
, mFormat(format)
, mMaxQuadCount(maxQuadCount)
, mCaches(Caches::getInstance()) {
- mTexture.width = width;
- mTexture.height = height;
mTexture.blend = true;
mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
@@ -160,10 +160,7 @@
delete mPixelBuffer;
mPixelBuffer = nullptr;
}
- if (mTexture.id) {
- mCaches.textureState().deleteTexture(mTexture.id);
- mTexture.id = 0;
- }
+ mTexture.deleteTexture();
mDirty = false;
mCurrentQuad = 0;
}
@@ -183,22 +180,9 @@
mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight());
}
- if (!mTexture.id) {
- glGenTextures(1, &mTexture.id);
-
- mCaches.textureState().bindTexture(mTexture.id);
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- // Initialize texture dimensions
- glTexImage2D(GL_TEXTURE_2D, 0, mFormat, getWidth(), getHeight(), 0,
- mFormat, GL_UNSIGNED_BYTE, nullptr);
-
- const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST;
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering);
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- }
+ mTexture.resize(mWidth, mHeight, mFormat);
+ mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST);
+ mTexture.setWrap(GL_CLAMP_TO_EDGE);
}
bool CacheTexture::upload() {
diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h
index 6dabc76..5510666 100644
--- a/libs/hwui/font/CacheTexture.h
+++ b/libs/hwui/font/CacheTexture.h
@@ -92,11 +92,11 @@
bool fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY);
inline uint16_t getWidth() const {
- return mTexture.width;
+ return mWidth;
}
inline uint16_t getHeight() const {
- return mTexture.height;
+ return mHeight;
}
inline GLenum getFormat() const {
@@ -122,7 +122,7 @@
GLuint getTextureId() {
allocatePixelBuffer();
- return mTexture.id;
+ return mTexture.id();
}
inline bool isDirty() const {
@@ -183,6 +183,7 @@
PixelBuffer* mPixelBuffer = nullptr;
Texture mTexture;
+ uint32_t mWidth, mHeight;
GLenum mFormat;
bool mLinearFiltering = false;
bool mDirty = false;
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp
index 227b640..98c94df 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.cpp
+++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp
@@ -34,29 +34,22 @@
OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches,
uint32_t viewportWidth, uint32_t viewportHeight)
- : renderState(renderState)
+ : GpuMemoryTracker(GpuObjectType::OffscreenBuffer)
+ , renderState(renderState)
, viewportWidth(viewportWidth)
, viewportHeight(viewportHeight)
, texture(caches) {
- texture.width = computeIdealDimension(viewportWidth);
- texture.height = computeIdealDimension(viewportHeight);
+ uint32_t width = computeIdealDimension(viewportWidth);
+ uint32_t height = computeIdealDimension(viewportHeight);
+ texture.resize(width, height, GL_RGBA);
texture.blend = true;
-
- caches.textureState().activateTexture(0);
- glGenTextures(1, &texture.id);
- caches.textureState().bindTexture(GL_TEXTURE_2D, texture.id);
-
- texture.setWrap(GL_CLAMP_TO_EDGE, false, false, GL_TEXTURE_2D);
+ texture.setWrap(GL_CLAMP_TO_EDGE);
// not setting filter on texture, since it's set when drawing, based on transform
-
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture.width, texture.height, 0,
- GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}
Rect OffscreenBuffer::getTextureCoordinates() {
- const float texX = 1.0f / float(texture.width);
- const float texY = 1.0f / float(texture.height);
+ const float texX = 1.0f / static_cast<float>(texture.width());
+ const float texY = 1.0f / static_cast<float>(texture.height());
return Rect(0, viewportHeight * texY, viewportWidth * texX, 0);
}
@@ -69,8 +62,8 @@
size_t count;
const android::Rect* rects = safeRegion.getArray(&count);
- const float texX = 1.0f / float(texture.width);
- const float texY = 1.0f / float(texture.height);
+ const float texX = 1.0f / float(texture.width());
+ const float texY = 1.0f / float(texture.height());
FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed
TextureVertex* mesh = &meshVector[0];
@@ -157,8 +150,8 @@
OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer,
const uint32_t width, const uint32_t height) {
RenderState& renderState = layer->renderState;
- if (layer->texture.width == OffscreenBuffer::computeIdealDimension(width)
- && layer->texture.height == OffscreenBuffer::computeIdealDimension(height)) {
+ if (layer->texture.width() == OffscreenBuffer::computeIdealDimension(width)
+ && layer->texture.height() == OffscreenBuffer::computeIdealDimension(height)) {
// resize in place
layer->viewportWidth = width;
layer->viewportHeight = height;
diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h
index 2d8d529..94155ef 100644
--- a/libs/hwui/renderstate/OffscreenBufferPool.h
+++ b/libs/hwui/renderstate/OffscreenBufferPool.h
@@ -17,10 +17,10 @@
#ifndef ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
#define ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H
+#include <GpuMemoryTracker.h>
#include "Caches.h"
#include "Texture.h"
#include "utils/Macros.h"
-
#include <ui/Region.h>
#include <set>
@@ -40,7 +40,7 @@
* viewport bounds, since textures are always allocated with width / height as a multiple of 64, for
* the purpose of improving reuse.
*/
-class OffscreenBuffer {
+class OffscreenBuffer : GpuMemoryTracker {
public:
OffscreenBuffer(RenderState& renderState, Caches& caches,
uint32_t viewportWidth, uint32_t viewportHeight);
@@ -58,7 +58,7 @@
static uint32_t computeIdealDimension(uint32_t dimension);
- uint32_t getSizeInBytes() { return texture.width * texture.height * 4; }
+ uint32_t getSizeInBytes() { return texture.objectSize(); }
RenderState& renderState;
@@ -124,8 +124,8 @@
Entry(OffscreenBuffer* layer)
: layer(layer)
- , width(layer->texture.width)
- , height(layer->texture.height) {
+ , width(layer->texture.width())
+ , height(layer->texture.height()) {
}
static int compare(const Entry& lhs, const Entry& rhs);
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index 4fa8200..4a1e8fc 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -13,12 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#include <GpuMemoryTracker.h>
#include "renderstate/RenderState.h"
#include "renderthread/CanvasContext.h"
#include "renderthread/EglManager.h"
#include "utils/GLUtils.h"
-
#include <algorithm>
namespace android {
@@ -40,6 +40,8 @@
void RenderState::onGLContextCreated() {
LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil,
"State object lifecycle not managed correctly");
+ GpuMemoryTracker::onGLContextCreated();
+
mBlend = new Blend();
mMeshState = new MeshState();
mScissor = new Scissor();
@@ -106,6 +108,8 @@
mScissor = nullptr;
delete mStencil;
mStencil = nullptr;
+
+ GpuMemoryTracker::onGLContextDestroyed();
}
void RenderState::flush(Caches::FlushMode mode) {
@@ -310,7 +314,7 @@
texture.texture->setFilter(texture.filter, true, false, texture.target);
}
- mCaches->textureState().bindTexture(texture.target, texture.texture->id);
+ mCaches->textureState().bindTexture(texture.target, texture.texture->id());
meshState().enableTexCoordsVertexArray();
meshState().bindTexCoordsVertexPointer(force, vertices.texCoord, vertices.stride);
diff --git a/libs/hwui/renderstate/TextureState.cpp b/libs/hwui/renderstate/TextureState.cpp
index 1f50f71..26ebdee 100644
--- a/libs/hwui/renderstate/TextureState.cpp
+++ b/libs/hwui/renderstate/TextureState.cpp
@@ -34,134 +34,6 @@
GL_TEXTURE3
};
-static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp,
- GLsizei width, GLsizei height, const GLvoid * data) {
-
- glPixelStorei(GL_UNPACK_ALIGNMENT, bpp);
- const bool useStride = stride != width
- && Caches::getInstance().extensions().hasUnpackRowLength();
- if ((stride == width) || useStride) {
- if (useStride) {
- glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
- }
-
- if (resize) {
- glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data);
- } else {
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data);
- }
-
- if (useStride) {
- glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
- }
- } else {
- // With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer
- // if the stride doesn't match the width
-
- GLvoid * temp = (GLvoid *) malloc(width * height * bpp);
- if (!temp) return;
-
- uint8_t * pDst = (uint8_t *)temp;
- uint8_t * pSrc = (uint8_t *)data;
- for (GLsizei i = 0; i < height; i++) {
- memcpy(pDst, pSrc, width * bpp);
- pDst += width * bpp;
- pSrc += stride * bpp;
- }
-
- if (resize) {
- glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp);
- } else {
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp);
- }
-
- free(temp);
- }
-}
-
-static void uploadSkBitmapToTexture(const SkBitmap& bitmap,
- bool resize, GLenum format, GLenum type) {
- uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(),
- bitmap.width(), bitmap.height(), bitmap.getPixels());
-}
-
-void TextureState::generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate) {
- SkAutoLockPixels alp(*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
- // of 2 but we'd have to deal with size changes. Let's keep this simple
- const bool canMipMap = Caches::getInstance().extensions().hasNPot();
-
- // If the texture had mipmap enabled but not anymore,
- // force a glTexImage2D to discard the mipmap levels
- const bool resize = !regenerate || bitmap->width() != int(texture->width) ||
- bitmap->height() != int(texture->height) ||
- (regenerate && canMipMap && texture->mipMap && !bitmap->hasHardwareMipMap());
-
- if (!regenerate) {
- glGenTextures(1, &texture->id);
- }
-
- texture->generation = bitmap->getGenerationID();
- texture->width = bitmap->width();
- texture->height = bitmap->height();
-
- bindTexture(texture->id);
-
- switch (bitmap->colorType()) {
- case kAlpha_8_SkColorType:
- uploadSkBitmapToTexture(*bitmap, resize, GL_ALPHA, GL_UNSIGNED_BYTE);
- texture->blend = true;
- break;
- case kRGB_565_SkColorType:
- uploadSkBitmapToTexture(*bitmap, resize, GL_RGB, GL_UNSIGNED_SHORT_5_6_5);
- texture->blend = false;
- break;
- case kN32_SkColorType:
- uploadSkBitmapToTexture(*bitmap, resize, GL_RGBA, GL_UNSIGNED_BYTE);
- // Do this after calling getPixels() to make sure Skia's deferred
- // decoding happened
- texture->blend = !bitmap->isOpaque();
- break;
- case kARGB_4444_SkColorType:
- case kIndex_8_SkColorType: {
- SkBitmap rgbaBitmap;
- rgbaBitmap.allocPixels(SkImageInfo::MakeN32(texture->width, texture->height,
- bitmap->alphaType()));
- rgbaBitmap.eraseColor(0);
-
- SkCanvas canvas(rgbaBitmap);
- canvas.drawBitmap(*bitmap, 0.0f, 0.0f, nullptr);
-
- uploadSkBitmapToTexture(rgbaBitmap, resize, GL_RGBA, GL_UNSIGNED_BYTE);
- texture->blend = !bitmap->isOpaque();
- break;
- }
- default:
- ALOGW("Unsupported bitmap colorType: %d", bitmap->colorType());
- break;
- }
-
- if (canMipMap) {
- texture->mipMap = bitmap->hasHardwareMipMap();
- if (texture->mipMap) {
- glGenerateMipmap(GL_TEXTURE_2D);
- }
- }
-
- if (!regenerate) {
- texture->setFilter(GL_NEAREST);
- texture->setWrap(GL_CLAMP_TO_EDGE);
- }
-}
-
TextureState::TextureState()
: mTextureUnit(0) {
glActiveTexture(kTextureUnits[0]);
diff --git a/libs/hwui/renderstate/TextureState.h b/libs/hwui/renderstate/TextureState.h
index 3a2b85a..ec94d7e 100644
--- a/libs/hwui/renderstate/TextureState.h
+++ b/libs/hwui/renderstate/TextureState.h
@@ -76,13 +76,6 @@
*/
void unbindTexture(GLuint texture);
- /**
- * Generates the texture from a bitmap into the specified texture structure.
- *
- * @param regenerate If true, the bitmap data is reuploaded into the texture, but
- * no new texture is generated.
- */
- void generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate);
private:
// total number of texture units available for use
static const int kTextureUnitsCount = 4;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 644f356..968135b 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <GpuMemoryTracker.h>
#include "CanvasContext.h"
#include "AnimationContext.h"
@@ -497,6 +498,8 @@
mJankTracker.addFrame(*mCurrentFrameInfo);
mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
+
+ GpuMemoryTracker::onFrameCompleted();
}
// Called by choreographer to do an RT-driven animation
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index edde31e..42cd0ce 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -197,6 +197,10 @@
renderthread::RenderThread::getInstance().queueAndWait(&task);
}
+ static bool isRenderThreadRunning() {
+ return renderthread::RenderThread::hasInstance();
+ }
+
static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
static void drawTextToCanvas(TestCanvas* canvas, const char* text,
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index 1616a95..02a3950 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -25,6 +25,7 @@
#include <unistd.h>
#include <unordered_map>
#include <vector>
+#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
new file mode 100644
index 0000000..aa1dcb2
--- /dev/null
+++ b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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 <GpuMemoryTracker.h>
+
+#include "renderthread/EglManager.h"
+#include "renderthread/RenderThread.h"
+#include "tests/common/TestUtils.h"
+
+#include <utils/StrongPointer.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+
+class TestGPUObject : public GpuMemoryTracker {
+public:
+ TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {}
+
+ void changeSize(int newSize) {
+ notifySizeChanged(newSize);
+ }
+};
+
+// Other tests may have created a renderthread and EGL context.
+// This will destroy the EGLContext on RenderThread if it exists so that the
+// current thread can spoof being a GPU thread
+static void destroyEglContext() {
+ if (TestUtils::isRenderThreadRunning()) {
+ TestUtils::runOnRenderThread([](RenderThread& thread) {
+ thread.eglManager().destroy();
+ });
+ }
+}
+
+TEST(GpuMemoryTracker, sizeCheck) {
+ destroyEglContext();
+
+ GpuMemoryTracker::onGLContextCreated();
+ ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+ ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
+ {
+ TestGPUObject myObj;
+ ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
+ myObj.changeSize(500);
+ ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+ myObj.changeSize(1000);
+ ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+ myObj.changeSize(300);
+ ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+ }
+ ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
+ ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
+ GpuMemoryTracker::onGLContextDestroyed();
+}
diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
index e96e9ba..5278730 100644
--- a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
+++ b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp
@@ -36,8 +36,8 @@
EXPECT_EQ(49u, layer.viewportWidth);
EXPECT_EQ(149u, layer.viewportHeight);
- EXPECT_EQ(64u, layer.texture.width);
- EXPECT_EQ(192u, layer.texture.height);
+ EXPECT_EQ(64u, layer.texture.width());
+ EXPECT_EQ(192u, layer.texture.height());
EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes());
});
@@ -100,8 +100,8 @@
ASSERT_EQ(layer, pool.resize(layer, 60u, 55u));
EXPECT_EQ(60u, layer->viewportWidth);
EXPECT_EQ(55u, layer->viewportHeight);
- EXPECT_EQ(64u, layer->texture.width);
- EXPECT_EQ(64u, layer->texture.height);
+ EXPECT_EQ(64u, layer->texture.width());
+ EXPECT_EQ(64u, layer->texture.height());
// resized to use different object in pool
auto layer2 = pool.get(thread.renderState(), 128u, 128u);
@@ -110,8 +110,8 @@
ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u));
EXPECT_EQ(120u, layer2->viewportWidth);
EXPECT_EQ(125u, layer2->viewportHeight);
- EXPECT_EQ(128u, layer2->texture.width);
- EXPECT_EQ(128u, layer2->texture.height);
+ EXPECT_EQ(128u, layer2->texture.width());
+ EXPECT_EQ(128u, layer2->texture.height());
// original allocation now only thing in pool
EXPECT_EQ(1u, pool.getCount());
diff --git a/libs/hwui/tests/unit/StringUtilsTests.cpp b/libs/hwui/tests/unit/StringUtilsTests.cpp
index 6b2e265..b60e96c 100644
--- a/libs/hwui/tests/unit/StringUtilsTests.cpp
+++ b/libs/hwui/tests/unit/StringUtilsTests.cpp
@@ -36,3 +36,18 @@
EXPECT_TRUE(collection.has("GL_ext1"));
EXPECT_FALSE(collection.has("GL_ext")); // string present, but not in list
}
+
+TEST(StringUtils, sizePrinter) {
+ std::stringstream os;
+ os << SizePrinter{500};
+ EXPECT_EQ("500.00B", os.str());
+ os.str("");
+ os << SizePrinter{46080};
+ EXPECT_EQ("45.00KiB", os.str());
+ os.str("");
+ os << SizePrinter{5 * 1024 * 1024 + 520 * 1024};
+ EXPECT_EQ("5.51MiB", os.str());
+ os.str("");
+ os << SizePrinter{2147483647};
+ EXPECT_EQ("2048.00MiB", os.str());
+}
diff --git a/libs/hwui/utils/StringUtils.h b/libs/hwui/utils/StringUtils.h
index 055869f..05a3d59 100644
--- a/libs/hwui/utils/StringUtils.h
+++ b/libs/hwui/utils/StringUtils.h
@@ -18,6 +18,8 @@
#include <string>
#include <unordered_set>
+#include <ostream>
+#include <iomanip>
namespace android {
namespace uirenderer {
@@ -34,6 +36,21 @@
static unordered_string_set split(const char* spacedList);
};
+struct SizePrinter {
+ int bytes;
+ friend std::ostream& operator<<(std::ostream& stream, const SizePrinter& d) {
+ static const char* SUFFIXES[] = {"B", "KiB", "MiB"};
+ size_t suffix = 0;
+ double temp = d.bytes;
+ while (temp > 1000 && suffix < 2) {
+ temp /= 1024.0;
+ suffix++;
+ }
+ stream << std::fixed << std::setprecision(2) << temp << SUFFIXES[suffix];
+ return stream;
+ }
+};
+
} /* namespace uirenderer */
} /* namespace android */