summaryrefslogtreecommitdiffstats
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/androidfw/Android.mk1
-rw-r--r--libs/androidfw/AssetManager.cpp231
-rw-r--r--libs/androidfw/BackupData.cpp6
-rw-r--r--libs/androidfw/ResourceTypes.cpp2482
-rw-r--r--libs/androidfw/TypeWrappers.cpp70
-rw-r--r--libs/androidfw/tests/Android.mk78
-rw-r--r--libs/androidfw/tests/BackupData_test.cpp438
-rw-r--r--libs/androidfw/tests/ByteBucketArray_test.cpp41
-rw-r--r--libs/androidfw/tests/Idmap_test.cpp115
-rw-r--r--libs/androidfw/tests/ObbFile_test.cpp2
-rw-r--r--libs/androidfw/tests/ResTable_test.cpp214
-rw-r--r--libs/androidfw/tests/ResourceTypes_test.cpp4
-rw-r--r--libs/androidfw/tests/Split_test.cpp203
-rw-r--r--libs/androidfw/tests/TestHelpers.h17
-rw-r--r--libs/androidfw/tests/TypeWrappers_test.cpp109
-rw-r--r--libs/androidfw/tests/data/.gitignore2
-rw-r--r--libs/androidfw/tests/data/basic/AndroidManifest.xml21
-rw-r--r--libs/androidfw/tests/data/basic/R.h55
-rw-r--r--libs/androidfw/tests/data/basic/basic_arsc.h161
-rwxr-xr-xlibs/androidfw/tests/data/basic/build9
-rw-r--r--libs/androidfw/tests/data/basic/res/layout-fr-sw600dp/main.xml3
-rw-r--r--libs/androidfw/tests/data/basic/res/layout/main.xml3
-rw-r--r--libs/androidfw/tests/data/basic/res/values-de/values.xml5
-rw-r--r--libs/androidfw/tests/data/basic/res/values-fr/values.xml5
-rw-r--r--libs/androidfw/tests/data/basic/res/values-sv/values.xml4
-rw-r--r--libs/androidfw/tests/data/basic/res/values/values.xml26
-rw-r--r--libs/androidfw/tests/data/basic/split_de_fr_arsc.h85
-rw-r--r--libs/androidfw/tests/data/feature/AndroidManifest.xml19
-rwxr-xr-xlibs/androidfw/tests/data/feature/build6
-rw-r--r--libs/androidfw/tests/data/feature/feature_arsc.h73
-rw-r--r--libs/androidfw/tests/data/feature/res/values/values.xml7
-rw-r--r--libs/androidfw/tests/data/lib/AndroidManifest.xml21
-rw-r--r--libs/androidfw/tests/data/lib/R.h22
-rwxr-xr-xlibs/androidfw/tests/data/lib/build6
-rw-r--r--libs/androidfw/tests/data/lib/lib_arsc.h84
-rw-r--r--libs/androidfw/tests/data/lib/res/values/values.xml8
-rw-r--r--libs/androidfw/tests/data/overlay/AndroidManifest.xml21
-rwxr-xr-xlibs/androidfw/tests/data/overlay/build6
-rw-r--r--libs/androidfw/tests/data/overlay/overlay_arsc.h69
-rw-r--r--libs/androidfw/tests/data/overlay/res/values/values.xml8
-rw-r--r--libs/common_time/Android.mk36
-rw-r--r--libs/common_time/clock_recovery.cpp423
-rw-r--r--libs/common_time/clock_recovery.h159
-rw-r--r--libs/common_time/common_clock.cpp150
-rw-r--r--libs/common_time/common_clock.h57
-rw-r--r--libs/common_time/common_clock_service.cpp157
-rw-r--r--libs/common_time/common_clock_service.h91
-rw-r--r--libs/common_time/common_time_config_service.cpp112
-rw-r--r--libs/common_time/common_time_config_service.h60
-rw-r--r--libs/common_time/common_time_server.cpp1506
-rw-r--r--libs/common_time/common_time_server.h324
-rw-r--r--libs/common_time/common_time_server_api.cpp438
-rw-r--r--libs/common_time/common_time_server_packets.cpp293
-rw-r--r--libs/common_time/common_time_server_packets.h189
-rw-r--r--libs/common_time/diag_thread.cpp323
-rw-r--r--libs/common_time/diag_thread.h76
-rw-r--r--libs/common_time/main.cpp43
-rw-r--r--libs/common_time/utils.cpp164
-rw-r--r--libs/common_time/utils.h83
-rw-r--r--libs/hwui/AmbientShadow.cpp333
-rw-r--r--libs/hwui/AmbientShadow.h57
-rw-r--r--libs/hwui/Android.mk52
-rw-r--r--libs/hwui/Animator.cpp319
-rw-r--r--libs/hwui/Animator.h196
-rw-r--r--libs/hwui/AnimatorManager.cpp109
-rw-r--r--libs/hwui/AnimatorManager.h58
-rw-r--r--libs/hwui/AssetAtlas.cpp4
-rw-r--r--libs/hwui/AssetAtlas.h6
-rw-r--r--libs/hwui/Caches.cpp100
-rw-r--r--libs/hwui/Caches.h41
-rw-r--r--libs/hwui/CanvasProperty.h47
-rw-r--r--libs/hwui/DamageAccumulator.cpp230
-rw-r--r--libs/hwui/DamageAccumulator.h71
-rw-r--r--libs/hwui/Debug.h6
-rw-r--r--libs/hwui/DeferredDisplayList.cpp27
-rw-r--r--libs/hwui/DeferredDisplayList.h6
-rw-r--r--libs/hwui/DeferredLayerUpdater.cpp131
-rw-r--r--libs/hwui/DeferredLayerUpdater.h111
-rw-r--r--libs/hwui/DisplayList.cpp531
-rw-r--r--libs/hwui/DisplayList.h535
-rw-r--r--libs/hwui/DisplayListOp.h556
-rw-r--r--libs/hwui/DisplayListRenderer.cpp362
-rw-r--r--libs/hwui/DisplayListRenderer.h317
-rw-r--r--libs/hwui/DrawProfiler.cpp261
-rw-r--r--libs/hwui/DrawProfiler.h96
-rw-r--r--libs/hwui/FontRenderer.cpp56
-rw-r--r--libs/hwui/FontRenderer.h28
-rw-r--r--libs/hwui/GradientCache.cpp7
-rw-r--r--libs/hwui/GradientCache.h2
-rw-r--r--libs/hwui/Interpolator.cpp127
-rw-r--r--libs/hwui/Interpolator.h117
-rw-r--r--libs/hwui/Layer.cpp101
-rw-r--r--libs/hwui/Layer.h78
-rw-r--r--libs/hwui/LayerCache.cpp4
-rw-r--r--libs/hwui/LayerCache.h4
-rw-r--r--libs/hwui/LayerRenderer.cpp83
-rw-r--r--libs/hwui/LayerRenderer.h24
-rw-r--r--libs/hwui/Matrix.cpp28
-rw-r--r--libs/hwui/Matrix.h26
-rwxr-xr-xlibs/hwui/OpenGLRenderer.cpp2030
-rwxr-xr-xlibs/hwui/OpenGLRenderer.h634
-rw-r--r--libs/hwui/Outline.h122
-rw-r--r--libs/hwui/Patch.h3
-rw-r--r--libs/hwui/PathCache.cpp47
-rw-r--r--libs/hwui/PathCache.h60
-rw-r--r--libs/hwui/PathTessellator.cpp401
-rw-r--r--libs/hwui/PathTessellator.h147
-rw-r--r--libs/hwui/Program.cpp15
-rw-r--r--libs/hwui/Program.h63
-rw-r--r--libs/hwui/ProgramCache.cpp99
-rw-r--r--libs/hwui/Properties.h32
-rw-r--r--libs/hwui/Rect.h57
-rw-r--r--libs/hwui/RenderNode.cpp909
-rw-r--r--libs/hwui/RenderNode.h293
-rw-r--r--libs/hwui/RenderProperties.cpp215
-rw-r--r--libs/hwui/RenderProperties.h623
-rw-r--r--libs/hwui/RenderState.cpp112
-rw-r--r--libs/hwui/RenderState.h70
-rw-r--r--libs/hwui/Renderer.h211
-rw-r--r--libs/hwui/ResourceCache.cpp120
-rw-r--r--libs/hwui/ResourceCache.h48
-rw-r--r--libs/hwui/RevealClip.h73
-rw-r--r--libs/hwui/ShadowTessellator.cpp249
-rw-r--r--libs/hwui/ShadowTessellator.h108
-rw-r--r--libs/hwui/SkiaColorFilter.cpp117
-rw-r--r--libs/hwui/SkiaColorFilter.h128
-rw-r--r--libs/hwui/SkiaShader.cpp562
-rw-r--r--libs/hwui/SkiaShader.h258
-rw-r--r--libs/hwui/Snapshot.cpp79
-rw-r--r--libs/hwui/Snapshot.h107
-rw-r--r--libs/hwui/SpotShadow.cpp939
-rw-r--r--libs/hwui/SpotShadow.h82
-rw-r--r--libs/hwui/StatefulBaseRenderer.cpp287
-rw-r--r--libs/hwui/StatefulBaseRenderer.h171
-rw-r--r--libs/hwui/Stencil.cpp2
-rw-r--r--libs/hwui/Stencil.h2
-rw-r--r--libs/hwui/TessellationCache.cpp476
-rw-r--r--libs/hwui/TessellationCache.h192
-rw-r--r--libs/hwui/TextDropShadowCache.cpp4
-rw-r--r--libs/hwui/TextDropShadowCache.h4
-rw-r--r--libs/hwui/Texture.cpp4
-rw-r--r--libs/hwui/Texture.h6
-rw-r--r--libs/hwui/TextureCache.cpp118
-rw-r--r--libs/hwui/TextureCache.h37
-rw-r--r--libs/hwui/TreeInfo.h129
-rw-r--r--libs/hwui/Vector.h33
-rw-r--r--libs/hwui/Vertex.h41
-rw-r--r--libs/hwui/VertexBuffer.h143
-rw-r--r--libs/hwui/font/CacheTexture.cpp4
-rw-r--r--libs/hwui/font/CacheTexture.h2
-rw-r--r--libs/hwui/font/Font.cpp54
-rw-r--r--libs/hwui/font/Font.h30
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp301
-rw-r--r--libs/hwui/renderthread/CanvasContext.h116
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp151
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.h94
-rw-r--r--libs/hwui/renderthread/EglManager.cpp288
-rw-r--r--libs/hwui/renderthread/EglManager.h88
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp423
-rw-r--r--libs/hwui/renderthread/RenderProxy.h121
-rw-r--r--libs/hwui/renderthread/RenderTask.cpp38
-rw-r--r--libs/hwui/renderthread/RenderTask.h99
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp350
-rw-r--r--libs/hwui/renderthread/RenderThread.h139
-rw-r--r--libs/hwui/renderthread/TimeLord.cpp46
-rw-r--r--libs/hwui/renderthread/TimeLord.h49
-rw-r--r--libs/hwui/thread/Task.h4
-rw-r--r--libs/hwui/thread/TaskManager.cpp16
-rw-r--r--libs/hwui/thread/TaskManager.h1
-rw-r--r--libs/hwui/utils/Blur.cpp45
-rw-r--r--libs/hwui/utils/Blur.h10
-rw-r--r--libs/hwui/utils/GLUtils.cpp52
-rw-r--r--libs/hwui/utils/GLUtils.h35
-rw-r--r--libs/hwui/utils/Macros.h32
-rw-r--r--libs/hwui/utils/MathUtils.h85
-rw-r--r--libs/input/Android.mk53
-rw-r--r--libs/input/PointerController.cpp604
-rw-r--r--libs/input/PointerController.h189
-rw-r--r--libs/input/SpriteController.cpp482
-rw-r--r--libs/input/SpriteController.h293
-rw-r--r--libs/usb/tests/AccessoryChat/accessorychat/Android.mk1
-rw-r--r--libs/usb/tests/AccessoryChat/accessorychat/linux/usb/f_accessory.h53
-rw-r--r--libs/usb/tests/accessorytest/Android.mk25
-rw-r--r--libs/usb/tests/accessorytest/accessory.c79
-rw-r--r--libs/usb/tests/accessorytest/accessory.h26
-rw-r--r--libs/usb/tests/accessorytest/audio.c221
-rw-r--r--libs/usb/tests/accessorytest/f_accessory.h148
-rw-r--r--libs/usb/tests/accessorytest/hid.c202
-rw-r--r--libs/usb/tests/accessorytest/usb.c227
189 files changed, 24647 insertions, 5962 deletions
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
index 258a296..dbee7ed 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -25,6 +25,7 @@ commonSources := \
ObbFile.cpp \
ResourceTypes.cpp \
StreamingZipInflater.cpp \
+ TypeWrappers.cpp \
ZipFileRO.cpp \
ZipUtils.cpp
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 56c95bd..de6a33c 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -74,6 +74,7 @@ static const char* kAssetsRoot = "assets";
static const char* kAppZipName = NULL; //"classes.jar";
static const char* kSystemAssets = "framework/framework-res.apk";
static const char* kResourceCache = "resource-cache";
+static const char* kAndroidManifest = "AndroidManifest.xml";
static const char* kExcludeExtension = ".EXCLUDE";
@@ -205,6 +206,16 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
ALOGV("In %p Asset %s path: %s", this,
ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
+ // Check that the path has an AndroidManifest.xml
+ Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
+ kAndroidManifest, Asset::ACCESS_BUFFER, ap);
+ if (manifestAsset == NULL) {
+ // This asset path does not contain any resources.
+ delete manifestAsset;
+ return false;
+ }
+ delete manifestAsset;
+
mAssetPaths.add(ap);
// new paths are always added at the end
@@ -220,6 +231,10 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
}
#endif
+ if (mResources != NULL) {
+ appendPathToResTable(ap);
+ }
+
return true;
}
@@ -245,7 +260,7 @@ bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie)
String8 targetPath;
String8 overlayPath;
if (!ResTable::getIdmapInfo(idmap->getBuffer(false), idmap->getLength(),
- NULL, NULL, &targetPath, &overlayPath)) {
+ NULL, NULL, NULL, &targetPath, &overlayPath)) {
ALOGW("failed to read idmap file %s\n", idmapPath.string());
delete idmap;
return false;
@@ -300,7 +315,7 @@ bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApk
ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
return false;
}
- tables[i].add(ass, 1, false /* copyData */, NULL /* idMap */);
+ tables[i].add(ass);
}
return tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
@@ -508,7 +523,7 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode)
* The "fileName" is the partial path starting from the application
* name.
*/
-Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode)
+Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode, int32_t* outCookie)
{
AutoMutex _l(mLock);
@@ -529,6 +544,7 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode)
Asset* pAsset = openNonAssetInPathLocked(
fileName, mode, mAssetPaths.itemAt(i));
if (pAsset != NULL) {
+ if (outCookie != NULL) *outCookie = static_cast<int32_t>(i + 1);
return pAsset != kExcludedAsset ? pAsset : NULL;
}
}
@@ -584,6 +600,96 @@ FileType AssetManager::getFileType(const char* fileName)
return kFileTypeRegular;
}
+bool AssetManager::appendPathToResTable(const asset_path& ap) const {
+ Asset* ass = NULL;
+ ResTable* sharedRes = NULL;
+ bool shared = true;
+ bool onlyEmptyResources = true;
+ MY_TRACE_BEGIN(ap.path.string());
+ Asset* idmap = openIdmapLocked(ap);
+ size_t nextEntryIdx = mResources->getTableCount();
+ ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
+ if (ap.type != kFileTypeDirectory) {
+ if (nextEntryIdx == 0) {
+ // The first item is typically the framework resources,
+ // which we want to avoid parsing every time.
+ sharedRes = const_cast<AssetManager*>(this)->
+ mZipSet.getZipResourceTable(ap.path);
+ if (sharedRes != NULL) {
+ // skip ahead the number of system overlay packages preloaded
+ nextEntryIdx = sharedRes->getTableCount();
+ }
+ }
+ if (sharedRes == NULL) {
+ ass = const_cast<AssetManager*>(this)->
+ mZipSet.getZipResourceTableAsset(ap.path);
+ if (ass == NULL) {
+ ALOGV("loading resource table %s\n", ap.path.string());
+ ass = const_cast<AssetManager*>(this)->
+ openNonAssetInPathLocked("resources.arsc",
+ Asset::ACCESS_BUFFER,
+ ap);
+ if (ass != NULL && ass != kExcludedAsset) {
+ ass = const_cast<AssetManager*>(this)->
+ mZipSet.setZipResourceTableAsset(ap.path, ass);
+ }
+ }
+
+ if (nextEntryIdx == 0 && ass != NULL) {
+ // If this is the first resource table in the asset
+ // manager, then we are going to cache it so that we
+ // can quickly copy it out for others.
+ ALOGV("Creating shared resources for %s", ap.path.string());
+ sharedRes = new ResTable();
+ sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
+#ifdef HAVE_ANDROID_OS
+ const char* data = getenv("ANDROID_DATA");
+ LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
+ String8 overlaysListPath(data);
+ overlaysListPath.appendPath(kResourceCache);
+ overlaysListPath.appendPath("overlays.list");
+ addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
+#endif
+ sharedRes = const_cast<AssetManager*>(this)->
+ mZipSet.setZipResourceTable(ap.path, sharedRes);
+ }
+ }
+ } else {
+ ALOGV("loading resource table %s\n", ap.path.string());
+ ass = const_cast<AssetManager*>(this)->
+ openNonAssetInPathLocked("resources.arsc",
+ Asset::ACCESS_BUFFER,
+ ap);
+ shared = false;
+ }
+
+ if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
+ ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
+ if (sharedRes != NULL) {
+ ALOGV("Copying existing resources for %s", ap.path.string());
+ mResources->add(sharedRes);
+ } else {
+ ALOGV("Parsing resources for %s", ap.path.string());
+ mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
+ }
+ onlyEmptyResources = false;
+
+ if (!shared) {
+ delete ass;
+ }
+ } else {
+ ALOGV("Installing empty resources in to table %p\n", mResources);
+ mResources->addEmpty(nextEntryIdx + 1);
+ }
+
+ if (idmap != NULL) {
+ delete idmap;
+ }
+ MY_TRACE_END();
+
+ return onlyEmptyResources;
+}
+
const ResTable* AssetManager::getResTable(bool required) const
{
ResTable* rt = mResources;
@@ -603,100 +709,27 @@ const ResTable* AssetManager::getResTable(bool required) const
LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
}
- if (mCacheMode != CACHE_OFF && !mCacheValid)
+ if (mCacheMode != CACHE_OFF && !mCacheValid) {
const_cast<AssetManager*>(this)->loadFileNameCacheLocked();
+ }
+ mResources = new ResTable();
+ updateResourceParamsLocked();
+
+ bool onlyEmptyResources = true;
const size_t N = mAssetPaths.size();
for (size_t i=0; i<N; i++) {
- Asset* ass = NULL;
- ResTable* sharedRes = NULL;
- bool shared = true;
- const asset_path& ap = mAssetPaths.itemAt(i);
- MY_TRACE_BEGIN(ap.path.string());
- Asset* idmap = openIdmapLocked(ap);
- ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
- if (ap.type != kFileTypeDirectory) {
- if (i == 0) {
- // The first item is typically the framework resources,
- // which we want to avoid parsing every time.
- sharedRes = const_cast<AssetManager*>(this)->
- mZipSet.getZipResourceTable(ap.path);
- if (sharedRes != NULL) {
- // skip ahead the number of system overlay packages preloaded
- i += sharedRes->getTableCount() - 1;
- }
- }
- if (sharedRes == NULL) {
- ass = const_cast<AssetManager*>(this)->
- mZipSet.getZipResourceTableAsset(ap.path);
- if (ass == NULL) {
- ALOGV("loading resource table %s\n", ap.path.string());
- ass = const_cast<AssetManager*>(this)->
- openNonAssetInPathLocked("resources.arsc",
- Asset::ACCESS_BUFFER,
- ap);
- if (ass != NULL && ass != kExcludedAsset) {
- ass = const_cast<AssetManager*>(this)->
- mZipSet.setZipResourceTableAsset(ap.path, ass);
- }
- }
-
- if (i == 0 && ass != NULL) {
- // If this is the first resource table in the asset
- // manager, then we are going to cache it so that we
- // can quickly copy it out for others.
- ALOGV("Creating shared resources for %s", ap.path.string());
- sharedRes = new ResTable();
- sharedRes->add(ass, i + 1, false, idmap);
-#ifdef HAVE_ANDROID_OS
- const char* data = getenv("ANDROID_DATA");
- LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
- String8 overlaysListPath(data);
- overlaysListPath.appendPath(kResourceCache);
- overlaysListPath.appendPath("overlays.list");
- addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, i);
-#endif
- sharedRes = const_cast<AssetManager*>(this)->
- mZipSet.setZipResourceTable(ap.path, sharedRes);
- }
- }
- } else {
- ALOGV("loading resource table %s\n", ap.path.string());
- ass = const_cast<AssetManager*>(this)->
- openNonAssetInPathLocked("resources.arsc",
- Asset::ACCESS_BUFFER,
- ap);
- shared = false;
- }
- if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
- if (rt == NULL) {
- mResources = rt = new ResTable();
- updateResourceParamsLocked();
- }
- ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
- if (sharedRes != NULL) {
- ALOGV("Copying existing resources for %s", ap.path.string());
- rt->add(sharedRes);
- } else {
- ALOGV("Parsing resources for %s", ap.path.string());
- rt->add(ass, i + 1, !shared, idmap);
- }
-
- if (!shared) {
- delete ass;
- }
- }
- if (idmap != NULL) {
- delete idmap;
- }
- MY_TRACE_END();
+ bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
+ onlyEmptyResources = onlyEmptyResources && empty;
}
- if (required && !rt) ALOGW("Unable to find resources file resources.arsc");
- if (!rt) {
- mResources = rt = new ResTable();
+ if (required && onlyEmptyResources) {
+ ALOGW("Unable to find resources file resources.arsc");
+ delete mResources;
+ mResources = NULL;
}
- return rt;
+
+ return mResources;
}
void AssetManager::updateResourceParamsLocked() const
@@ -762,7 +795,7 @@ void AssetManager::addSystemOverlays(const char* pathOverlaysList,
if (oass != NULL) {
Asset* oidmap = openIdmapLocked(oap);
offset++;
- sharedRes->add(oass, offset + 1, false, oidmap);
+ sharedRes->add(oass, oidmap, offset + 1, false);
const_cast<AssetManager*>(this)->mAssetPaths.add(oap);
const_cast<AssetManager*>(this)->mZipSet.addOverlay(targetPackagePath, oap);
}
@@ -903,7 +936,7 @@ Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode m
/* look at the filesystem on disk */
String8 path(createPathNameLocked(ap, locale, vendor));
path.appendPath(fileName);
-
+
String8 excludeName(path);
excludeName.append(kExcludeExtension);
if (::getFileType(excludeName.string()) != kFileTypeNonexistent) {
@@ -911,28 +944,28 @@ Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode m
//printf("+++ excluding '%s'\n", (const char*) excludeName);
return kExcludedAsset;
}
-
+
pAsset = openAssetFromFileLocked(path, mode);
-
+
if (pAsset == NULL) {
/* try again, this time with ".gz" */
path.append(".gz");
pAsset = openAssetFromFileLocked(path, mode);
}
-
+
if (pAsset != NULL)
pAsset->setAssetSource(path);
} else {
/* find in cache */
String8 path(createPathNameLocked(ap, locale, vendor));
path.appendPath(fileName);
-
+
AssetDir::FileInfo tmpInfo;
bool found = false;
-
+
String8 excludeName(path);
excludeName.append(kExcludeExtension);
-
+
if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) {
/* go no farther */
//printf("+++ Excluding '%s'\n", (const char*) excludeName);
@@ -1756,7 +1789,7 @@ bool AssetManager::fncScanAndMergeDirLocked(
// XXX This is broken -- the filename cache needs to hold the base
// asset path separately from its filename.
-
+
partialPath = createPathNameLocked(ap, locale, vendor);
if (dirName[0] != '\0') {
partialPath.appendPath(dirName);
diff --git a/libs/androidfw/BackupData.cpp b/libs/androidfw/BackupData.cpp
index bd9dc76..d16d5498 100644
--- a/libs/androidfw/BackupData.cpp
+++ b/libs/androidfw/BackupData.cpp
@@ -59,9 +59,10 @@ padding_extra(size_t n)
BackupDataWriter::BackupDataWriter(int fd)
:m_fd(fd),
m_status(NO_ERROR),
- m_pos(0),
m_entityCount(0)
{
+ m_pos = (ssize_t) lseek(fd, 0, SEEK_CUR);
+ if (DEBUG) ALOGI("BackupDataWriter(%d) @ %ld", fd, (long)m_pos);
}
BackupDataWriter::~BackupDataWriter()
@@ -184,10 +185,11 @@ BackupDataReader::BackupDataReader(int fd)
:m_fd(fd),
m_done(false),
m_status(NO_ERROR),
- m_pos(0),
m_entityCount(0)
{
memset(&m_header, 0, sizeof(m_header));
+ m_pos = (ssize_t) lseek(fd, 0, SEEK_CUR);
+ if (DEBUG) ALOGI("BackupDataReader(%d) @ %ld", fd, (long)m_pos);
}
BackupDataReader::~BackupDataReader()
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 2f3b1f7..2a4dec0 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -17,7 +17,9 @@
#define LOG_TAG "ResourceType"
//#define LOG_NDEBUG 0
+#include <androidfw/ByteBucketArray.h>
#include <androidfw/ResourceTypes.h>
+#include <androidfw/TypeWrappers.h>
#include <utils/Atomic.h>
#include <utils/ByteOrder.h>
#include <utils/Debug.h>
@@ -30,6 +32,7 @@
#include <memory.h>
#include <ctype.h>
#include <stdint.h>
+#include <stddef.h>
#ifndef INT32_MAX
#define INT32_MAX ((int32_t)(2147483647))
@@ -42,6 +45,7 @@
#define TABLE_SUPER_NOISY(x) //x
#define LOAD_TABLE_NOISY(x) //x
#define TABLE_THEME(x) //x
+#define LIB_NOISY(x) //x
namespace android {
@@ -62,9 +66,11 @@ namespace android {
#endif
#endif
-#define IDMAP_MAGIC 0x706d6469
-// size measured in sizeof(uint32_t)
-#define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t))
+#define IDMAP_MAGIC 0x504D4449
+#define IDMAP_CURRENT_VERSION 0x00000001
+
+#define APP_PACKAGE_ID 0x7f
+#define SYS_PACKAGE_ID 0x01
// Standard C isspace() is only required to look at the low byte of its input, so
// produces incorrect results for UTF-16 characters. For safety's sake, assume that
@@ -73,6 +79,11 @@ inline int isspace16(char16_t c) {
return (c < 0x0080 && isspace(c));
}
+template<typename T>
+inline static T max(T a, T b) {
+ return a > b ? a : b;
+}
+
// range checked; guaranteed to NUL-terminate within the stated number of available slots
// NOTE: if this truncates the dst string due to running out of space, no attempt is
// made to avoid splitting surrogate pairs.
@@ -98,7 +109,7 @@ static status_t validate_chunk(const ResChunk_header* chunk,
if (headerSize >= minSize) {
if (headerSize <= size) {
if (((headerSize|size)&0x3) == 0) {
- if ((ssize_t)size <= (dataEnd-((const uint8_t*)chunk))) {
+ if ((size_t)size <= (size_t)(dataEnd-((const uint8_t*)chunk))) {
return NO_ERROR;
}
ALOGW("%s data size 0x%x extends beyond resource end %p.",
@@ -113,7 +124,7 @@ static status_t validate_chunk(const ResChunk_header* chunk,
name, size, headerSize);
return BAD_TYPE;
}
- ALOGW("%s header size 0x%x is too small.",
+ ALOGW("%s header size 0x%04x is too small.",
name, headerSize);
return BAD_TYPE;
}
@@ -211,104 +222,179 @@ void Res_png_9patch::serialize(const Res_png_9patch& patch, const int32_t* xDivs
fill9patchOffsets(reinterpret_cast<Res_png_9patch*>(outData));
}
-static bool assertIdmapHeader(const uint32_t* map, size_t sizeBytes)
-{
- if (sizeBytes < ResTable::IDMAP_HEADER_SIZE_BYTES) {
- ALOGW("idmap assertion failed: size=%d bytes\n", (int)sizeBytes);
+static bool assertIdmapHeader(const void* idmap, size_t size) {
+ if (reinterpret_cast<uintptr_t>(idmap) & 0x03) {
+ ALOGE("idmap: header is not word aligned");
+ return false;
+ }
+
+ if (size < ResTable::IDMAP_HEADER_SIZE_BYTES) {
+ ALOGW("idmap: header too small (%d bytes)", (uint32_t) size);
+ return false;
+ }
+
+ const uint32_t magic = htodl(*reinterpret_cast<const uint32_t*>(idmap));
+ if (magic != IDMAP_MAGIC) {
+ ALOGW("idmap: no magic found in header (is 0x%08x, expected 0x%08x)",
+ magic, IDMAP_MAGIC);
return false;
}
- if (*map != htodl(IDMAP_MAGIC)) { // htodl: map data expected to be in correct endianess
- ALOGW("idmap assertion failed: invalid magic found (is 0x%08x, expected 0x%08x)\n",
- *map, htodl(IDMAP_MAGIC));
+
+ const uint32_t version = htodl(*(reinterpret_cast<const uint32_t*>(idmap) + 1));
+ if (version != IDMAP_CURRENT_VERSION) {
+ // We are strict about versions because files with this format are
+ // auto-generated and don't need backwards compatibility.
+ ALOGW("idmap: version mismatch in header (is 0x%08x, expected 0x%08x)",
+ version, IDMAP_CURRENT_VERSION);
return false;
}
return true;
}
-static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, uint32_t* outValue)
-{
- // see README for details on the format of map
- if (!assertIdmapHeader(map, sizeBytes)) {
- return UNKNOWN_ERROR;
- }
- map = map + IDMAP_HEADER_SIZE; // skip ahead to data segment
- // size of data block, in uint32_t
- const size_t size = (sizeBytes - ResTable::IDMAP_HEADER_SIZE_BYTES) / sizeof(uint32_t);
- const uint32_t type = Res_GETTYPE(key) + 1; // add one, idmap stores "public" type id
- const uint32_t entry = Res_GETENTRY(key);
- const uint32_t typeCount = *map;
+class IdmapEntries {
+public:
+ IdmapEntries() : mData(NULL) {}
- if (type > typeCount) {
- ALOGW("Resource ID map: type=%d exceeds number of types=%d\n", type, typeCount);
- return UNKNOWN_ERROR;
+ bool hasEntries() const {
+ if (mData == NULL) {
+ return false;
+ }
+
+ return (dtohs(*mData) > 0);
}
- if (typeCount > size) {
- ALOGW("Resource ID map: number of types=%d exceeds size of map=%d\n", typeCount, (int)size);
- return UNKNOWN_ERROR;
+
+ size_t byteSize() const {
+ if (mData == NULL) {
+ return 0;
+ }
+ uint16_t entryCount = dtohs(mData[2]);
+ return (sizeof(uint16_t) * 4) + (sizeof(uint32_t) * static_cast<size_t>(entryCount));
}
- const uint32_t typeOffset = map[type];
- if (typeOffset == 0) {
- *outValue = 0;
- return NO_ERROR;
+
+ uint8_t targetTypeId() const {
+ if (mData == NULL) {
+ return 0;
+ }
+ return dtohs(mData[0]);
}
- if (typeOffset + 1 > size) {
- ALOGW("Resource ID map: type offset=%d exceeds reasonable value, size of map=%d\n",
- typeOffset, (int)size);
- return UNKNOWN_ERROR;
+
+ uint8_t overlayTypeId() const {
+ if (mData == NULL) {
+ return 0;
+ }
+ return dtohs(mData[1]);
}
- const uint32_t entryCount = map[typeOffset];
- const uint32_t entryOffset = map[typeOffset + 1];
- if (entryCount == 0 || entry < entryOffset || entry - entryOffset > entryCount - 1) {
- *outValue = 0;
+
+ status_t setTo(const void* entryHeader, size_t size) {
+ if (reinterpret_cast<uintptr_t>(entryHeader) & 0x03) {
+ ALOGE("idmap: entry header is not word aligned");
+ return UNKNOWN_ERROR;
+ }
+
+ if (size < sizeof(uint16_t) * 4) {
+ ALOGE("idmap: entry header is too small (%u bytes)", (uint32_t) size);
+ return UNKNOWN_ERROR;
+ }
+
+ const uint16_t* header = reinterpret_cast<const uint16_t*>(entryHeader);
+ const uint16_t targetTypeId = dtohs(header[0]);
+ const uint16_t overlayTypeId = dtohs(header[1]);
+ if (targetTypeId == 0 || overlayTypeId == 0 || targetTypeId > 255 || overlayTypeId > 255) {
+ ALOGE("idmap: invalid type map (%u -> %u)", targetTypeId, overlayTypeId);
+ return UNKNOWN_ERROR;
+ }
+
+ uint16_t entryCount = dtohs(header[2]);
+ if (size < sizeof(uint32_t) * (entryCount + 2)) {
+ ALOGE("idmap: too small (%u bytes) for the number of entries (%u)",
+ (uint32_t) size, (uint32_t) entryCount);
+ return UNKNOWN_ERROR;
+ }
+ mData = header;
return NO_ERROR;
}
- const uint32_t index = typeOffset + 2 + entry - entryOffset;
- if (index > size) {
- ALOGW("Resource ID map: entry index=%d exceeds size of map=%d\n", index, (int)size);
- *outValue = 0;
+
+ status_t lookup(uint16_t entryId, uint16_t* outEntryId) const {
+ uint16_t entryCount = dtohs(mData[2]);
+ uint16_t offset = dtohs(mData[3]);
+
+ if (entryId < offset) {
+ // The entry is not present in this idmap
+ return BAD_INDEX;
+ }
+
+ entryId -= offset;
+
+ if (entryId >= entryCount) {
+ // The entry is not present in this idmap
+ return BAD_INDEX;
+ }
+
+ // It is safe to access the type here without checking the size because
+ // we have checked this when it was first loaded.
+ const uint32_t* entries = reinterpret_cast<const uint32_t*>(mData) + 2;
+ uint32_t mappedEntry = dtohl(entries[entryId]);
+ if (mappedEntry == 0xffffffff) {
+ // This entry is not present in this idmap
+ return BAD_INDEX;
+ }
+ *outEntryId = static_cast<uint16_t>(mappedEntry);
return NO_ERROR;
}
- *outValue = map[index];
- return NO_ERROR;
-}
+private:
+ const uint16_t* mData;
+};
-static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t *outId)
-{
- if (!assertIdmapHeader(map, mapSize)) {
+status_t parseIdmap(const void* idmap, size_t size, uint8_t* outPackageId, KeyedVector<uint8_t, IdmapEntries>* outMap) {
+ if (!assertIdmapHeader(idmap, size)) {
return UNKNOWN_ERROR;
}
- if (mapSize <= IDMAP_HEADER_SIZE + 1) {
- ALOGW("corrupt idmap: map size %d too short\n", mapSize);
+
+ size -= ResTable::IDMAP_HEADER_SIZE_BYTES;
+ if (size < sizeof(uint16_t) * 2) {
+ ALOGE("idmap: too small to contain any mapping");
return UNKNOWN_ERROR;
}
- uint32_t typeCount = *(map + IDMAP_HEADER_SIZE);
- if (typeCount == 0) {
- ALOGW("corrupt idmap: no types\n");
+
+ const uint16_t* data = reinterpret_cast<const uint16_t*>(
+ reinterpret_cast<const uint8_t*>(idmap) + ResTable::IDMAP_HEADER_SIZE_BYTES);
+
+ uint16_t targetPackageId = dtohs(*(data++));
+ if (targetPackageId == 0 || targetPackageId > 255) {
+ ALOGE("idmap: target package ID is invalid (%02x)", targetPackageId);
return UNKNOWN_ERROR;
}
- if (IDMAP_HEADER_SIZE + 1 + typeCount > mapSize) {
- ALOGW("corrupt idmap: number of types %d extends past idmap size %d\n", typeCount, mapSize);
+
+ uint16_t mapCount = dtohs(*(data++));
+ if (mapCount == 0) {
+ ALOGE("idmap: no mappings");
return UNKNOWN_ERROR;
}
- const uint32_t* p = map + IDMAP_HEADER_SIZE + 1;
- // find first defined type
- while (*p == 0) {
- ++p;
- if (--typeCount == 0) {
- ALOGW("corrupt idmap: types declared, none found\n");
- return UNKNOWN_ERROR;
- }
+
+ if (mapCount > 255) {
+ ALOGW("idmap: too many mappings. Only 255 are possible but %u are present", (uint32_t) mapCount);
}
- // determine package id from first entry of first type
- const uint32_t offset = *p + IDMAP_HEADER_SIZE + 2;
- if (offset > mapSize) {
- ALOGW("corrupt idmap: entry offset %d points outside map size %d\n", offset, mapSize);
- return UNKNOWN_ERROR;
+ while (size > sizeof(uint16_t) * 4) {
+ IdmapEntries entries;
+ status_t err = entries.setTo(data, size);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ ssize_t index = outMap->add(entries.overlayTypeId(), entries);
+ if (index < 0) {
+ return NO_MEMORY;
+ }
+
+ data += entries.byteSize() / sizeof(uint16_t);
+ size -= entries.byteSize();
}
- *outId = (map[offset] >> 24) & 0x000000ff;
+ if (outPackageId != NULL) {
+ *outPackageId = static_cast<uint8_t>(targetPackageId);
+ }
return NO_ERROR;
}
@@ -342,6 +428,22 @@ ResStringPool::~ResStringPool()
uninit();
}
+void ResStringPool::setToEmpty()
+{
+ uninit();
+
+ mOwnedData = calloc(1, sizeof(ResStringPool_header));
+ ResStringPool_header* header = (ResStringPool_header*) mOwnedData;
+ mSize = 0;
+ mEntries = NULL;
+ mStrings = NULL;
+ mStringPoolSize = 0;
+ mEntryStyles = NULL;
+ mStyles = NULL;
+ mStylePoolSize = 0;
+ mHeader = (const ResStringPool_header*) header;
+}
+
status_t ResStringPool::setTo(const void* data, size_t size, bool copyData)
{
if (!data || !size) {
@@ -402,19 +504,22 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData)
charSize = sizeof(char16_t);
}
- mStrings = (const void*)
- (((const uint8_t*)data)+mHeader->stringsStart);
- if (mHeader->stringsStart >= (mHeader->header.size-sizeof(uint16_t))) {
+ // There should be at least space for the smallest string
+ // (2 bytes length, null terminator).
+ if (mHeader->stringsStart >= (mSize - sizeof(uint16_t))) {
ALOGW("Bad string block: string pool starts at %d, after total size %d\n",
(int)mHeader->stringsStart, (int)mHeader->header.size);
return (mError=BAD_TYPE);
}
+
+ mStrings = (const void*)
+ (((const uint8_t*)data) + mHeader->stringsStart);
+
if (mHeader->styleCount == 0) {
- mStringPoolSize =
- (mHeader->header.size-mHeader->stringsStart)/charSize;
+ mStringPoolSize = (mSize - mHeader->stringsStart) / charSize;
} else {
// check invariant: styles starts before end of data
- if (mHeader->stylesStart >= (mHeader->header.size-sizeof(uint16_t))) {
+ if (mHeader->stylesStart >= (mSize - sizeof(uint16_t))) {
ALOGW("Bad style block: style block starts at %d past data size of %d\n",
(int)mHeader->stylesStart, (int)mHeader->header.size);
return (mError=BAD_TYPE);
@@ -697,11 +802,16 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const
const String8 ResStringPool::string8ObjectAt(size_t idx) const
{
size_t len;
- const char *str = (const char*)string8At(idx, &len);
+ const char *str = string8At(idx, &len);
if (str != NULL) {
- return String8(str);
+ return String8(str, len);
+ }
+
+ const char16_t *str16 = stringAt(idx, &len);
+ if (str16 != NULL) {
+ return String8(str16, len);
}
- return String8(stringAt(idx, &len));
+ return String8();
}
const ResStringPool_span* ResStringPool::styleAt(const ResStringPool_ref& ref) const
@@ -1111,7 +1221,14 @@ int32_t ResXMLParser::getAttributeDataType(size_t idx) const
(((const uint8_t*)tag)
+ dtohs(tag->attributeStart)
+ (dtohs(tag->attributeSize)*idx));
- return attr->typedValue.dataType;
+ uint8_t type = attr->typedValue.dataType;
+ if (type != Res_value::TYPE_DYNAMIC_REFERENCE) {
+ return type;
+ }
+
+ // This is a dynamic reference. We adjust those references
+ // to regular references at this level, so lie to the caller.
+ return Res_value::TYPE_REFERENCE;
}
}
return Res_value::TYPE_NULL;
@@ -1126,7 +1243,15 @@ int32_t ResXMLParser::getAttributeData(size_t idx) const
(((const uint8_t*)tag)
+ dtohs(tag->attributeStart)
+ (dtohs(tag->attributeSize)*idx));
- return dtohl(attr->typedValue.data);
+ if (attr->typedValue.dataType != Res_value::TYPE_DYNAMIC_REFERENCE ||
+ mTree.mDynamicRefTable == NULL) {
+ return dtohl(attr->typedValue.data);
+ }
+
+ uint32_t data = dtohl(attr->typedValue.data);
+ if (mTree.mDynamicRefTable->lookupResourceId(&data) == NO_ERROR) {
+ return data;
+ }
}
}
return 0;
@@ -1142,6 +1267,10 @@ ssize_t ResXMLParser::getAttributeValue(size_t idx, Res_value* outValue) const
+ dtohs(tag->attributeStart)
+ (dtohs(tag->attributeSize)*idx));
outValue->copyFrom_dtoh(attr->typedValue);
+ if (mTree.mDynamicRefTable != NULL &&
+ mTree.mDynamicRefTable->lookupResourceValue(outValue) != NO_ERROR) {
+ return BAD_TYPE;
+ }
return sizeof(Res_value);
}
}
@@ -1333,25 +1462,26 @@ void ResXMLParser::setPosition(const ResXMLParser::ResXMLPosition& pos)
mCurExt = pos.curExt;
}
-
// --------------------------------------------------------------------
static volatile int32_t gCount = 0;
-ResXMLTree::ResXMLTree()
+ResXMLTree::ResXMLTree(const DynamicRefTable* dynamicRefTable)
: ResXMLParser(*this)
+ , mDynamicRefTable(dynamicRefTable)
, mError(NO_INIT), mOwnedData(NULL)
{
//ALOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1);
restart();
}
-ResXMLTree::ResXMLTree(const void* data, size_t size, bool copyData)
+ResXMLTree::ResXMLTree()
: ResXMLParser(*this)
+ , mDynamicRefTable(NULL)
, mError(NO_INIT), mOwnedData(NULL)
{
//ALOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1);
- setTo(data, size, copyData);
+ restart();
}
ResXMLTree::~ResXMLTree()
@@ -2412,15 +2542,19 @@ String8 ResTable_config::toString() const {
if (mcc != 0) {
if (res.size() > 0) res.append("-");
- res.appendFormat("%dmcc", dtohs(mcc));
+ res.appendFormat("mcc%d", dtohs(mcc));
}
if (mnc != 0) {
if (res.size() > 0) res.append("-");
- res.appendFormat("%dmnc", dtohs(mnc));
+ res.appendFormat("mnc%d", dtohs(mnc));
}
+
char localeStr[RESTABLE_MAX_LOCALE_LEN];
getBcp47Locale(localeStr);
- res.append(localeStr);
+ if (strlen(localeStr) > 0) {
+ if (res.size() > 0) res.append("-");
+ res.append(localeStr);
+ }
if ((screenLayout&MASK_LAYOUTDIR) != 0) {
if (res.size() > 0) res.append("-");
@@ -2517,6 +2651,9 @@ String8 ResTable_config::toString() const {
case ResTable_config::UI_MODE_TYPE_APPLIANCE:
res.append("appliance");
break;
+ case ResTable_config::UI_MODE_TYPE_WATCH:
+ res.append("watch");
+ break;
default:
res.appendFormat("uiModeType=%d",
dtohs(screenLayout&ResTable_config::MASK_UI_MODE_TYPE));
@@ -2559,6 +2696,9 @@ String8 ResTable_config::toString() const {
case ResTable_config::DENSITY_XXHIGH:
res.append("xxhdpi");
break;
+ case ResTable_config::DENSITY_XXXHIGH:
+ res.append("xxxhdpi");
+ break;
case ResTable_config::DENSITY_NONE:
res.append("nodpi");
break;
@@ -2584,6 +2724,20 @@ String8 ResTable_config::toString() const {
break;
}
}
+ if ((inputFlags&MASK_KEYSHIDDEN) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (inputFlags&MASK_KEYSHIDDEN) {
+ case ResTable_config::KEYSHIDDEN_NO:
+ res.append("keysexposed");
+ break;
+ case ResTable_config::KEYSHIDDEN_YES:
+ res.append("keyshidden");
+ break;
+ case ResTable_config::KEYSHIDDEN_SOFT:
+ res.append("keyssoft");
+ break;
+ }
+ }
if (keyboard != KEYBOARD_ANY) {
if (res.size() > 0) res.append("-");
switch (keyboard) {
@@ -2601,17 +2755,18 @@ String8 ResTable_config::toString() const {
break;
}
}
- if ((inputFlags&MASK_KEYSHIDDEN) != 0) {
+ if ((inputFlags&MASK_NAVHIDDEN) != 0) {
if (res.size() > 0) res.append("-");
- switch (inputFlags&MASK_KEYSHIDDEN) {
- case ResTable_config::KEYSHIDDEN_NO:
- res.append("keysexposed");
+ switch (inputFlags&MASK_NAVHIDDEN) {
+ case ResTable_config::NAVHIDDEN_NO:
+ res.append("navexposed");
break;
- case ResTable_config::KEYSHIDDEN_YES:
- res.append("keyshidden");
+ case ResTable_config::NAVHIDDEN_YES:
+ res.append("navhidden");
break;
- case ResTable_config::KEYSHIDDEN_SOFT:
- res.append("keyssoft");
+ default:
+ res.appendFormat("inputFlagsNavHidden=%d",
+ dtohs(inputFlags&MASK_NAVHIDDEN));
break;
}
}
@@ -2635,21 +2790,6 @@ String8 ResTable_config::toString() const {
break;
}
}
- if ((inputFlags&MASK_NAVHIDDEN) != 0) {
- if (res.size() > 0) res.append("-");
- switch (inputFlags&MASK_NAVHIDDEN) {
- case ResTable_config::NAVHIDDEN_NO:
- res.append("navsexposed");
- break;
- case ResTable_config::NAVHIDDEN_YES:
- res.append("navhidden");
- break;
- default:
- res.appendFormat("inputFlagsNavHidden=%d",
- dtohs(inputFlags&MASK_NAVHIDDEN));
- break;
- }
- }
if (screenSize != 0) {
if (res.size() > 0) res.append("-");
res.appendFormat("%dx%d", dtohs(screenWidth), dtohs(screenHeight));
@@ -2679,7 +2819,7 @@ struct ResTable::Header
free(resourceIDMap);
}
- ResTable* const owner;
+ const ResTable* const owner;
void* ownedData;
const ResTable_header* header;
size_t size;
@@ -2692,6 +2832,17 @@ struct ResTable::Header
size_t resourceIDMapSize;
};
+struct ResTable::Entry {
+ ResTable_config config;
+ const ResTable_entry* entry;
+ const ResTable_type* type;
+ uint32_t specFlags;
+ const Package* package;
+
+ StringPoolRef typeStr;
+ StringPoolRef keyStr;
+};
+
struct ResTable::Type
{
Type(const Header* _header, const Package* _package, size_t count)
@@ -2702,33 +2853,29 @@ struct ResTable::Type
const size_t entryCount;
const ResTable_typeSpec* typeSpec;
const uint32_t* typeSpecFlags;
+ IdmapEntries idmapEntries;
Vector<const ResTable_type*> configs;
};
struct ResTable::Package
{
Package(ResTable* _owner, const Header* _header, const ResTable_package* _package)
- : owner(_owner), header(_header), package(_package) { }
- ~Package()
- {
- size_t i = types.size();
- while (i > 0) {
- i--;
- delete types[i];
+ : owner(_owner), header(_header), package(_package), typeIdOffset(0) {
+ if (dtohs(package->header.headerSize) == sizeof(package)) {
+ // The package structure is the same size as the definition.
+ // This means it contains the typeIdOffset field.
+ typeIdOffset = package->typeIdOffset;
}
}
- ResTable* const owner;
+ const ResTable* const owner;
const Header* const header;
const ResTable_package* const package;
- Vector<Type*> types;
ResStringPool typeStrings;
ResStringPool keyStrings;
- const Type* getType(size_t idx) const {
- return idx < types.size() ? types[idx] : NULL;
- }
+ size_t typeIdOffset;
};
// A group of objects describing a particular resource package.
@@ -2737,9 +2884,27 @@ struct ResTable::Package
struct ResTable::PackageGroup
{
PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id)
- : owner(_owner), name(_name), id(_id), typeCount(0), bags(NULL) { }
+ : owner(_owner)
+ , name(_name)
+ , id(_id)
+ , largestTypeId(0)
+ , bags(NULL)
+ , dynamicRefTable(static_cast<uint8_t>(_id))
+ { }
+
~PackageGroup() {
clearBagCache();
+ const size_t numTypes = types.size();
+ for (size_t i = 0; i < numTypes; i++) {
+ const TypeList& typeList = types[i];
+ const size_t numInnerTypes = typeList.size();
+ for (size_t j = 0; j < numInnerTypes; j++) {
+ if (typeList[j]->package->owner == owner) {
+ delete typeList[j];
+ }
+ }
+ }
+
const size_t N = packages.size();
for (size_t i=0; i<N; i++) {
Package* pkg = packages[i];
@@ -2752,17 +2917,15 @@ struct ResTable::PackageGroup
void clearBagCache() {
if (bags) {
TABLE_NOISY(printf("bags=%p\n", bags));
- Package* pkg = packages[0];
- TABLE_NOISY(printf("typeCount=%x\n", typeCount));
- for (size_t i=0; i<typeCount; i++) {
+ for (size_t i = 0; i < bags->size(); i++) {
TABLE_NOISY(printf("type=%d\n", i));
- const Type* type = pkg->getType(i);
- if (type != NULL) {
- bag_set** typeBags = bags[i];
+ const TypeList& typeList = types[i];
+ if (typeList.isEmpty()) {
+ bag_set** typeBags = bags->get(i);
TABLE_NOISY(printf("typeBags=%p\n", typeBags));
if (typeBags) {
- TABLE_NOISY(printf("type->entryCount=%x\n", type->entryCount));
- const size_t N = type->entryCount;
+ const size_t N = typeList[0]->entryCount;
+ TABLE_NOISY(printf("type->entryCount=%x\n", N));
for (size_t j=0; j<N; j++) {
if (typeBags[j] && typeBags[j] != (bag_set*)0xFFFFFFFF)
free(typeBags[j]);
@@ -2771,25 +2934,45 @@ struct ResTable::PackageGroup
}
}
}
- free(bags);
+ delete bags;
bags = NULL;
}
}
- ResTable* const owner;
+ ssize_t findType16(const char16_t* type, size_t len) const {
+ const size_t N = packages.size();
+ for (size_t i = 0; i < N; i++) {
+ ssize_t index = packages[i]->typeStrings.indexOfString(type, len);
+ if (index >= 0) {
+ return index + packages[i]->typeIdOffset;
+ }
+ }
+ return -1;
+ }
+
+ const ResTable* const owner;
String16 const name;
uint32_t const id;
+
+ // This is mainly used to keep track of the loaded packages
+ // and to clean them up properly. Accessing resources happens from
+ // the 'types' array.
Vector<Package*> packages;
- // This is for finding typeStrings and other common package stuff.
- Package* basePackage;
+ ByteBucketArray<TypeList> types;
- // For quick access.
- size_t typeCount;
+ uint8_t largestTypeId;
// Computed attribute bags, first indexed by the type and second
// by the entry in that type.
- bag_set*** bags;
+ ByteBucketArray<bag_set**>* bags;
+
+ // The table mapping dynamic references to resolved references for
+ // this package group.
+ // TODO: We may be able to support dynamic references in overlays
+ // by having these tables in a per-package scope rather than
+ // per-package-group.
+ DynamicRefTable dynamicRefTable;
};
struct ResTable::bag_set
@@ -2818,7 +3001,7 @@ ResTable::Theme::~Theme()
void ResTable::Theme::free_package(package_info* pi)
{
- for (size_t j=0; j<pi->numTypes; j++) {
+ for (size_t j = 0; j <= Res_MAXTYPE; j++) {
theme_entry* te = pi->types[j].entries;
if (te != NULL) {
free(te);
@@ -2829,10 +3012,8 @@ void ResTable::Theme::free_package(package_info* pi)
ResTable::Theme::package_info* ResTable::Theme::copy_package(package_info* pi)
{
- package_info* newpi = (package_info*)malloc(
- sizeof(package_info) + (pi->numTypes*sizeof(type_info)));
- newpi->numTypes = pi->numTypes;
- for (size_t j=0; j<newpi->numTypes; j++) {
+ package_info* newpi = (package_info*)malloc(sizeof(package_info));
+ for (size_t j = 0; j <= Res_MAXTYPE; j++) {
size_t cnt = pi->types[j].numEntries;
newpi->types[j].numEntries = cnt;
theme_entry* te = pi->types[j].entries;
@@ -2885,17 +3066,14 @@ status_t ResTable::Theme::applyStyle(uint32_t resID, bool force)
curPI = mPackages[pidx];
if (curPI == NULL) {
PackageGroup* const grp = mTable.mPackageGroups[pidx];
- int cnt = grp->typeCount;
- curPI = (package_info*)malloc(
- sizeof(package_info) + (cnt*sizeof(type_info)));
- curPI->numTypes = cnt;
- memset(curPI->types, 0, cnt*sizeof(type_info));
+ curPI = (package_info*)malloc(sizeof(package_info));
+ memset(curPI, 0, sizeof(*curPI));
mPackages[pidx] = curPI;
}
curType = 0xffffffff;
}
if (curType != t) {
- if (t >= curPI->numTypes) {
+ if (t > Res_MAXTYPE) {
ALOGE("Style contains key with bad type: 0x%08x\n", attrRes);
bag++;
continue;
@@ -2904,8 +3082,8 @@ status_t ResTable::Theme::applyStyle(uint32_t resID, bool force)
curEntries = curPI->types[t].entries;
if (curEntries == NULL) {
PackageGroup* const grp = mTable.mPackageGroups[curPackageIndex];
- const Type* type = grp->packages[0]->getType(t);
- int cnt = type != NULL ? type->entryCount : 0;
+ const TypeList& typeList = grp->types[t];
+ int cnt = typeList.isEmpty() ? 0 : typeList[0]->entryCount;
curEntries = (theme_entry*)malloc(cnt*sizeof(theme_entry));
memset(curEntries, Res_value::TYPE_NULL, cnt*sizeof(theme_entry));
curPI->types[t].numEntries = cnt;
@@ -2920,8 +3098,8 @@ status_t ResTable::Theme::applyStyle(uint32_t resID, bool force)
}
theme_entry* curEntry = curEntries + e;
TABLE_NOISY(ALOGV("Attr 0x%08x: type=0x%x, data=0x%08x; curType=0x%x",
- attrRes, bag->map.value.dataType, bag->map.value.data,
- curEntry->value.dataType));
+ attrRes, bag->map.value.dataType, bag->map.value.data,
+ curEntry->value.dataType));
if (force || curEntry->value.dataType == Res_value::TYPE_NULL) {
curEntry->stringBlock = bag->stringBlock;
curEntry->typeSpecFlags |= bagTypeSpecFlags;
@@ -2996,8 +3174,8 @@ ssize_t ResTable::Theme::getAttribute(uint32_t resID, Res_value* outValue,
const package_info* const pi = mPackages[p];
TABLE_THEME(ALOGI("Found package: %p", pi));
if (pi != NULL) {
- TABLE_THEME(ALOGI("Desired type index is %ld in avail %d", t, pi->numTypes));
- if (t < pi->numTypes) {
+ TABLE_THEME(ALOGI("Desired type index is %ld in avail %d", t, Res_MAXTYPE + 1));
+ if (t <= Res_MAXTYPE) {
const type_info& ti = pi->types[t];
TABLE_THEME(ALOGI("Desired entry index is %ld in avail %d", e, ti.numEntries));
if (e < ti.numEntries) {
@@ -3059,14 +3237,13 @@ void ResTable::Theme::dumpToLog() const
package_info* pi = mPackages[i];
if (pi == NULL) continue;
- ALOGI(" Package #0x%02x:\n", (int)(i+1));
- for (size_t j=0; j<pi->numTypes; j++) {
+ ALOGI(" Package #0x%02x:\n", (int)(i + 1));
+ for (size_t j = 0; j <= Res_MAXTYPE; j++) {
type_info& ti = pi->types[j];
if (ti.numEntries == 0) continue;
-
- ALOGI(" Type #0x%02x:\n", (int)(j+1));
- for (size_t k=0; k<ti.numEntries; k++) {
- theme_entry& te = ti.entries[k];
+ ALOGI(" Type #0x%02x:\n", (int)(j + 1));
+ for (size_t k = 0; k < ti.numEntries; k++) {
+ const theme_entry& te = ti.entries[k];
if (te.value.dataType == Res_value::TYPE_NULL) continue;
ALOGI(" 0x%08x: t=0x%x, d=0x%08x (block=%d)\n",
(int)Res_MAKEID(i, j, k),
@@ -3077,7 +3254,7 @@ void ResTable::Theme::dumpToLog() const
}
ResTable::ResTable()
- : mError(NO_INIT)
+ : mError(NO_INIT), mNextPackageId(2)
{
memset(&mParams, 0, sizeof(mParams));
memset(mPackageMap, 0, sizeof(mPackageMap));
@@ -3085,11 +3262,11 @@ ResTable::ResTable()
}
ResTable::ResTable(const void* data, size_t size, const int32_t cookie, bool copyData)
- : mError(NO_INIT)
+ : mError(NO_INIT), mNextPackageId(2)
{
memset(&mParams, 0, sizeof(mParams));
memset(mPackageMap, 0, sizeof(mPackageMap));
- addInternal(data, size, cookie, NULL /* asset */, copyData, NULL /* idMap */);
+ addInternal(data, size, NULL, 0, cookie, copyData);
LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table");
//ALOGI("Creating ResTable %p\n", this);
}
@@ -3105,21 +3282,45 @@ inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const
return ((ssize_t)mPackageMap[Res_GETPACKAGE(resID)+1])-1;
}
-status_t ResTable::add(const void* data, size_t size) {
- return addInternal(data, size, 0 /* cookie */, NULL /* asset */,
- false /* copyData */, NULL /* idMap */);
+status_t ResTable::add(const void* data, size_t size, const int32_t cookie, bool copyData) {
+ return addInternal(data, size, NULL, 0, cookie, copyData);
}
-status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData, const void* idmap)
-{
+status_t ResTable::add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,
+ const int32_t cookie, bool copyData) {
+ return addInternal(data, size, idmapData, idmapDataSize, cookie, copyData);
+}
+
+status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) {
const void* data = asset->getBuffer(true);
if (data == NULL) {
ALOGW("Unable to get buffer of resource asset file");
return UNKNOWN_ERROR;
}
- size_t size = (size_t)asset->getLength();
- return addInternal(data, size, cookie, asset, copyData,
- reinterpret_cast<const Asset*>(idmap));
+
+ return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, 0, cookie, copyData);
+}
+
+status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) {
+ const void* data = asset->getBuffer(true);
+ if (data == NULL) {
+ ALOGW("Unable to get buffer of resource asset file");
+ return UNKNOWN_ERROR;
+ }
+
+ size_t idmapSize = 0;
+ const void* idmapData = NULL;
+ if (idmapAsset != NULL) {
+ idmapData = idmapAsset->getBuffer(true);
+ if (idmapData == NULL) {
+ ALOGW("Unable to get buffer of idmap asset file");
+ return UNKNOWN_ERROR;
+ }
+ idmapSize = static_cast<size_t>(idmapAsset->getLength());
+ }
+
+ return addInternal(data, static_cast<size_t>(asset->getLength()),
+ idmapData, idmapSize, cookie, copyData);
}
status_t ResTable::add(ResTable* src)
@@ -3136,8 +3337,16 @@ status_t ResTable::add(ResTable* src)
for (size_t j=0; j<srcPg->packages.size(); j++) {
pg->packages.add(srcPg->packages[j]);
}
- pg->basePackage = srcPg->basePackage;
- pg->typeCount = srcPg->typeCount;
+
+ for (size_t j = 0; j < srcPg->types.size(); j++) {
+ if (srcPg->types[j].isEmpty()) {
+ continue;
+ }
+
+ TypeList& typeList = pg->types.editItemAt(j);
+ typeList.appendVector(srcPg->types[j]);
+ }
+ pg->largestTypeId = max(pg->largestTypeId, srcPg->largestTypeId);
mPackageGroups.add(pg);
}
@@ -3146,38 +3355,62 @@ status_t ResTable::add(ResTable* src)
return mError;
}
-status_t ResTable::addInternal(const void* data, size_t size, const int32_t cookie,
- Asset* /*asset*/, bool copyData, const Asset* idmap)
+status_t ResTable::addEmpty(const int32_t cookie) {
+ Header* header = new Header(this);
+ header->index = mHeaders.size();
+ header->cookie = cookie;
+ header->values.setToEmpty();
+ header->ownedData = calloc(1, sizeof(ResTable_header));
+
+ ResTable_header* resHeader = (ResTable_header*) header->ownedData;
+ resHeader->header.type = RES_TABLE_TYPE;
+ resHeader->header.headerSize = sizeof(ResTable_header);
+ resHeader->header.size = sizeof(ResTable_header);
+
+ header->header = (const ResTable_header*) resHeader;
+ mHeaders.add(header);
+ return (mError=NO_ERROR);
+}
+
+status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize,
+ const int32_t cookie, bool copyData)
{
- if (!data) return NO_ERROR;
+ if (!data) {
+ return NO_ERROR;
+ }
+
+ if (dataSize < sizeof(ResTable_header)) {
+ ALOGE("Invalid data. Size(%d) is smaller than a ResTable_header(%d).",
+ (int) dataSize, (int) sizeof(ResTable_header));
+ return UNKNOWN_ERROR;
+ }
+
Header* header = new Header(this);
header->index = mHeaders.size();
header->cookie = cookie;
- if (idmap != NULL) {
- const size_t idmap_size = idmap->getLength();
- const void* idmap_data = const_cast<Asset*>(idmap)->getBuffer(true);
- header->resourceIDMap = (uint32_t*)malloc(idmap_size);
+ if (idmapData != NULL) {
+ header->resourceIDMap = (uint32_t*) malloc(idmapDataSize);
if (header->resourceIDMap == NULL) {
delete header;
return (mError = NO_MEMORY);
}
- memcpy((void*)header->resourceIDMap, idmap_data, idmap_size);
- header->resourceIDMapSize = idmap_size;
+ memcpy(header->resourceIDMap, idmapData, idmapDataSize);
+ header->resourceIDMapSize = idmapDataSize;
}
mHeaders.add(header);
const bool notDeviceEndian = htods(0xf0) != 0xf0;
LOAD_TABLE_NOISY(
- ALOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%d, asset=%p, copy=%d "
- "idmap=%p\n", data, size, cookie, asset, copyData, idmap));
+ ALOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%d, copy=%d "
+ "idmap=%p\n", data, dataSize, cookie, copyData, idmap));
if (copyData || notDeviceEndian) {
- header->ownedData = malloc(size);
+ header->ownedData = malloc(dataSize);
if (header->ownedData == NULL) {
return (mError=NO_MEMORY);
}
- memcpy(header->ownedData, data, size);
+ memcpy(header->ownedData, data, dataSize);
data = header->ownedData;
}
@@ -3186,13 +3419,11 @@ status_t ResTable::addInternal(const void* data, size_t size, const int32_t cook
//ALOGI("Got size 0x%x, again size 0x%x, raw size 0x%x\n", header->size,
// dtohl(header->header->header.size), header->header->header.size);
LOAD_TABLE_NOISY(ALOGV("Loading ResTable @%p:\n", header->header));
- LOAD_TABLE_NOISY(printHexData(2, header->header, header->size < 256 ? header->size : 256,
- 16, 16, 0, false, printToLogFunc));
if (dtohs(header->header->header.headerSize) > header->size
- || header->size > size) {
+ || header->size > dataSize) {
ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n",
(int)dtohs(header->header->header.headerSize),
- (int)header->size, (int)size);
+ (int)header->size, (int)dataSize);
return (mError=BAD_TYPE);
}
if (((dtohs(header->header->header.headerSize)|header->size)&0x3) != 0) {
@@ -3237,16 +3468,8 @@ status_t ResTable::addInternal(const void* data, size_t size, const int32_t cook
dtohl(header->header->packageCount));
return (mError=BAD_TYPE);
}
- uint32_t idmap_id = 0;
- if (idmap != NULL) {
- uint32_t tmp;
- if (getIdmapPackageId(header->resourceIDMap,
- header->resourceIDMapSize,
- &tmp) == NO_ERROR) {
- idmap_id = tmp;
- }
- }
- if (parsePackage((ResTable_package*)chunk, header, idmap_id) != NO_ERROR) {
+
+ if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) {
return mError;
}
curPackage++;
@@ -3329,46 +3552,38 @@ bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* ou
ALOGW("Bad identifier when getting name for resource number 0x%08x", resID);
return false;
}
- if (grp->packages.size() > 0) {
- const Package* const package = grp->packages[0];
- const ResTable_type* type;
- const ResTable_entry* entry;
- ssize_t offset = getEntry(package, t, e, NULL, &type, &entry, NULL);
- if (offset <= 0) {
- return false;
- }
+ Entry entry;
+ status_t err = getEntry(grp, t, e, NULL, &entry);
+ if (err != NO_ERROR) {
+ return false;
+ }
- outName->package = grp->name.string();
- outName->packageLen = grp->name.size();
- if (allowUtf8) {
- outName->type8 = grp->basePackage->typeStrings.string8At(t, &outName->typeLen);
- outName->name8 = grp->basePackage->keyStrings.string8At(
- dtohl(entry->key.index), &outName->nameLen);
- } else {
- outName->type8 = NULL;
- outName->name8 = NULL;
- }
- if (outName->type8 == NULL) {
- outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen);
- // If we have a bad index for some reason, we should abort.
- if (outName->type == NULL) {
- return false;
- }
+ outName->package = grp->name.string();
+ outName->packageLen = grp->name.size();
+ if (allowUtf8) {
+ outName->type8 = entry.typeStr.string8(&outName->typeLen);
+ outName->name8 = entry.keyStr.string8(&outName->nameLen);
+ } else {
+ outName->type8 = NULL;
+ outName->name8 = NULL;
+ }
+ if (outName->type8 == NULL) {
+ outName->type = entry.typeStr.string16(&outName->typeLen);
+ // If we have a bad index for some reason, we should abort.
+ if (outName->type == NULL) {
+ return false;
}
- if (outName->name8 == NULL) {
- outName->name = grp->basePackage->keyStrings.stringAt(
- dtohl(entry->key.index), &outName->nameLen);
- // If we have a bad index for some reason, we should abort.
- if (outName->name == NULL) {
- return false;
- }
+ }
+ if (outName->name8 == NULL) {
+ outName->name = entry.keyStr.string16(&outName->nameLen);
+ // If we have a bad index for some reason, we should abort.
+ if (outName->name == NULL) {
+ return false;
}
-
- return true;
}
- return false;
+ return true;
}
ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
@@ -3395,15 +3610,6 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag
return BAD_INDEX;
}
- const Res_value* bestValue = NULL;
- const Package* bestPackage = NULL;
- ResTable_config bestItem;
- memset(&bestItem, 0, sizeof(bestItem)); // make the compiler shut up
-
- if (outSpecFlags != NULL) *outSpecFlags = 0;
-
- // Look through all resource packages, starting with the most
- // recently added.
const PackageGroup* const grp = mPackageGroups[p];
if (grp == NULL) {
ALOGW("Bad identifier when getting value for resource number 0x%08x", resID);
@@ -3411,133 +3617,62 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag
}
// Allow overriding density
- const ResTable_config* desiredConfig = &mParams;
- ResTable_config* overrideConfig = NULL;
+ ResTable_config desiredConfig = mParams;
if (density > 0) {
- overrideConfig = (ResTable_config*) malloc(sizeof(ResTable_config));
- if (overrideConfig == NULL) {
- ALOGE("Couldn't malloc ResTable_config for overrides: %s", strerror(errno));
- return BAD_INDEX;
- }
- memcpy(overrideConfig, &mParams, sizeof(ResTable_config));
- overrideConfig->density = density;
- desiredConfig = overrideConfig;
- }
-
- ssize_t rc = BAD_VALUE;
- size_t ip = grp->packages.size();
- while (ip > 0) {
- ip--;
- int T = t;
- int E = e;
-
- const Package* const package = grp->packages[ip];
- if (package->header->resourceIDMap) {
- uint32_t overlayResID = 0x0;
- status_t retval = idmapLookup(package->header->resourceIDMap,
- package->header->resourceIDMapSize,
- resID, &overlayResID);
- if (retval == NO_ERROR && overlayResID != 0x0) {
- // for this loop iteration, this is the type and entry we really want
- ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
- T = Res_GETTYPE(overlayResID);
- E = Res_GETENTRY(overlayResID);
- } else {
- // resource not present in overlay package, continue with the next package
- continue;
- }
- }
-
- const ResTable_type* type;
- const ResTable_entry* entry;
- const Type* typeClass;
- ssize_t offset = getEntry(package, T, E, desiredConfig, &type, &entry, &typeClass);
- if (offset <= 0) {
- // No {entry, appropriate config} pair found in package. If this
- // package is an overlay package (ip != 0), this simply means the
- // overlay package did not specify a default.
- // Non-overlay packages are still required to provide a default.
- if (offset < 0 && ip == 0) {
- ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %zd (error %d)\n",
- resID, T, E, ip, (int)offset);
- rc = offset;
- goto out;
- }
- continue;
- }
-
- if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) != 0) {
- if (!mayBeBag) {
- ALOGW("Requesting resource 0x%x failed because it is complex\n",
- resID);
- }
- continue;
- }
+ desiredConfig.density = density;
+ }
- TABLE_NOISY(aout << "Resource type data: "
- << HexDump(type, dtohl(type->header.size)) << endl);
+ Entry entry;
+ status_t err = getEntry(grp, t, e, &desiredConfig, &entry);
+ if (err != NO_ERROR) {
+ ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) (error %d)\n",
+ resID, t, e, err);
+ return err;
+ }
- if ((size_t)offset > (dtohl(type->header.size)-sizeof(Res_value))) {
- ALOGW("ResTable_item at %d is beyond type chunk data %d",
- (int)offset, dtohl(type->header.size));
- rc = BAD_TYPE;
- goto out;
+ if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (!mayBeBag) {
+ ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
}
+ return BAD_VALUE;
+ }
- const Res_value* item =
- (const Res_value*)(((const uint8_t*)type) + offset);
- ResTable_config thisConfig;
- thisConfig.copyFromDtoH(type->config);
-
- if (outSpecFlags != NULL) {
- if (typeClass->typeSpecFlags != NULL) {
- *outSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
- } else {
- *outSpecFlags = -1;
- }
- }
+ const Res_value* value = reinterpret_cast<const Res_value*>(
+ reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);
- if (bestPackage != NULL &&
- (bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) {
- // Discard thisConfig not only if bestItem is more specific, but also if the two configs
- // are identical (diff == 0), or overlay packages will not take effect.
- continue;
- }
+ outValue->size = dtohs(value->size);
+ outValue->res0 = value->res0;
+ outValue->dataType = value->dataType;
+ outValue->data = dtohl(value->data);
- bestItem = thisConfig;
- bestValue = item;
- bestPackage = package;
+ // The reference may be pointing to a resource in a shared library. These
+ // references have build-time generated package IDs. These ids may not match
+ // the actual package IDs of the corresponding packages in this ResTable.
+ // We need to fix the package ID based on a mapping.
+ if (grp->dynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) {
+ ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data);
+ return BAD_VALUE;
}
- TABLE_NOISY(printf("Found result: package %p\n", bestPackage));
+ TABLE_NOISY(size_t len;
+ printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n",
+ entry.package->header->index,
+ outValue->dataType,
+ outValue->dataType == Res_value::TYPE_STRING
+ ? String8(entry.package->header->values.stringAt(
+ outValue->data, &len)).string()
+ : "",
+ outValue->data));
- if (bestValue) {
- outValue->size = dtohs(bestValue->size);
- outValue->res0 = bestValue->res0;
- outValue->dataType = bestValue->dataType;
- outValue->data = dtohl(bestValue->data);
- if (outConfig != NULL) {
- *outConfig = bestItem;
- }
- TABLE_NOISY(size_t len;
- printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n",
- bestPackage->header->index,
- outValue->dataType,
- outValue->dataType == bestValue->TYPE_STRING
- ? String8(bestPackage->header->values.stringAt(
- outValue->data, &len)).string()
- : "",
- outValue->data));
- rc = bestPackage->header->index;
- goto out;
+ if (outSpecFlags != NULL) {
+ *outSpecFlags = entry.specFlags;
}
-out:
- if (overrideConfig != NULL) {
- free(overrideConfig);
+ if (outConfig != NULL) {
+ *outConfig = entry.config;
}
- return rc;
+ return entry.package->header->index;
}
ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
@@ -3545,8 +3680,8 @@ ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
ResTable_config* outConfig) const
{
int count=0;
- while (blockIndex >= 0 && value->dataType == value->TYPE_REFERENCE
- && value->data != 0 && count < 20) {
+ while (blockIndex >= 0 && value->dataType == Res_value::TYPE_REFERENCE
+ && value->data != 0 && count < 20) {
if (outLastRef) *outLastRef = value->data;
uint32_t lastRef = value->data;
uint32_t newFlags = 0;
@@ -3636,29 +3771,25 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
PackageGroup* const grp = mPackageGroups[p];
if (grp == NULL) {
ALOGW("Bad identifier when getting bag for resource number 0x%08x", resID);
- return false;
+ return BAD_INDEX;
}
- if (t >= (int)grp->typeCount) {
- ALOGW("Type identifier 0x%x is larger than type count 0x%x",
- t+1, (int)grp->typeCount);
+ const TypeList& typeConfigs = grp->types[t];
+ if (typeConfigs.isEmpty()) {
+ ALOGW("Type identifier 0x%x does not exist.", t+1);
return BAD_INDEX;
}
- const Package* const basePackage = grp->packages[0];
-
- const Type* const typeConfigs = basePackage->getType(t);
-
- const size_t NENTRY = typeConfigs->entryCount;
+ const size_t NENTRY = typeConfigs[0]->entryCount;
if (e >= (int)NENTRY) {
ALOGW("Entry identifier 0x%x is larger than entry count 0x%x",
- e, (int)typeConfigs->entryCount);
+ e, (int)typeConfigs[0]->entryCount);
return BAD_INDEX;
}
// First see if we've already computed this bag...
if (grp->bags) {
- bag_set** typeSet = grp->bags[t];
+ bag_set** typeSet = grp->bags->get(t);
if (typeSet) {
bag_set* set = typeSet[e];
if (set) {
@@ -3679,211 +3810,184 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
// Bag not found, we need to compute it!
if (!grp->bags) {
- grp->bags = (bag_set***)calloc(grp->typeCount, sizeof(bag_set*));
+ grp->bags = new ByteBucketArray<bag_set**>();
if (!grp->bags) return NO_MEMORY;
}
- bag_set** typeSet = grp->bags[t];
+ bag_set** typeSet = grp->bags->get(t);
if (!typeSet) {
typeSet = (bag_set**)calloc(NENTRY, sizeof(bag_set*));
if (!typeSet) return NO_MEMORY;
- grp->bags[t] = typeSet;
+ grp->bags->set(t, typeSet);
}
// Mark that we are currently working on this one.
typeSet[e] = (bag_set*)0xFFFFFFFF;
+ TABLE_NOISY(ALOGI("Building bag: %p\n", (void*)resID));
+
+ // Now collect all bag attributes
+ Entry entry;
+ status_t err = getEntry(grp, t, e, &mParams, &entry);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ const uint16_t entrySize = dtohs(entry.entry->size);
+ const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
+ ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0;
+ const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
+ ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0;
+
+ size_t N = count;
+
+ TABLE_NOISY(ALOGI("Found map: size=%p parent=%p count=%d\n",
+ entrySize, parent, count));
+
+ // If this map inherits from another, we need to start
+ // with its parent's values. Otherwise start out empty.
+ TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n",
+ entrySize, parent));
+
// This is what we are building.
bag_set* set = NULL;
- TABLE_NOISY(ALOGI("Building bag: %p\n", (void*)resID));
+ if (parent) {
+ uint32_t resolvedParent = parent;
- ResTable_config bestConfig;
- memset(&bestConfig, 0, sizeof(bestConfig));
+ // Bags encode a parent reference without using the standard
+ // Res_value structure. That means we must always try to
+ // resolve a parent reference in case it is actually a
+ // TYPE_DYNAMIC_REFERENCE.
+ status_t err = grp->dynamicRefTable.lookupResourceId(&resolvedParent);
+ if (err != NO_ERROR) {
+ ALOGE("Failed resolving bag parent id 0x%08x", parent);
+ return UNKNOWN_ERROR;
+ }
- // Now collect all bag attributes from all packages.
- size_t ip = grp->packages.size();
- while (ip > 0) {
- ip--;
- int T = t;
- int E = e;
-
- const Package* const package = grp->packages[ip];
- if (package->header->resourceIDMap) {
- uint32_t overlayResID = 0x0;
- status_t retval = idmapLookup(package->header->resourceIDMap,
- package->header->resourceIDMapSize,
- resID, &overlayResID);
- if (retval == NO_ERROR && overlayResID != 0x0) {
- // for this loop iteration, this is the type and entry we really want
- ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
- T = Res_GETTYPE(overlayResID);
- E = Res_GETENTRY(overlayResID);
- } else {
- // resource not present in overlay package, continue with the next package
- continue;
- }
+ const bag_entry* parentBag;
+ uint32_t parentTypeSpecFlags = 0;
+ const ssize_t NP = getBagLocked(resolvedParent, &parentBag, &parentTypeSpecFlags);
+ const size_t NT = ((NP >= 0) ? NP : 0) + N;
+ set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
+ if (set == NULL) {
+ return NO_MEMORY;
}
+ if (NP > 0) {
+ memcpy(set+1, parentBag, NP*sizeof(bag_entry));
+ set->numAttrs = NP;
+ TABLE_NOISY(ALOGI("Initialized new bag with %d inherited attributes.\n", NP));
+ } else {
+ TABLE_NOISY(ALOGI("Initialized new bag with no inherited attributes.\n"));
+ set->numAttrs = 0;
+ }
+ set->availAttrs = NT;
+ set->typeSpecFlags = parentTypeSpecFlags;
+ } else {
+ set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ set->numAttrs = 0;
+ set->availAttrs = N;
+ set->typeSpecFlags = 0;
+ }
- const ResTable_type* type;
- const ResTable_entry* entry;
- const Type* typeClass;
- ALOGV("Getting entry pkg=%p, t=%d, e=%d\n", package, T, E);
- ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass);
- ALOGV("Resulting offset=%d\n", offset);
- if (offset <= 0) {
- // No {entry, appropriate config} pair found in package. If this
- // package is an overlay package (ip != 0), this simply means the
- // overlay package did not specify a default.
- // Non-overlay packages are still required to provide a default.
- if (offset < 0 && ip == 0) {
- if (set) free(set);
- return offset;
- }
- continue;
+ set->typeSpecFlags |= entry.specFlags;
+
+ // Now merge in the new attributes...
+ size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type))
+ + dtohs(entry.entry->size);
+ const ResTable_map* map;
+ bag_entry* entries = (bag_entry*)(set+1);
+ size_t curEntry = 0;
+ uint32_t pos = 0;
+ TABLE_NOISY(ALOGI("Starting with set %p, entries=%p, avail=%d\n",
+ set, entries, set->availAttrs));
+ while (pos < count) {
+ TABLE_NOISY(printf("Now at %p\n", (void*)curOff));
+
+ if (curOff > (dtohl(entry.type->header.size)-sizeof(ResTable_map))) {
+ ALOGW("ResTable_map at %d is beyond type chunk data %d",
+ (int)curOff, dtohl(entry.type->header.size));
+ return BAD_TYPE;
}
+ map = (const ResTable_map*)(((const uint8_t*)entry.type) + curOff);
+ N++;
- if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) == 0) {
- ALOGW("Skipping entry 0x%x in package table %zu because it is not complex!\n",
- resID, ip);
- continue;
+ uint32_t newName = htodl(map->name.ident);
+ if (!Res_INTERNALID(newName)) {
+ // Attributes don't have a resource id as the name. They specify
+ // other data, which would be wrong to change via a lookup.
+ if (grp->dynamicRefTable.lookupResourceId(&newName) != NO_ERROR) {
+ ALOGE("Failed resolving ResTable_map name at %d with ident 0x%08x",
+ (int) curOff, (int) newName);
+ return UNKNOWN_ERROR;
+ }
}
- if (set != NULL && !type->config.isBetterThan(bestConfig, NULL)) {
- continue;
+ bool isInside;
+ uint32_t oldName = 0;
+ while ((isInside=(curEntry < set->numAttrs))
+ && (oldName=entries[curEntry].map.name.ident) < newName) {
+ TABLE_NOISY(printf("#%d: Keeping existing attribute: 0x%08x\n",
+ curEntry, entries[curEntry].map.name.ident));
+ curEntry++;
}
- bestConfig = type->config;
- if (set) {
- free(set);
- set = NULL;
- }
-
- const uint16_t entrySize = dtohs(entry->size);
- const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry)->parent.ident) : 0;
- const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry)->count) : 0;
-
- size_t N = count;
-
- TABLE_NOISY(ALOGI("Found map: size=%p parent=%p count=%d\n",
- entrySize, parent, count));
-
- // If this map inherits from another, we need to start
- // with its parent's values. Otherwise start out empty.
- TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n",
- entrySize, parent));
- if (parent) {
- const bag_entry* parentBag;
- uint32_t parentTypeSpecFlags = 0;
- const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags);
- const size_t NT = ((NP >= 0) ? NP : 0) + N;
- set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
- if (set == NULL) {
- return NO_MEMORY;
- }
- if (NP > 0) {
- memcpy(set+1, parentBag, NP*sizeof(bag_entry));
- set->numAttrs = NP;
- TABLE_NOISY(ALOGI("Initialized new bag with %d inherited attributes.\n", NP));
- } else {
- TABLE_NOISY(ALOGI("Initialized new bag with no inherited attributes.\n"));
- set->numAttrs = 0;
- }
- set->availAttrs = NT;
- set->typeSpecFlags = parentTypeSpecFlags;
+
+ if ((!isInside) || oldName != newName) {
+ // This is a new attribute... figure out what to do with it.
+ if (set->numAttrs >= set->availAttrs) {
+ // Need to alloc more memory...
+ const size_t newAvail = set->availAttrs+N;
+ set = (bag_set*)realloc(set,
+ sizeof(bag_set)
+ + sizeof(bag_entry)*newAvail);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ set->availAttrs = newAvail;
+ entries = (bag_entry*)(set+1);
+ TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n",
+ set, entries, set->availAttrs));
+ }
+ if (isInside) {
+ // Going in the middle, need to make space.
+ memmove(entries+curEntry+1, entries+curEntry,
+ sizeof(bag_entry)*(set->numAttrs-curEntry));
+ set->numAttrs++;
+ }
+ TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n",
+ curEntry, newName));
} else {
- set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N);
- if (set == NULL) {
- return NO_MEMORY;
- }
- set->numAttrs = 0;
- set->availAttrs = N;
- set->typeSpecFlags = 0;
+ TABLE_NOISY(printf("#%d: Replacing existing attribute: 0x%08x\n",
+ curEntry, oldName));
}
- if (typeClass->typeSpecFlags != NULL) {
- set->typeSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
- } else {
- set->typeSpecFlags = -1;
- }
-
- // Now merge in the new attributes...
- ssize_t curOff = offset;
- const ResTable_map* map;
- bag_entry* entries = (bag_entry*)(set+1);
- size_t curEntry = 0;
- uint32_t pos = 0;
- TABLE_NOISY(ALOGI("Starting with set %p, entries=%p, avail=%d\n",
- set, entries, set->availAttrs));
- while (pos < count) {
- TABLE_NOISY(printf("Now at %p\n", (void*)curOff));
-
- if ((size_t)curOff > (dtohl(type->header.size)-sizeof(ResTable_map))) {
- ALOGW("ResTable_map at %d is beyond type chunk data %d",
- (int)curOff, dtohl(type->header.size));
- return BAD_TYPE;
- }
- map = (const ResTable_map*)(((const uint8_t*)type) + curOff);
- N++;
-
- const uint32_t newName = htodl(map->name.ident);
- bool isInside;
- uint32_t oldName = 0;
- while ((isInside=(curEntry < set->numAttrs))
- && (oldName=entries[curEntry].map.name.ident) < newName) {
- TABLE_NOISY(printf("#%d: Keeping existing attribute: 0x%08x\n",
- curEntry, entries[curEntry].map.name.ident));
- curEntry++;
- }
-
- if ((!isInside) || oldName != newName) {
- // This is a new attribute... figure out what to do with it.
- if (set->numAttrs >= set->availAttrs) {
- // Need to alloc more memory...
- const size_t newAvail = set->availAttrs+N;
- set = (bag_set*)realloc(set,
- sizeof(bag_set)
- + sizeof(bag_entry)*newAvail);
- if (set == NULL) {
- return NO_MEMORY;
- }
- set->availAttrs = newAvail;
- entries = (bag_entry*)(set+1);
- TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n",
- set, entries, set->availAttrs));
- }
- if (isInside) {
- // Going in the middle, need to make space.
- memmove(entries+curEntry+1, entries+curEntry,
- sizeof(bag_entry)*(set->numAttrs-curEntry));
- set->numAttrs++;
- }
- TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n",
- curEntry, newName));
- } else {
- TABLE_NOISY(printf("#%d: Replacing existing attribute: 0x%08x\n",
- curEntry, oldName));
- }
+ bag_entry* cur = entries+curEntry;
- bag_entry* cur = entries+curEntry;
+ cur->stringBlock = entry.package->header->index;
+ cur->map.name.ident = newName;
+ cur->map.value.copyFrom_dtoh(map->value);
+ status_t err = grp->dynamicRefTable.lookupResourceValue(&cur->map.value);
+ if (err != NO_ERROR) {
+ ALOGE("Reference item(0x%08x) in bag could not be resolved.", cur->map.value.data);
+ return UNKNOWN_ERROR;
+ }
- cur->stringBlock = package->header->index;
- cur->map.name.ident = newName;
- cur->map.value.copyFrom_dtoh(map->value);
- TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n",
- curEntry, cur, cur->stringBlock, cur->map.name.ident,
- cur->map.value.dataType, cur->map.value.data));
+ TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n",
+ curEntry, cur, cur->stringBlock, cur->map.name.ident,
+ cur->map.value.dataType, cur->map.value.data));
- // On to the next!
- curEntry++;
- pos++;
- const size_t size = dtohs(map->value.size);
- curOff += size + sizeof(*map)-sizeof(map->value);
- };
- if (curEntry > set->numAttrs) {
- set->numAttrs = curEntry;
- }
+ // On to the next!
+ curEntry++;
+ pos++;
+ const size_t size = dtohs(map->value.size);
+ curOff += size + sizeof(*map)-sizeof(map->value);
+ };
+
+ if (curEntry > set->numAttrs) {
+ set->numAttrs = curEntry;
}
// And this is it...
@@ -4051,80 +4155,63 @@ nope:
continue;
}
- const ssize_t ti = group->basePackage->typeStrings.indexOfString(type, typeLen);
+ const ssize_t ti = group->findType16(type, typeLen);
if (ti < 0) {
TABLE_NOISY(printf("Type not found in package %s\n", String8(group->name).string()));
continue;
}
- const ssize_t ei = group->basePackage->keyStrings.indexOfString(name, nameLen);
- if (ei < 0) {
- TABLE_NOISY(printf("Name not found in package %s\n", String8(group->name).string()));
- continue;
- }
-
- TABLE_NOISY(printf("Search indices: type=%d, name=%d\n", ti, ei));
-
- const Type* const typeConfigs = group->packages[0]->getType(ti);
- if (typeConfigs == NULL || typeConfigs->configs.size() <= 0) {
- TABLE_NOISY(printf("Expected type structure not found in package %s for idnex %d\n",
+ const TypeList& typeList = group->types[ti];
+ if (typeList.isEmpty()) {
+ TABLE_NOISY(printf("Expected type structure not found in package %s for index %d\n",
String8(group->name).string(), ti));
+ continue;
}
- size_t NTC = typeConfigs->configs.size();
- for (size_t tci=0; tci<NTC; tci++) {
- const ResTable_type* const ty = typeConfigs->configs[tci];
- const uint32_t typeOffset = dtohl(ty->entriesStart);
-
- const uint8_t* const end = ((const uint8_t*)ty) + dtohl(ty->header.size);
- const uint32_t* const eindex = (const uint32_t*)
- (((const uint8_t*)ty) + dtohs(ty->header.headerSize));
-
- const size_t NE = dtohl(ty->entryCount);
- for (size_t i=0; i<NE; i++) {
- uint32_t offset = dtohl(eindex[i]);
- if (offset == ResTable_type::NO_ENTRY) {
- continue;
- }
-
- offset += typeOffset;
+ const size_t typeCount = typeList.size();
+ for (size_t i = 0; i < typeCount; i++) {
+ const Type* t = typeList[i];
+ const ssize_t ei = t->package->keyStrings.indexOfString(name, nameLen);
+ if (ei < 0) {
+ continue;
+ }
- if (offset > (dtohl(ty->header.size)-sizeof(ResTable_entry))) {
- ALOGW("ResTable_entry at %d is beyond type chunk data %d",
- offset, dtohl(ty->header.size));
- return 0;
- }
- if ((offset&0x3) != 0) {
- ALOGW("ResTable_entry at %d (pkg=%d type=%d ent=%d) is not on an integer boundary when looking for %s:%s/%s",
- (int)offset, (int)group->id, (int)ti+1, (int)i,
- String8(package, packageLen).string(),
- String8(type, typeLen).string(),
- String8(name, nameLen).string());
- return 0;
- }
+ const size_t configCount = t->configs.size();
+ for (size_t j = 0; j < configCount; j++) {
+ const TypeVariant tv(t->configs[j]);
+ for (TypeVariant::iterator iter = tv.beginEntries();
+ iter != tv.endEntries();
+ iter++) {
+ const ResTable_entry* entry = *iter;
+ if (entry == NULL) {
+ continue;
+ }
- const ResTable_entry* const entry = (const ResTable_entry*)
- (((const uint8_t*)ty) + offset);
- if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGW("ResTable_entry size %d is too small", dtohs(entry->size));
- return BAD_TYPE;
- }
+ if (dtohl(entry->key.index) == (size_t) ei) {
+ uint32_t resId = Res_MAKEID(group->id - 1, ti, iter.index());
+ if (outTypeSpecFlags) {
+ Entry result;
+ if (getEntry(group, ti, iter.index(), NULL, &result) != NO_ERROR) {
+ ALOGW("Failed to find spec flags for %s:%s/%s (0x%08x)",
+ String8(group->name).string(),
+ String8(String16(type, typeLen)).string(),
+ String8(String16(name, nameLen)).string(),
+ resId);
+ return 0;
+ }
+ *outTypeSpecFlags = result.specFlags;
- TABLE_SUPER_NOISY(printf("Looking at entry #%d: want str %d, have %d\n",
- i, ei, dtohl(entry->key.index)));
- if (dtohl(entry->key.index) == (size_t)ei) {
- if (outTypeSpecFlags) {
- *outTypeSpecFlags = typeConfigs->typeSpecFlags[i];
- if (fakePublic) {
- *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
+ if (fakePublic) {
+ *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
+ }
}
+ return resId;
}
- return Res_MAKEID(group->id-1, ti, i);
}
}
}
+ break;
}
-
return 0;
}
@@ -4561,23 +4648,29 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString,
type.size(), package.string(), package.size(), &specFlags);
if (rid != 0) {
if (enforcePrivate) {
- if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) {
- if (accessor != NULL) {
- accessor->reportError(accessorCookie, "Resource is not public.");
+ if (accessor == NULL || accessor->getAssetsPackage() != package) {
+ if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Resource is not public.");
+ }
+ return false;
}
- return false;
}
}
- if (!accessor) {
- outValue->data = rid;
- return true;
+
+ if (accessor) {
+ rid = Res_MAKEID(
+ accessor->getRemappedPackage(Res_GETPACKAGE(rid)),
+ Res_GETTYPE(rid), Res_GETENTRY(rid));
+ TABLE_NOISY(printf("Incl %s:%s/%s: 0x%08x\n",
+ String8(package).string(), String8(type).string(),
+ String8(name).string(), rid));
+ }
+
+ uint32_t packageId = Res_GETPACKAGE(rid) + 1;
+ if (packageId != APP_PACKAGE_ID && packageId != SYS_PACKAGE_ID) {
+ outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
}
- rid = Res_MAKEID(
- accessor->getRemappedPackage(Res_GETPACKAGE(rid)),
- Res_GETTYPE(rid), Res_GETENTRY(rid));
- TABLE_NOISY(printf("Incl %s:%s/%s: 0x%08x\n",
- String8(package).string(), String8(type).string(),
- String8(name).string(), rid));
outValue->data = rid;
return true;
}
@@ -4589,8 +4682,17 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString,
TABLE_NOISY(printf("Pckg %s:%s/%s: 0x%08x\n",
String8(package).string(), String8(type).string(),
String8(name).string(), rid));
- outValue->data = rid;
- return true;
+ uint32_t packageId = Res_GETPACKAGE(rid) + 1;
+ if (packageId == 0x00) {
+ outValue->data = rid;
+ outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE;
+ return true;
+ } else if (packageId == APP_PACKAGE_ID || packageId == SYS_PACKAGE_ID) {
+ // We accept packageId's generated as 0x01 in order to support
+ // building the android system resources
+ outValue->data = rid;
+ return true;
+ }
}
}
}
@@ -5122,15 +5224,15 @@ size_t ResTable::getBasePackageCount() const
return mPackageGroups.size();
}
-const char16_t* ResTable::getBasePackageName(size_t idx) const
+const String16 ResTable::getBasePackageName(size_t idx) const
{
if (mError != NO_ERROR) {
- return 0;
+ return String16();
}
LOG_FATAL_IF(idx >= mPackageGroups.size(),
"Requested package index %d past package count %d",
(int)idx, (int)mPackageGroups.size());
- return mPackageGroups[idx]->name.string();
+ return mPackageGroups[idx]->name;
}
uint32_t ResTable::getBasePackageId(size_t idx) const
@@ -5144,6 +5246,18 @@ uint32_t ResTable::getBasePackageId(size_t idx) const
return mPackageGroups[idx]->id;
}
+uint32_t ResTable::getLastTypeIdForPackage(size_t idx) const
+{
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+ LOG_FATAL_IF(idx >= mPackageGroups.size(),
+ "Requested package index %d past package count %d",
+ (int)idx, (int)mPackageGroups.size());
+ const PackageGroup* const group = mPackageGroups[idx];
+ return group->largestTypeId;
+}
+
size_t ResTable::getTableCount() const
{
return mHeaders.size();
@@ -5159,34 +5273,48 @@ int32_t ResTable::getTableCookie(size_t index) const
return mHeaders[index]->cookie;
}
+const DynamicRefTable* ResTable::getDynamicRefTableForCookie(int32_t cookie) const
+{
+ const size_t N = mPackageGroups.size();
+ for (size_t i = 0; i < N; i++) {
+ const PackageGroup* pg = mPackageGroups[i];
+ size_t M = pg->packages.size();
+ for (size_t j = 0; j < M; j++) {
+ if (pg->packages[j]->header->cookie == cookie) {
+ return &pg->dynamicRefTable;
+ }
+ }
+ }
+ return NULL;
+}
+
void ResTable::getConfigurations(Vector<ResTable_config>* configs) const
{
- const size_t I = mPackageGroups.size();
- for (size_t i=0; i<I; i++) {
+ const size_t packageCount = mPackageGroups.size();
+ for (size_t i = 0; i < packageCount; i++) {
const PackageGroup* packageGroup = mPackageGroups[i];
- const size_t J = packageGroup->packages.size();
- for (size_t j=0; j<J; j++) {
- const Package* package = packageGroup->packages[j];
- const size_t K = package->types.size();
- for (size_t k=0; k<K; k++) {
- const Type* type = package->types[k];
- if (type == NULL) continue;
- const size_t L = type->configs.size();
- for (size_t l=0; l<L; l++) {
- const ResTable_type* config = type->configs[l];
+ const size_t typeCount = packageGroup->types.size();
+ for (size_t j = 0; j < typeCount; j++) {
+ const TypeList& typeList = packageGroup->types[j];
+ const size_t numTypes = typeList.size();
+ for (size_t k = 0; k < numTypes; k++) {
+ const Type* type = typeList[k];
+ const size_t numConfigs = type->configs.size();
+ for (size_t m = 0; m < numConfigs; m++) {
+ const ResTable_type* config = type->configs[m];
ResTable_config cfg;
memset(&cfg, 0, sizeof(ResTable_config));
cfg.copyFromDtoH(config->config);
// only insert unique
- const size_t M = configs->size();
- size_t m;
- for (m=0; m<M; m++) {
- if (0 == (*configs)[m].compare(cfg)) {
+ const size_t N = configs->size();
+ size_t n;
+ for (n = 0; n < N; n++) {
+ if (0 == (*configs)[n].compare(cfg)) {
break;
}
}
// if we didn't find it
- if (m == M) {
+ if (n == N) {
configs->add(cfg);
}
}
@@ -5219,123 +5347,180 @@ void ResTable::getLocales(Vector<String8>* locales) const
}
}
-ssize_t ResTable::getEntry(
- const Package* package, int typeIndex, int entryIndex,
- const ResTable_config* config,
- const ResTable_type** outType, const ResTable_entry** outEntry,
- const Type** outTypeClass) const
-{
- ALOGV("Getting entry from package %p\n", package);
- const ResTable_package* const pkg = package->package;
+StringPoolRef::StringPoolRef(const ResStringPool* pool, uint32_t index)
+ : mPool(pool), mIndex(index) {}
- const Type* allTypes = package->getType(typeIndex);
- ALOGV("allTypes=%p\n", allTypes);
- if (allTypes == NULL) {
- ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex);
- return 0;
+StringPoolRef::StringPoolRef()
+ : mPool(NULL), mIndex(0) {}
+
+const char* StringPoolRef::string8(size_t* outLen) const {
+ if (mPool != NULL) {
+ return mPool->string8At(mIndex, outLen);
}
+ if (outLen != NULL) {
+ *outLen = 0;
+ }
+ return NULL;
+}
- if ((size_t)entryIndex >= allTypes->entryCount) {
- ALOGW("getEntry failing because entryIndex %d is beyond type entryCount %d",
- entryIndex, (int)allTypes->entryCount);
+const char16_t* StringPoolRef::string16(size_t* outLen) const {
+ if (mPool != NULL) {
+ return mPool->stringAt(mIndex, outLen);
+ }
+ if (outLen != NULL) {
+ *outLen = 0;
+ }
+ return NULL;
+}
+
+status_t ResTable::getEntry(
+ const PackageGroup* packageGroup, int typeIndex, int entryIndex,
+ const ResTable_config* config,
+ Entry* outEntry) const
+{
+ const TypeList& typeList = packageGroup->types[typeIndex];
+ if (typeList.isEmpty()) {
+ ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex);
return BAD_TYPE;
}
- const ResTable_type* type = NULL;
- uint32_t offset = ResTable_type::NO_ENTRY;
+ const ResTable_type* bestType = NULL;
+ uint32_t bestOffset = ResTable_type::NO_ENTRY;
+ const Package* bestPackage = NULL;
+ uint32_t specFlags = 0;
+ uint8_t actualTypeIndex = typeIndex;
ResTable_config bestConfig;
- memset(&bestConfig, 0, sizeof(bestConfig)); // make the compiler shut up
-
- const size_t NT = allTypes->configs.size();
- for (size_t i=0; i<NT; i++) {
- const ResTable_type* const thisType = allTypes->configs[i];
- if (thisType == NULL) continue;
-
- ResTable_config thisConfig;
- thisConfig.copyFromDtoH(thisType->config);
+ memset(&bestConfig, 0, sizeof(bestConfig));
- TABLE_GETENTRY(ALOGI("Match entry 0x%x in type 0x%x (sz 0x%x): %s\n",
- entryIndex, typeIndex+1, dtohl(thisType->config.size),
- thisConfig.toString().string()));
+ // Iterate over the Types of each package.
+ const size_t typeCount = typeList.size();
+ for (size_t i = 0; i < typeCount; i++) {
+ const Type* const typeSpec = typeList[i];
+
+ int realEntryIndex = entryIndex;
+ int realTypeIndex = typeIndex;
+ bool currentTypeIsOverlay = false;
+
+ // Runtime overlay packages provide a mapping of app resource
+ // ID to package resource ID.
+ if (typeSpec->idmapEntries.hasEntries()) {
+ uint16_t overlayEntryIndex;
+ if (typeSpec->idmapEntries.lookup(entryIndex, &overlayEntryIndex) != NO_ERROR) {
+ // No such mapping exists
+ continue;
+ }
+ realEntryIndex = overlayEntryIndex;
+ realTypeIndex = typeSpec->idmapEntries.overlayTypeId() - 1;
+ currentTypeIsOverlay = true;
+ }
- // Check to make sure this one is valid for the current parameters.
- if (config && !thisConfig.match(*config)) {
- TABLE_GETENTRY(ALOGI("Does not match config!\n"));
+ if (static_cast<size_t>(realEntryIndex) >= typeSpec->entryCount) {
+ ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)",
+ Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex),
+ entryIndex, static_cast<int>(typeSpec->entryCount));
+ // We should normally abort here, but some legacy apps declare
+ // resources in the 'android' package (old bug in AAPT).
continue;
}
- // Check if there is the desired entry in this type.
+ // Aggregate all the flags for each package that defines this entry.
+ if (typeSpec->typeSpecFlags != NULL) {
+ specFlags |= dtohl(typeSpec->typeSpecFlags[realEntryIndex]);
+ } else {
+ specFlags = -1;
+ }
- const uint8_t* const end = ((const uint8_t*)thisType)
- + dtohl(thisType->header.size);
- const uint32_t* const eindex = (const uint32_t*)
- (((const uint8_t*)thisType) + dtohs(thisType->header.headerSize));
+ const size_t numConfigs = typeSpec->configs.size();
+ for (size_t c = 0; c < numConfigs; c++) {
+ const ResTable_type* const thisType = typeSpec->configs[c];
+ if (thisType == NULL) {
+ continue;
+ }
- uint32_t thisOffset = dtohl(eindex[entryIndex]);
- if (thisOffset == ResTable_type::NO_ENTRY) {
- TABLE_GETENTRY(ALOGI("Skipping because it is not defined!\n"));
- continue;
- }
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(thisType->config);
- if (type != NULL) {
- // Check if this one is less specific than the last found. If so,
- // we will skip it. We check starting with things we most care
- // about to those we least care about.
- if (!thisConfig.isBetterThan(bestConfig, config)) {
- TABLE_GETENTRY(ALOGI("This config is worse than last!\n"));
+ // Check to make sure this one is valid for the current parameters.
+ if (config != NULL && !thisConfig.match(*config)) {
continue;
}
- }
- type = thisType;
- offset = thisOffset;
- bestConfig = thisConfig;
- TABLE_GETENTRY(ALOGI("Best entry so far -- using it!\n"));
- if (!config) break;
+ // Check if there is the desired entry in this type.
+ const uint8_t* const end = reinterpret_cast<const uint8_t*>(thisType)
+ + dtohl(thisType->header.size);
+ const uint32_t* const eindex = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize));
+
+ uint32_t thisOffset = dtohl(eindex[realEntryIndex]);
+ if (thisOffset == ResTable_type::NO_ENTRY) {
+ // There is no entry for this index and configuration.
+ continue;
+ }
+
+ if (bestType != NULL) {
+ // Check if this one is less specific than the last found. If so,
+ // we will skip it. We check starting with things we most care
+ // about to those we least care about.
+ if (!thisConfig.isBetterThan(bestConfig, config)) {
+ if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) {
+ continue;
+ }
+ }
+ }
+
+ bestType = thisType;
+ bestOffset = thisOffset;
+ bestConfig = thisConfig;
+ bestPackage = typeSpec->package;
+ actualTypeIndex = realTypeIndex;
+
+ // If no config was specified, any type will do, so skip
+ if (config == NULL) {
+ break;
+ }
+ }
}
- if (type == NULL) {
- TABLE_GETENTRY(ALOGI("No value found for requested entry!\n"));
+ if (bestType == NULL) {
return BAD_INDEX;
}
- offset += dtohl(type->entriesStart);
- TABLE_NOISY(aout << "Looking in resource table " << package->header->header
- << ", typeOff="
- << (void*)(((const char*)type)-((const char*)package->header->header))
- << ", offset=" << (void*)offset << endl);
+ bestOffset += dtohl(bestType->entriesStart);
- if (offset > (dtohl(type->header.size)-sizeof(ResTable_entry))) {
+ if (bestOffset > (dtohl(bestType->header.size)-sizeof(ResTable_entry))) {
ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x",
- offset, dtohl(type->header.size));
+ bestOffset, dtohl(bestType->header.size));
return BAD_TYPE;
}
- if ((offset&0x3) != 0) {
- ALOGW("ResTable_entry at 0x%x is not on an integer boundary",
- offset);
+ if ((bestOffset & 0x3) != 0) {
+ ALOGW("ResTable_entry at 0x%x is not on an integer boundary", bestOffset);
return BAD_TYPE;
}
- const ResTable_entry* const entry = (const ResTable_entry*)
- (((const uint8_t*)type) + offset);
+ const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
+ reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
if (dtohs(entry->size) < sizeof(*entry)) {
ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
return BAD_TYPE;
}
- *outType = type;
- *outEntry = entry;
- if (outTypeClass != NULL) {
- *outTypeClass = allTypes;
+ if (outEntry != NULL) {
+ outEntry->entry = entry;
+ outEntry->config = bestConfig;
+ outEntry->type = bestType;
+ outEntry->specFlags = specFlags;
+ outEntry->package = bestPackage;
+ outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
+ outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
}
- return offset + dtohs(entry->size);
+ return NO_ERROR;
}
status_t ResTable::parsePackage(const ResTable_package* const pkg,
- const Header* const header, uint32_t idmap_id)
+ const Header* const header)
{
const uint8_t* base = (const uint8_t*)pkg;
- status_t err = validate_chunk(&pkg->header, sizeof(*pkg),
+ status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset),
header->dataEnd, "ResTable_package");
if (err != NO_ERROR) {
return (mError=err);
@@ -5364,84 +5549,88 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
return (mError=BAD_TYPE);
}
- Package* package = NULL;
- PackageGroup* group = NULL;
- uint32_t id = idmap_id != 0 ? idmap_id : dtohl(pkg->id);
- // If at this point id == 0, pkg is an overlay package without a
- // corresponding idmap. During regular usage, overlay packages are
- // always loaded alongside their idmaps, but during idmap creation
- // the package is temporarily loaded by itself.
- if (id < 256) {
-
- package = new Package(this, header, pkg);
- if (package == NULL) {
- return (mError=NO_MEMORY);
+ uint32_t id = dtohl(pkg->id);
+ KeyedVector<uint8_t, IdmapEntries> idmapEntries;
+
+ if (header->resourceIDMap != NULL) {
+ uint8_t targetPackageId = 0;
+ status_t err = parseIdmap(header->resourceIDMap, header->resourceIDMapSize, &targetPackageId, &idmapEntries);
+ if (err != NO_ERROR) {
+ ALOGW("Overlay is broken");
+ return (mError=err);
}
+ id = targetPackageId;
+ }
- size_t idx = mPackageMap[id];
- if (idx == 0) {
- idx = mPackageGroups.size()+1;
+ if (id >= 256) {
+ LOG_ALWAYS_FATAL("Package id out of range");
+ return NO_ERROR;
+ } else if (id == 0) {
+ // This is a library so assign an ID
+ id = mNextPackageId++;
+ }
- char16_t tmpName[sizeof(pkg->name)/sizeof(char16_t)];
- strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(char16_t));
- group = new PackageGroup(this, String16(tmpName), id);
- if (group == NULL) {
- delete package;
- return (mError=NO_MEMORY);
- }
+ PackageGroup* group = NULL;
+ Package* package = new Package(this, header, pkg);
+ if (package == NULL) {
+ return (mError=NO_MEMORY);
+ }
- err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
- header->dataEnd-(base+dtohl(pkg->typeStrings)));
- if (err != NO_ERROR) {
- delete group;
- delete package;
- return (mError=err);
- }
- err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
- header->dataEnd-(base+dtohl(pkg->keyStrings)));
- if (err != NO_ERROR) {
- delete group;
- delete package;
- return (mError=err);
- }
+ err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
+ header->dataEnd-(base+dtohl(pkg->typeStrings)));
+ if (err != NO_ERROR) {
+ delete group;
+ delete package;
+ return (mError=err);
+ }
- err = group->packages.add(package);
- if (err < NO_ERROR) {
- delete group;
- delete package;
- return (mError=err);
- }
- group->basePackage = package;
+ err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
+ header->dataEnd-(base+dtohl(pkg->keyStrings)));
+ if (err != NO_ERROR) {
+ delete group;
+ delete package;
+ return (mError=err);
+ }
- //printf("Adding new package id %d at index %d\n", id, idx);
- err = mPackageGroups.add(group);
- if (err < NO_ERROR) {
- delete group;
- return (mError=err);
- }
+ size_t idx = mPackageMap[id];
+ if (idx == 0) {
+ idx = mPackageGroups.size() + 1;
- mPackageMap[id] = (uint8_t)idx;
- } else {
- group = mPackageGroups.itemAt(idx-1);
- if (group == NULL) {
- delete package;
- return (mError=UNKNOWN_ERROR);
- }
- err = group->packages.add(package);
- if (err < NO_ERROR) {
- delete package;
- return (mError=err);
- }
+ char16_t tmpName[sizeof(pkg->name)/sizeof(char16_t)];
+ strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(char16_t));
+ group = new PackageGroup(this, String16(tmpName), id);
+ if (group == NULL) {
+ delete package;
+ return (mError=NO_MEMORY);
+ }
+
+ //printf("Adding new package id %d at index %d\n", id, idx);
+ err = mPackageGroups.add(group);
+ if (err < NO_ERROR) {
+ return (mError=err);
+ }
+
+ mPackageMap[id] = static_cast<uint8_t>(idx);
+
+ // Find all packages that reference this package
+ size_t N = mPackageGroups.size();
+ for (size_t i = 0; i < N; i++) {
+ mPackageGroups[i]->dynamicRefTable.addMapping(
+ group->name, static_cast<uint8_t>(group->id));
}
} else {
- LOG_ALWAYS_FATAL("Package id out of range");
- return NO_ERROR;
+ group = mPackageGroups.itemAt(idx - 1);
+ if (group == NULL) {
+ return (mError=UNKNOWN_ERROR);
+ }
}
+ err = group->packages.add(package);
+ if (err < NO_ERROR) {
+ return (mError=err);
+ }
// Iterate through all chunks.
- size_t curPackage = 0;
-
const ResChunk_header* chunk =
(const ResChunk_header*)(((const uint8_t*)pkg)
+ dtohs(pkg->header.headerSize));
@@ -5462,20 +5651,20 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
}
const size_t typeSpecSize = dtohl(typeSpec->header.size);
+ const size_t newEntryCount = dtohl(typeSpec->entryCount);
LOAD_TABLE_NOISY(printf("TypeSpec off %p: type=0x%x, headerSize=0x%x, size=%p\n",
(void*)(base-(const uint8_t*)chunk),
dtohs(typeSpec->header.type),
dtohs(typeSpec->header.headerSize),
- (void*)typeSize));
+ (void*)typeSpecSize));
// look for block overrun or int overflow when multiplying by 4
if ((dtohl(typeSpec->entryCount) > (INT32_MAX/sizeof(uint32_t))
- || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*dtohl(typeSpec->entryCount))
+ || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*newEntryCount)
> typeSpecSize)) {
ALOGW("ResTable_typeSpec entry index to %p extends beyond chunk end %p.",
- (void*)(dtohs(typeSpec->header.headerSize)
- +(sizeof(uint32_t)*dtohl(typeSpec->entryCount))),
- (void*)typeSpecSize);
+ (void*)(dtohs(typeSpec->header.headerSize) + (sizeof(uint32_t)*newEntryCount)),
+ (void*)typeSpecSize);
return (mError=BAD_TYPE);
}
@@ -5484,21 +5673,36 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
return (mError=BAD_TYPE);
}
- while (package->types.size() < typeSpec->id) {
- package->types.add(NULL);
- }
- Type* t = package->types[typeSpec->id-1];
- if (t == NULL) {
- t = new Type(header, package, dtohl(typeSpec->entryCount));
- package->types.editItemAt(typeSpec->id-1) = t;
- } else if (dtohl(typeSpec->entryCount) != t->entryCount) {
- ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
- (int)dtohl(typeSpec->entryCount), (int)t->entryCount);
- return (mError=BAD_TYPE);
+ if (newEntryCount > 0) {
+ uint8_t typeIndex = typeSpec->id - 1;
+ ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id);
+ if (idmapIndex >= 0) {
+ typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+ }
+
+ TypeList& typeList = group->types.editItemAt(typeIndex);
+ if (!typeList.isEmpty()) {
+ const Type* existingType = typeList[0];
+ if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
+ ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
+ (int) newEntryCount, (int) existingType->entryCount);
+ // We should normally abort here, but some legacy apps declare
+ // resources in the 'android' package (old bug in AAPT).
+ }
+ }
+
+ Type* t = new Type(header, package, newEntryCount);
+ t->typeSpec = typeSpec;
+ t->typeSpecFlags = (const uint32_t*)(
+ ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
+ if (idmapIndex >= 0) {
+ t->idmapEntries = idmapEntries[idmapIndex];
+ }
+ typeList.add(t);
+ group->largestTypeId = max(group->largestTypeId, typeSpec->id);
+ } else {
+ ALOGV("Skipping empty ResTable_typeSpec for type %d", typeSpec->id);
}
- t->typeSpecFlags = (const uint32_t*)(
- ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
- t->typeSpec = typeSpec;
} else if (ctype == RES_TABLE_TYPE_TYPE) {
const ResTable_type* type = (const ResTable_type*)(chunk);
@@ -5509,50 +5713,84 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
}
const uint32_t typeSize = dtohl(type->header.size);
+ const size_t newEntryCount = dtohl(type->entryCount);
LOAD_TABLE_NOISY(printf("Type off %p: type=0x%x, headerSize=0x%x, size=%p\n",
(void*)(base-(const uint8_t*)chunk),
dtohs(type->header.type),
dtohs(type->header.headerSize),
(void*)typeSize));
- if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*dtohl(type->entryCount))
- > typeSize) {
+ if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*newEntryCount)
+ > typeSize) {
ALOGW("ResTable_type entry index to %p extends beyond chunk end 0x%x.",
- (void*)(dtohs(type->header.headerSize)
- +(sizeof(uint32_t)*dtohl(type->entryCount))),
- typeSize);
+ (void*)(dtohs(type->header.headerSize) + (sizeof(uint32_t)*newEntryCount)),
+ typeSize);
return (mError=BAD_TYPE);
}
- if (dtohl(type->entryCount) != 0
+
+ if (newEntryCount != 0
&& dtohl(type->entriesStart) > (typeSize-sizeof(ResTable_entry))) {
ALOGW("ResTable_type entriesStart at 0x%x extends beyond chunk end 0x%x.",
dtohl(type->entriesStart), typeSize);
return (mError=BAD_TYPE);
}
+
if (type->id == 0) {
ALOGW("ResTable_type has an id of 0.");
return (mError=BAD_TYPE);
}
- while (package->types.size() < type->id) {
- package->types.add(NULL);
- }
- Type* t = package->types[type->id-1];
- if (t == NULL) {
- t = new Type(header, package, dtohl(type->entryCount));
- package->types.editItemAt(type->id-1) = t;
- } else if (dtohl(type->entryCount) != t->entryCount) {
- ALOGW("ResTable_type entry count inconsistent: given %d, previously %d",
- (int)dtohl(type->entryCount), (int)t->entryCount);
- return (mError=BAD_TYPE);
+ if (newEntryCount > 0) {
+ uint8_t typeIndex = type->id - 1;
+ ssize_t idmapIndex = idmapEntries.indexOfKey(type->id);
+ if (idmapIndex >= 0) {
+ typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
+ }
+
+ TypeList& typeList = group->types.editItemAt(typeIndex);
+ if (typeList.isEmpty()) {
+ ALOGE("No TypeSpec for type %d", type->id);
+ return (mError=BAD_TYPE);
+ }
+
+ Type* t = typeList.editItemAt(typeList.size() - 1);
+ if (newEntryCount != t->entryCount) {
+ ALOGE("ResTable_type entry count inconsistent: given %d, previously %d",
+ (int)newEntryCount, (int)t->entryCount);
+ return (mError=BAD_TYPE);
+ }
+
+ if (t->package != package) {
+ ALOGE("No TypeSpec for type %d", type->id);
+ return (mError=BAD_TYPE);
+ }
+
+ t->configs.add(type);
+
+ TABLE_GETENTRY(
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(type->config);
+ ALOGI("Adding config to type %d: %s\n",
+ type->id, thisConfig.toString().string()));
+ } else {
+ ALOGV("Skipping empty ResTable_type for type %d", type->id);
}
- TABLE_GETENTRY(
- ResTable_config thisConfig;
- thisConfig.copyFromDtoH(type->config);
- ALOGI("Adding config to type %d: %s\n",
- type->id, thisConfig.toString().string()));
- t->configs.add(type);
+ } else if (ctype == RES_TABLE_LIBRARY_TYPE) {
+ if (group->dynamicRefTable.entries().size() == 0) {
+ status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk);
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+
+ // Fill in the reference table with the entries we already know about.
+ size_t N = mPackageGroups.size();
+ for (size_t i = 0; i < N; i++) {
+ group->dynamicRefTable.addMapping(mPackageGroups[i]->name, mPackageGroups[i]->id);
+ }
+ } else {
+ ALOGW("Found multiple library tables, ignoring...");
+ }
} else {
status_t err = validate_chunk(chunk, sizeof(ResChunk_header),
endPos, "ResTable_package:unknown");
@@ -5564,13 +5802,112 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg,
(((const uint8_t*)chunk) + csize);
}
- if (group->typeCount == 0) {
- group->typeCount = package->types.size();
+ return NO_ERROR;
+}
+
+DynamicRefTable::DynamicRefTable(uint8_t packageId)
+ : mAssignedPackageId(packageId)
+{
+ memset(mLookupTable, 0, sizeof(mLookupTable));
+
+ // Reserved package ids
+ mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
+ mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
+}
+
+status_t DynamicRefTable::load(const ResTable_lib_header* const header)
+{
+ const uint32_t entryCount = dtohl(header->count);
+ const uint32_t sizeOfEntries = sizeof(ResTable_lib_entry) * entryCount;
+ const uint32_t expectedSize = dtohl(header->header.size) - dtohl(header->header.headerSize);
+ if (sizeOfEntries > expectedSize) {
+ ALOGE("ResTable_lib_header size %u is too small to fit %u entries (x %u).",
+ expectedSize, entryCount, (uint32_t)sizeof(ResTable_lib_entry));
+ return UNKNOWN_ERROR;
+ }
+
+ const ResTable_lib_entry* entry = (const ResTable_lib_entry*)(((uint8_t*) header) +
+ dtohl(header->header.headerSize));
+ for (uint32_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
+ uint32_t packageId = dtohl(entry->packageId);
+ char16_t tmpName[sizeof(entry->packageName) / sizeof(char16_t)];
+ strcpy16_dtoh(tmpName, entry->packageName, sizeof(entry->packageName) / sizeof(char16_t));
+ LIB_NOISY(ALOGV("Found lib entry %s with id %d\n", String8(tmpName).string(),
+ dtohl(entry->packageId)));
+ if (packageId >= 256) {
+ ALOGE("Bad package id 0x%08x", packageId);
+ return UNKNOWN_ERROR;
+ }
+ mEntries.replaceValueFor(String16(tmpName), (uint8_t) packageId);
+ entry = entry + 1;
+ }
+ return NO_ERROR;
+}
+
+status_t DynamicRefTable::addMapping(const String16& packageName, uint8_t packageId)
+{
+ ssize_t index = mEntries.indexOfKey(packageName);
+ if (index < 0) {
+ return UNKNOWN_ERROR;
+ }
+ mLookupTable[mEntries.valueAt(index)] = packageId;
+ return NO_ERROR;
+}
+
+status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
+ uint32_t res = *resId;
+ size_t packageId = Res_GETPACKAGE(res) + 1;
+
+ if (packageId == APP_PACKAGE_ID) {
+ // No lookup needs to be done, app package IDs are absolute.
+ return NO_ERROR;
+ }
+
+ if (packageId == 0) {
+ // The package ID is 0x00. That means that a shared library is accessing
+ // its own local resource, so we fix up the resource with the calling
+ // package ID.
+ *resId |= ((uint32_t) mAssignedPackageId) << 24;
+ return NO_ERROR;
+ }
+
+ // Do a proper lookup.
+ uint8_t translatedId = mLookupTable[packageId];
+ if (translatedId == 0) {
+ ALOGE("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",
+ (uint8_t)mAssignedPackageId, (uint8_t)packageId);
+ for (size_t i = 0; i < 256; i++) {
+ if (mLookupTable[i] != 0) {
+ ALOGE("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]);
+ }
+ }
+ return UNKNOWN_ERROR;
+ }
+
+ *resId = (res & 0x00ffffff) | (((uint32_t) translatedId) << 24);
+ return NO_ERROR;
+}
+
+status_t DynamicRefTable::lookupResourceValue(Res_value* value) const {
+ if (value->dataType != Res_value::TYPE_DYNAMIC_REFERENCE) {
+ return NO_ERROR;
+ }
+
+ status_t err = lookupResourceId(&value->data);
+ if (err != NO_ERROR) {
+ return err;
}
+ value->dataType = Res_value::TYPE_REFERENCE;
return NO_ERROR;
}
+struct IdmapTypeMap {
+ ssize_t overlayTypeId;
+ size_t entryOffset;
+ Vector<uint32_t> entryMap;
+};
+
status_t ResTable::createIdmap(const ResTable& overlay,
uint32_t targetCrc, uint32_t overlayCrc,
const char* targetPath, const char* overlayPath,
@@ -5581,41 +5918,46 @@ status_t ResTable::createIdmap(const ResTable& overlay,
ALOGW("idmap: target package has no package groups, cannot create idmap\n");
return UNKNOWN_ERROR;
}
+
if (mPackageGroups[0]->packages.size() == 0) {
ALOGW("idmap: target package has no packages in its first package group, "
"cannot create idmap\n");
return UNKNOWN_ERROR;
}
- Vector<Vector<uint32_t> > map;
+ KeyedVector<uint8_t, IdmapTypeMap> map;
+
// overlaid packages are assumed to contain only one package group
const PackageGroup* pg = mPackageGroups[0];
- const Package* pkg = pg->packages[0];
- size_t typeCount = pkg->types.size();
- // starting size is header + first item (number of types in map)
- *outSize = (IDMAP_HEADER_SIZE + 1) * sizeof(uint32_t);
+
+ // starting size is header
+ *outSize = ResTable::IDMAP_HEADER_SIZE_BYTES;
+
+ // target package id and number of types in map
+ *outSize += 2 * sizeof(uint16_t);
+
// overlay packages are assumed to contain only one package group
const String16 overlayPackage(overlay.mPackageGroups[0]->packages[0]->package->name);
- const uint32_t pkg_id = pkg->package->id << 24;
-
- for (size_t typeIndex = 0; typeIndex < typeCount; ++typeIndex) {
- ssize_t first = -1;
- ssize_t last = -1;
- const Type* typeConfigs = pkg->getType(typeIndex);
- ssize_t mapIndex = map.add();
- if (mapIndex < 0) {
- return NO_MEMORY;
+
+ for (size_t typeIndex = 0; typeIndex < pg->types.size(); ++typeIndex) {
+ const TypeList& typeList = pg->types[typeIndex];
+ if (typeList.isEmpty()) {
+ continue;
}
- Vector<uint32_t>& vector = map.editItemAt(mapIndex);
+
+ const Type* typeConfigs = typeList[0];
+
+ IdmapTypeMap typeMap;
+ typeMap.overlayTypeId = -1;
+ typeMap.entryOffset = 0;
+
for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) {
- uint32_t resID = pkg_id
- | (0x00ff0000 & ((typeIndex+1)<<16))
- | (0x0000ffff & (entryIndex));
+ uint32_t resID = Res_MAKEID(pg->id - 1, typeIndex, entryIndex);
resource_name resName;
if (!this->getResourceName(resID, false, &resName)) {
- ALOGW("idmap: resource 0x%08x has spec but lacks values, skipping\n", resID);
- // add dummy value, or trimming leading/trailing zeroes later will fail
- vector.push(0);
+ if (typeMap.entryMap.isEmpty()) {
+ typeMap.entryOffset++;
+ }
continue;
}
@@ -5627,49 +5969,55 @@ status_t ResTable::createIdmap(const ResTable& overlay,
overlayType.size(),
overlayPackage.string(),
overlayPackage.size());
- if (overlayResID != 0) {
- overlayResID = pkg_id | (0x00ffffff & overlayResID);
- last = Res_GETENTRY(resID);
- if (first == -1) {
- first = Res_GETENTRY(resID);
+ if (overlayResID == 0) {
+ if (typeMap.entryMap.isEmpty()) {
+ typeMap.entryOffset++;
}
+ continue;
}
- vector.push(overlayResID);
-#if 0
- if (overlayResID != 0) {
- ALOGD("%s/%s 0x%08x -> 0x%08x\n",
- String8(String16(resName.type)).string(),
- String8(String16(resName.name)).string(),
- resID, overlayResID);
+
+ if (typeMap.overlayTypeId == -1) {
+ typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1;
+ }
+
+ if (Res_GETTYPE(overlayResID) + 1 != static_cast<size_t>(typeMap.overlayTypeId)) {
+ ALOGE("idmap: can't mix type ids in entry map. Resource 0x%08x maps to 0x%08x"
+ " but entries should map to resources of type %02x",
+ resID, overlayResID, typeMap.overlayTypeId);
+ return BAD_TYPE;
+ }
+
+ if (typeMap.entryOffset + typeMap.entryMap.size() < entryIndex) {
+ // Resize to accomodate this entry and the 0's in between.
+ if (typeMap.entryMap.resize((entryIndex - typeMap.entryOffset) + 1) < 0) {
+ return NO_MEMORY;
+ }
+ typeMap.entryMap.editTop() = Res_GETENTRY(overlayResID);
+ } else {
+ typeMap.entryMap.add(Res_GETENTRY(overlayResID));
}
-#endif
}
- if (first != -1) {
- // shave off trailing entries which lack overlay values
- const size_t last_past_one = last + 1;
- if (last_past_one < vector.size()) {
- vector.removeItemsAt(last_past_one, vector.size() - last_past_one);
+ if (!typeMap.entryMap.isEmpty()) {
+ if (map.add(static_cast<uint8_t>(typeIndex), typeMap) < 0) {
+ return NO_MEMORY;
}
- // shave off leading entries which lack overlay values
- vector.removeItemsAt(0, first);
- // store offset to first overlaid resource ID of this type
- vector.insertAt((uint32_t)first, 0, 1);
- // reserve space for number and offset of entries, and the actual entries
- *outSize += (2 + vector.size()) * sizeof(uint32_t);
- } else {
- // no entries of current type defined in overlay package
- vector.clear();
- // reserve space for type offset
- *outSize += 1 * sizeof(uint32_t);
+ *outSize += (4 * sizeof(uint16_t)) + (typeMap.entryMap.size() * sizeof(uint32_t));
}
}
+ if (map.isEmpty()) {
+ ALOGW("idmap: no resources in overlay package present in base package");
+ return UNKNOWN_ERROR;
+ }
+
if ((*outData = malloc(*outSize)) == NULL) {
return NO_MEMORY;
}
+
uint32_t* data = (uint32_t*)*outData;
*data++ = htodl(IDMAP_MAGIC);
+ *data++ = htodl(IDMAP_CURRENT_VERSION);
*data++ = htodl(targetCrc);
*data++ = htodl(overlayCrc);
const char* paths[] = { targetPath, overlayPath };
@@ -5687,44 +6035,30 @@ status_t ResTable::createIdmap(const ResTable& overlay,
data += 256 / sizeof(uint32_t);
}
const size_t mapSize = map.size();
- *data++ = htodl(mapSize);
- size_t offset = mapSize;
- for (size_t i = 0; i < mapSize; ++i) {
- const Vector<uint32_t>& vector = map.itemAt(i);
- const size_t N = vector.size();
- if (N == 0) {
- *data++ = htodl(0);
- } else {
- offset++;
- *data++ = htodl(offset);
- offset += N;
- }
- }
- if (offset == mapSize) {
- ALOGW("idmap: no resources in overlay package present in base package\n");
- return UNKNOWN_ERROR;
- }
+ uint16_t* typeData = reinterpret_cast<uint16_t*>(data);
+ *typeData++ = htods(pg->id);
+ *typeData++ = htods(mapSize);
for (size_t i = 0; i < mapSize; ++i) {
- const Vector<uint32_t>& vector = map.itemAt(i);
- const size_t N = vector.size();
- if (N == 0) {
- continue;
- }
- if (N == 1) { // vector expected to hold (offset) + (N > 0 entries)
- ALOGW("idmap: type %d supposedly has entries, but no entries found\n", i);
- return UNKNOWN_ERROR;
- }
- *data++ = htodl(N - 1); // do not count the offset (which is vector's first element)
- for (size_t j = 0; j < N; ++j) {
- const uint32_t& overlayResID = vector.itemAt(j);
- *data++ = htodl(overlayResID);
+ uint8_t targetTypeId = map.keyAt(i);
+ const IdmapTypeMap& typeMap = map[i];
+ *typeData++ = htods(targetTypeId + 1);
+ *typeData++ = htods(typeMap.overlayTypeId);
+ *typeData++ = htods(typeMap.entryMap.size());
+ *typeData++ = htods(typeMap.entryOffset);
+
+ const size_t entryCount = typeMap.entryMap.size();
+ uint32_t* entries = reinterpret_cast<uint32_t*>(typeData);
+ for (size_t j = 0; j < entryCount; j++) {
+ entries[j] = htodl(typeMap.entryMap[j]);
}
+ typeData += entryCount * 2;
}
return NO_ERROR;
}
bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes,
+ uint32_t* pVersion,
uint32_t* pTargetCrc, uint32_t* pOverlayCrc,
String8* pTargetPath, String8* pOverlayPath)
{
@@ -5732,17 +6066,20 @@ bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes,
if (!assertIdmapHeader(map, sizeBytes)) {
return false;
}
+ if (pVersion) {
+ *pVersion = dtohl(map[1]);
+ }
if (pTargetCrc) {
- *pTargetCrc = map[1];
+ *pTargetCrc = dtohl(map[2]);
}
if (pOverlayCrc) {
- *pOverlayCrc = map[2];
+ *pOverlayCrc = dtohl(map[3]);
}
if (pTargetPath) {
- pTargetPath->setTo(reinterpret_cast<const char*>(map + 3));
+ pTargetPath->setTo(reinterpret_cast<const char*>(map + 4));
}
if (pOverlayPath) {
- pOverlayPath->setTo(reinterpret_cast<const char*>(map + 3 + 256 / sizeof(uint32_t)));
+ pOverlayPath->setTo(reinterpret_cast<const char*>(map + 4 + 256 / sizeof(uint32_t)));
}
return true;
}
@@ -5825,6 +6162,8 @@ void ResTable::print_value(const Package* pkg, const Res_value& value) const
printf("(null)\n");
} else if (value.dataType == Res_value::TYPE_REFERENCE) {
printf("(reference) 0x%08x\n", value.data);
+ } else if (value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE) {
+ printf("(dynamic reference) 0x%08x\n", value.data);
} else if (value.dataType == Res_value::TYPE_ATTRIBUTE) {
printf("(attribute) 0x%08x\n", value.data);
} else if (value.dataType == Res_value::TYPE_STRING) {
@@ -5873,11 +6212,6 @@ void ResTable::print(bool inclValues) const
if (mError != 0) {
printf("mError=0x%x (%s)\n", mError, strerror(mError));
}
-#if 0
- char localeStr[RESTABLE_MAX_LOCALE_LEN];
- mParams.getBcp47Locale(localeStr);
- printf("mParams=%s,\n" localeStr);
-#endif
size_t pgCount = mPackageGroups.size();
printf("Package Groups (%d)\n", (int)pgCount);
for (size_t pgIndex=0; pgIndex<pgCount; pgIndex++) {
@@ -5889,172 +6223,184 @@ void ResTable::print(bool inclValues) const
size_t pkgCount = pg->packages.size();
for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) {
const Package* pkg = pg->packages[pkgIndex];
- size_t typeCount = pkg->types.size();
- printf(" Package %d id=%d name=%s typeCount=%d\n", (int)pkgIndex,
- pkg->package->id, String8(String16(pkg->package->name)).string(),
- (int)typeCount);
- for (size_t typeIndex=0; typeIndex<typeCount; typeIndex++) {
- const Type* typeConfigs = pkg->getType(typeIndex);
- if (typeConfigs == NULL) {
- printf(" type %d NULL\n", (int)typeIndex);
+ printf(" Package %d id=%d name=%s\n", (int)pkgIndex,
+ pkg->package->id, String8(String16(pkg->package->name)).string());
+ }
+
+ for (size_t typeIndex=0; typeIndex < pg->types.size(); typeIndex++) {
+ const TypeList& typeList = pg->types[typeIndex];
+ if (typeList.isEmpty()) {
+ //printf(" type %d NULL\n", (int)typeIndex);
+ continue;
+ }
+ const Type* typeConfigs = typeList[0];
+ const size_t NTC = typeConfigs->configs.size();
+ printf(" type %d configCount=%d entryCount=%d\n",
+ (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount);
+ if (typeConfigs->typeSpecFlags != NULL) {
+ for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) {
+ uint32_t resID = (0xff000000 & ((pg->id)<<24))
+ | (0x00ff0000 & ((typeIndex+1)<<16))
+ | (0x0000ffff & (entryIndex));
+ // Since we are creating resID without actually
+ // iterating over them, we have no idea which is a
+ // dynamic reference. We must check.
+ pg->dynamicRefTable.lookupResourceId(&resID);
+
+ resource_name resName;
+ if (this->getResourceName(resID, true, &resName)) {
+ String8 type8;
+ String8 name8;
+ if (resName.type8 != NULL) {
+ type8 = String8(resName.type8, resName.typeLen);
+ } else {
+ type8 = String8(resName.type, resName.typeLen);
+ }
+ if (resName.name8 != NULL) {
+ name8 = String8(resName.name8, resName.nameLen);
+ } else {
+ name8 = String8(resName.name, resName.nameLen);
+ }
+ printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n",
+ resID,
+ CHAR16_TO_CSTR(resName.package, resName.packageLen),
+ type8.string(), name8.string(),
+ dtohl(typeConfigs->typeSpecFlags[entryIndex]));
+ } else {
+ printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID);
+ }
+ }
+ }
+ for (size_t configIndex=0; configIndex<NTC; configIndex++) {
+ const ResTable_type* type = typeConfigs->configs[configIndex];
+ if ((((uint64_t)type)&0x3) != 0) {
+ printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type);
continue;
}
- const size_t NTC = typeConfigs->configs.size();
- printf(" type %d configCount=%d entryCount=%d\n",
- (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount);
- if (typeConfigs->typeSpecFlags != NULL) {
- for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) {
- uint32_t resID = (0xff000000 & ((pkg->package->id)<<24))
- | (0x00ff0000 & ((typeIndex+1)<<16))
- | (0x0000ffff & (entryIndex));
- resource_name resName;
- if (this->getResourceName(resID, true, &resName)) {
- String8 type8;
- String8 name8;
- if (resName.type8 != NULL) {
- type8 = String8(resName.type8, resName.typeLen);
- } else {
- type8 = String8(resName.type, resName.typeLen);
- }
- if (resName.name8 != NULL) {
- name8 = String8(resName.name8, resName.nameLen);
- } else {
- name8 = String8(resName.name, resName.nameLen);
- }
- printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n",
- resID,
- CHAR16_TO_CSTR(resName.package, resName.packageLen),
- type8.string(), name8.string(),
- dtohl(typeConfigs->typeSpecFlags[entryIndex]));
+ String8 configStr = type->config.toString();
+ printf(" config %s:\n", configStr.size() > 0
+ ? configStr.string() : "(default)");
+ size_t entryCount = dtohl(type->entryCount);
+ uint32_t entriesStart = dtohl(type->entriesStart);
+ if ((entriesStart&0x3) != 0) {
+ printf(" NON-INTEGER ResTable_type entriesStart OFFSET: 0x%x\n", entriesStart);
+ continue;
+ }
+ uint32_t typeSize = dtohl(type->header.size);
+ if ((typeSize&0x3) != 0) {
+ printf(" NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize);
+ continue;
+ }
+ for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) {
+
+ const uint8_t* const end = ((const uint8_t*)type)
+ + dtohl(type->header.size);
+ const uint32_t* const eindex = (const uint32_t*)
+ (((const uint8_t*)type) + dtohs(type->header.headerSize));
+
+ uint32_t thisOffset = dtohl(eindex[entryIndex]);
+ if (thisOffset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ uint32_t resID = (0xff000000 & ((pg->id)<<24))
+ | (0x00ff0000 & ((typeIndex+1)<<16))
+ | (0x0000ffff & (entryIndex));
+ pg->dynamicRefTable.lookupResourceId(&resID);
+ resource_name resName;
+ if (this->getResourceName(resID, true, &resName)) {
+ String8 type8;
+ String8 name8;
+ if (resName.type8 != NULL) {
+ type8 = String8(resName.type8, resName.typeLen);
+ } else {
+ type8 = String8(resName.type, resName.typeLen);
+ }
+ if (resName.name8 != NULL) {
+ name8 = String8(resName.name8, resName.nameLen);
} else {
- printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID);
+ name8 = String8(resName.name, resName.nameLen);
}
+ printf(" resource 0x%08x %s:%s/%s: ", resID,
+ CHAR16_TO_CSTR(resName.package, resName.packageLen),
+ type8.string(), name8.string());
+ } else {
+ printf(" INVALID RESOURCE 0x%08x: ", resID);
}
- }
- for (size_t configIndex=0; configIndex<NTC; configIndex++) {
- const ResTable_type* type = typeConfigs->configs[configIndex];
- if ((((uint64_t)type)&0x3) != 0) {
- printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type);
+ if ((thisOffset&0x3) != 0) {
+ printf("NON-INTEGER OFFSET: 0x%x\n", thisOffset);
continue;
}
- String8 configStr = type->config.toString();
- printf(" config %s:\n", configStr.size() > 0
- ? configStr.string() : "(default)");
- size_t entryCount = dtohl(type->entryCount);
- uint32_t entriesStart = dtohl(type->entriesStart);
- if ((entriesStart&0x3) != 0) {
- printf(" NON-INTEGER ResTable_type entriesStart OFFSET: 0x%x\n", entriesStart);
+ if ((thisOffset+sizeof(ResTable_entry)) > typeSize) {
+ printf("OFFSET OUT OF BOUNDS: 0x%x+0x%x (size is 0x%x)\n",
+ entriesStart, thisOffset, typeSize);
continue;
}
- uint32_t typeSize = dtohl(type->header.size);
- if ((typeSize&0x3) != 0) {
- printf(" NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize);
+
+ const ResTable_entry* ent = (const ResTable_entry*)
+ (((const uint8_t*)type) + entriesStart + thisOffset);
+ if (((entriesStart + thisOffset)&0x3) != 0) {
+ printf("NON-INTEGER ResTable_entry OFFSET: 0x%x\n",
+ (entriesStart + thisOffset));
continue;
}
- for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) {
- const uint8_t* const end = ((const uint8_t*)type)
- + dtohl(type->header.size);
- const uint32_t* const eindex = (const uint32_t*)
- (((const uint8_t*)type) + dtohs(type->header.headerSize));
+ uintptr_t esize = dtohs(ent->size);
+ if ((esize&0x3) != 0) {
+ printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
+ continue;
+ }
+ if ((thisOffset+esize) > typeSize) {
+ printf("ResTable_entry OUT OF BOUNDS: 0x%x+0x%x+%p (size is 0x%x)\n",
+ entriesStart, thisOffset, (void *)esize, typeSize);
+ continue;
+ }
- uint32_t thisOffset = dtohl(eindex[entryIndex]);
- if (thisOffset == ResTable_type::NO_ENTRY) {
- continue;
- }
+ const Res_value* valuePtr = NULL;
+ const ResTable_map_entry* bagPtr = NULL;
+ Res_value value;
+ if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
+ printf("<bag>");
+ bagPtr = (const ResTable_map_entry*)ent;
+ } else {
+ valuePtr = (const Res_value*)
+ (((const uint8_t*)ent) + esize);
+ value.copyFrom_dtoh(*valuePtr);
+ printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
+ (int)value.dataType, (int)value.data,
+ (int)value.size, (int)value.res0);
+ }
- uint32_t resID = (0xff000000 & ((pkg->package->id)<<24))
- | (0x00ff0000 & ((typeIndex+1)<<16))
- | (0x0000ffff & (entryIndex));
- resource_name resName;
- if (this->getResourceName(resID, true, &resName)) {
- String8 type8;
- String8 name8;
- if (resName.type8 != NULL) {
- type8 = String8(resName.type8, resName.typeLen);
- } else {
- type8 = String8(resName.type, resName.typeLen);
- }
- if (resName.name8 != NULL) {
- name8 = String8(resName.name8, resName.nameLen);
- } else {
- name8 = String8(resName.name, resName.nameLen);
+ if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
+ printf(" (PUBLIC)");
+ }
+ printf("\n");
+
+ if (inclValues) {
+ if (valuePtr != NULL) {
+ printf(" ");
+ print_value(typeConfigs->package, value);
+ } else if (bagPtr != NULL) {
+ const int N = dtohl(bagPtr->count);
+ const uint8_t* baseMapPtr = (const uint8_t*)ent;
+ size_t mapOffset = esize;
+ const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
+ const uint32_t parent = dtohl(bagPtr->parent.ident);
+ uint32_t resolvedParent = parent;
+ status_t err = pg->dynamicRefTable.lookupResourceId(&resolvedParent);
+ if (err != NO_ERROR) {
+ resolvedParent = 0;
}
- printf(" resource 0x%08x %s:%s/%s: ", resID,
- CHAR16_TO_CSTR(resName.package, resName.packageLen),
- type8.string(), name8.string());
- } else {
- printf(" INVALID RESOURCE 0x%08x: ", resID);
- }
- if ((thisOffset&0x3) != 0) {
- printf("NON-INTEGER OFFSET: 0x%x\n", thisOffset);
- continue;
- }
- if ((thisOffset+sizeof(ResTable_entry)) > typeSize) {
- printf("OFFSET OUT OF BOUNDS: 0x%x+0x%x (size is 0x%x)\n",
- entriesStart, thisOffset, typeSize);
- continue;
- }
-
- const ResTable_entry* ent = (const ResTable_entry*)
- (((const uint8_t*)type) + entriesStart + thisOffset);
- if (((entriesStart + thisOffset)&0x3) != 0) {
- printf("NON-INTEGER ResTable_entry OFFSET: 0x%x\n",
- (entriesStart + thisOffset));
- continue;
- }
-
- uintptr_t esize = dtohs(ent->size);
- if ((esize&0x3) != 0) {
- printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
- continue;
- }
- if ((thisOffset+esize) > typeSize) {
- printf("ResTable_entry OUT OF BOUNDS: 0x%x+0x%x+%p (size is 0x%x)\n",
- entriesStart, thisOffset, (void *)esize, typeSize);
- continue;
- }
-
- const Res_value* valuePtr = NULL;
- const ResTable_map_entry* bagPtr = NULL;
- Res_value value;
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
- printf("<bag>");
- bagPtr = (const ResTable_map_entry*)ent;
- } else {
- valuePtr = (const Res_value*)
- (((const uint8_t*)ent) + esize);
- value.copyFrom_dtoh(*valuePtr);
- printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
- (int)value.dataType, (int)value.data,
- (int)value.size, (int)value.res0);
- }
-
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
- printf(" (PUBLIC)");
- }
- printf("\n");
-
- if (inclValues) {
- if (valuePtr != NULL) {
- printf(" ");
- print_value(pkg, value);
- } else if (bagPtr != NULL) {
- const int N = dtohl(bagPtr->count);
- const uint8_t* baseMapPtr = (const uint8_t*)ent;
- size_t mapOffset = esize;
- const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
- printf(" Parent=0x%08x, Count=%d\n",
- dtohl(bagPtr->parent.ident), N);
- for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) {
- printf(" #%i (Key=0x%08x): ",
- i, dtohl(mapPtr->name.ident));
- value.copyFrom_dtoh(mapPtr->value);
- print_value(pkg, value);
- const size_t size = dtohs(mapPtr->value.size);
- mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value);
- mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
- }
+ printf(" Parent=0x%08x(Resolved=0x%08x), Count=%d\n",
+ parent, resolvedParent, N);
+ for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) {
+ printf(" #%i (Key=0x%08x): ",
+ i, dtohl(mapPtr->name.ident));
+ value.copyFrom_dtoh(mapPtr->value);
+ print_value(typeConfigs->package, value);
+ const size_t size = dtohs(mapPtr->value.size);
+ mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value);
+ mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
}
}
}
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
new file mode 100644
index 0000000..06b4040
--- /dev/null
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 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 <androidfw/TypeWrappers.h>
+
+namespace android {
+
+TypeVariant::iterator& TypeVariant::iterator::operator++() {
+ mIndex++;
+ if (mIndex > dtohl(mTypeVariant->data->entryCount)) {
+ mIndex = dtohl(mTypeVariant->data->entryCount);
+ }
+ return *this;
+}
+
+const ResTable_entry* TypeVariant::iterator::operator*() const {
+ const ResTable_type* type = mTypeVariant->data;
+ const uint32_t entryCount = dtohl(type->entryCount);
+ if (mIndex >= entryCount) {
+ return NULL;
+ }
+
+ const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(type)
+ + dtohl(type->header.size);
+ const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
+ if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount) > containerEnd) {
+ ALOGE("Type's entry indices extend beyond its boundaries");
+ return NULL;
+ }
+
+ const uint32_t entryOffset = dtohl(entryIndices[mIndex]);
+ if (entryOffset == ResTable_type::NO_ENTRY) {
+ return NULL;
+ }
+
+ if ((entryOffset & 0x3) != 0) {
+ ALOGE("Index %u points to entry with unaligned offset 0x%08x", mIndex, entryOffset);
+ return NULL;
+ }
+
+ const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
+ reinterpret_cast<uintptr_t>(type) + dtohl(type->entriesStart) + entryOffset);
+ if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) {
+ ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex);
+ return NULL;
+ } else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) {
+ ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex);
+ return NULL;
+ } else if (dtohs(entry->size) < sizeof(*entry)) {
+ ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size));
+ return NULL;
+ }
+ return entry;
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
index 977ba80..4ff6eec 100644
--- a/libs/androidfw/tests/Android.mk
+++ b/libs/androidfw/tests/Android.mk
@@ -1,32 +1,66 @@
-# Build the unit tests.
+#
+# Copyright (C) 2014 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.
+#
+
+# ==========================================================
+# Setup some common variables for the different build
+# targets here.
+# ==========================================================
LOCAL_PATH:= $(call my-dir)
+testFiles := \
+ ByteBucketArray_test.cpp \
+ Idmap_test.cpp \
+ ResourceTypes_test.cpp \
+ ResTable_test.cpp \
+ Split_test.cpp \
+ TypeWrappers_test.cpp \
+ ZipUtils_test.cpp
+
+# ==========================================================
+# Build the host tests: libandroidfw_tests
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libandroidfw_tests
+
+LOCAL_SRC_FILES := $(testFiles)
+LOCAL_STATIC_LIBRARIES := \
+ libandroidfw \
+ libutils \
+ libcutils \
+ liblog
+
+include $(BUILD_HOST_NATIVE_TEST)
+
+
+# ==========================================================
+# Build the device tests: libandroidfw_tests
+# ==========================================================
include $(CLEAR_VARS)
-# Build the unit tests.
-test_src_files := \
- ObbFile_test.cpp \
- ZipUtils_test.cpp \
- ResourceTypes_test.cpp
+LOCAL_MODULE := libandroidfw_tests
+
+LOCAL_SRC_FILES := $(testFiles) \
+ BackupData_test.cpp \
+ ObbFile_test.cpp
-shared_libraries := \
+LOCAL_SHARED_LIBRARIES := \
libandroidfw \
libcutils \
libutils \
libui \
libstlport
-static_libraries := \
- libgtest \
- libgtest_main
-
-$(foreach file,$(test_src_files), \
- $(eval include $(CLEAR_VARS)) \
- $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
- $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
- $(eval LOCAL_SRC_FILES := $(file)) \
- $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
- $(eval include $(BUILD_NATIVE_TEST)) \
-)
-
-# Build the manual test programs.
-include $(call all-makefiles-under, $(LOCAL_PATH))
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/androidfw/tests/BackupData_test.cpp b/libs/androidfw/tests/BackupData_test.cpp
new file mode 100644
index 0000000..17f91ca
--- /dev/null
+++ b/libs/androidfw/tests/BackupData_test.cpp
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#define LOG_TAG "ObbFile_test"
+#include <androidfw/BackupHelpers.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+namespace android {
+
+#define TEST_FILENAME "/test.bd"
+
+// keys of different lengths to test padding
+#define KEY1 "key1"
+#define KEY2 "key2a"
+#define KEY3 "key3bc"
+#define KEY4 "key4def"
+
+// payloads of different lengths to test padding
+#define DATA1 "abcdefg"
+#define DATA2 "hijklmnopq"
+#define DATA3 "rstuvwxyz"
+// KEY4 is only ever deleted
+
+class BackupDataTest : public testing::Test {
+protected:
+ char* m_external_storage;
+ char* m_filename;
+ String8 mKey1;
+ String8 mKey2;
+ String8 mKey3;
+ String8 mKey4;
+
+ virtual void SetUp() {
+ m_external_storage = getenv("EXTERNAL_STORAGE");
+
+ const int totalLen = strlen(m_external_storage) + strlen(TEST_FILENAME) + 1;
+ m_filename = new char[totalLen];
+ snprintf(m_filename, totalLen, "%s%s", m_external_storage, TEST_FILENAME);
+
+ ::unlink(m_filename);
+ int fd = ::open(m_filename, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ FAIL() << "Couldn't create " << m_filename << " for writing";
+ }
+ mKey1 = String8(KEY1);
+ mKey2 = String8(KEY2);
+ mKey3 = String8(KEY3);
+ mKey4 = String8(KEY4);
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(BackupDataTest, WriteAndReadSingle) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+
+ EXPECT_EQ(NO_ERROR, writer->WriteEntityHeader(mKey1, sizeof(DATA1)))
+ << "WriteEntityHeader returned an error";
+ EXPECT_EQ(NO_ERROR, writer->WriteEntityData(DATA1, sizeof(DATA1)))
+ << "WriteEntityData returned an error";
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+ EXPECT_EQ(NO_ERROR, reader->Status())
+ << "Reader ctor failed";
+
+ bool done;
+ int type;
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader";
+
+ String8 key;
+ size_t dataSize;
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error";
+ EXPECT_EQ(mKey1, key)
+ << "wrong key from ReadEntityHeader";
+ EXPECT_EQ(sizeof(DATA1), dataSize)
+ << "wrong size from ReadEntityHeader";
+
+ char* dataBytes = new char[dataSize];
+ EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error";
+ for (unsigned int i = 0; i < sizeof(DATA1); i++) {
+ EXPECT_EQ(DATA1[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, WriteAndReadMultiple) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, sizeof(DATA2));
+ writer->WriteEntityData(DATA2, sizeof(DATA2));
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // read and verify second entity
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(sizeof(DATA2), dataSize)
+ << "wrong size from ReadEntityHeader on second entity";
+
+ dataBytes = new char[dataSize];
+ EXPECT_EQ((int)dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error on second entity";
+ for (unsigned int i = 0; i < sizeof(DATA2); i++) {
+ EXPECT_EQ(DATA2[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, SkipEntity) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, sizeof(DATA2));
+ writer->WriteEntityData(DATA2, sizeof(DATA2));
+ writer->WriteEntityHeader(mKey3, sizeof(DATA3));
+ writer->WriteEntityData(DATA3, sizeof(DATA3));
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // skip second entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ reader->SkipEntityData();
+
+ // read and verify third entity
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader after skip";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on third entity";
+ EXPECT_EQ(mKey3, key)
+ << "wrong key from ReadEntityHeader on third entity";
+ EXPECT_EQ(sizeof(DATA3), dataSize)
+ << "wrong size from ReadEntityHeader on third entity";
+
+ dataBytes = new char[dataSize];
+ EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error on third entity";
+ for (unsigned int i = 0; i < sizeof(DATA3); i++) {
+ EXPECT_EQ(DATA3[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, DeleteEntity) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, -1);
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // read and verify deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader on deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on second entity";
+
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, EneityAfterDelete) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, -1);
+ writer->WriteEntityHeader(mKey3, sizeof(DATA3));
+ writer->WriteEntityData(DATA3, sizeof(DATA3));
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // read and verify deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader on deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int)dataSize)
+ << "not recognizing deletion on second entity";
+
+ // read and verify third entity
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader after deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on third entity";
+ EXPECT_EQ(mKey3, key)
+ << "wrong key from ReadEntityHeader on third entity";
+ EXPECT_EQ(sizeof(DATA3), dataSize)
+ << "wrong size from ReadEntityHeader on third entity";
+
+ dataBytes = new char[dataSize];
+ EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error on third entity";
+ for (unsigned int i = 0; i < sizeof(DATA3); i++) {
+ EXPECT_EQ(DATA3[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, OnlyDeleteEntities) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, -1);
+ writer->WriteEntityHeader(mKey2, -1);
+ writer->WriteEntityHeader(mKey3, -1);
+ writer->WriteEntityHeader(mKey4, -1);
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ // read and verify first deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader first deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on first entity";
+ EXPECT_EQ(mKey1, key)
+ << "wrong key from ReadEntityHeader on first entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on first entity";
+
+ // read and verify second deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader second deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on second entity";
+
+ // read and verify third deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader third deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on third entity";
+ EXPECT_EQ(mKey3, key)
+ << "wrong key from ReadEntityHeader on third entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on third entity";
+
+ // read and verify fourth deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader fourth deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on fourth entity";
+ EXPECT_EQ(mKey4, key)
+ << "wrong key from ReadEntityHeader on fourth entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on fourth entity";
+
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, ReadDeletedEntityData) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, -1);
+ writer->WriteEntityHeader(mKey2, -1);
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ // read and verify first deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader first deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on first entity";
+ EXPECT_EQ(mKey1, key)
+ << "wrong key from ReadEntityHeader on first entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on first entity";
+
+ // erroneously try to read first entity data
+ char* dataBytes = new char[10];
+ dataBytes[0] = 'A';
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityData(dataBytes, dataSize));
+ // expect dataBytes to be unmodofied
+ EXPECT_EQ('A', dataBytes[0]);
+
+ // read and verify second deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader second deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on second entity";
+
+ delete writer;
+ delete reader;
+}
+
+}
diff --git a/libs/androidfw/tests/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp
new file mode 100644
index 0000000..376e79c
--- /dev/null
+++ b/libs/androidfw/tests/ByteBucketArray_test.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 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 <androidfw/ByteBucketArray.h>
+
+#include <gtest/gtest.h>
+
+using android::ByteBucketArray;
+
+TEST(ByteBucketArrayTest, TestSparseInsertion) {
+ ByteBucketArray<int> bba;
+ ASSERT_TRUE(bba.set(0, 1));
+ ASSERT_TRUE(bba.set(10, 2));
+ ASSERT_TRUE(bba.set(26, 3));
+ ASSERT_TRUE(bba.set(129, 4));
+ ASSERT_TRUE(bba.set(234, 5));
+
+ for (size_t i = 0; i < bba.size(); i++) {
+ switch (i) {
+ case 0: EXPECT_EQ(1, bba[i]); break;
+ case 10: EXPECT_EQ(2, bba[i]); break;
+ case 26: EXPECT_EQ(3, bba[i]); break;
+ case 129: EXPECT_EQ(4, bba[i]); break;
+ case 234: EXPECT_EQ(5, bba[i]); break;
+ default: EXPECT_EQ(0, bba[i]); break;
+ }
+ }
+}
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
new file mode 100644
index 0000000..f50c178
--- /dev/null
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 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 <androidfw/ResourceTypes.h>
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+/**
+ * Include a binary resource table.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/basic/basic_arsc.h"
+
+/**
+ * Include a binary resource table.
+ * This table is an overlay.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/overlay/overlay_arsc.h"
+
+enum { MAY_NOT_BE_BAG = false };
+
+class IdmapTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(basic_arsc, basic_arsc_len));
+ ASSERT_EQ(NO_ERROR, mOverlayTable.add(overlay_arsc, overlay_arsc_len));
+ char targetName[256] = "com.android.test.basic";
+ ASSERT_EQ(NO_ERROR, mTargetTable.createIdmap(mOverlayTable, 0, 0,
+ targetName, targetName, &mData, &mDataSize));
+ }
+
+ virtual void TearDown() {
+ free(mData);
+ }
+
+ ResTable mTargetTable;
+ ResTable mOverlayTable;
+ void* mData;
+ size_t mDataSize;
+};
+
+TEST_F(IdmapTest, canLoadIdmap) {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize));
+}
+
+TEST_F(IdmapTest, overlayOverridesResourceValue) {
+ Res_value val;
+ ssize_t block = mTargetTable.getResource(base::R::string::test2, &val, false);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
+ const ResStringPool* pool = mTargetTable.getTableStringBlock(block);
+ ASSERT_TRUE(pool != NULL);
+ ASSERT_LT(val.data, pool->size());
+
+ size_t strLen;
+ const char16_t* targetStr16 = pool->stringAt(val.data, &strLen);
+ ASSERT_TRUE(targetStr16 != NULL);
+ ASSERT_EQ(String16("test2"), String16(targetStr16, strLen));
+
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize));
+
+ ssize_t newBlock = mTargetTable.getResource(base::R::string::test2, &val, false);
+ ASSERT_GE(newBlock, 0);
+ ASSERT_NE(block, newBlock);
+ ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
+ pool = mTargetTable.getTableStringBlock(newBlock);
+ ASSERT_TRUE(pool != NULL);
+ ASSERT_LT(val.data, pool->size());
+
+ targetStr16 = pool->stringAt(val.data, &strLen);
+ ASSERT_TRUE(targetStr16 != NULL);
+ ASSERT_EQ(String16("test2-overlay"), String16(targetStr16, strLen));
+}
+
+TEST_F(IdmapTest, overlaidResourceHasSameName) {
+ ASSERT_EQ(NO_ERROR, mTargetTable.add(overlay_arsc, overlay_arsc_len, mData, mDataSize));
+
+ ResTable::resource_name resName;
+ ASSERT_TRUE(mTargetTable.getResourceName(base::R::array::integerArray1, false, &resName));
+
+ ASSERT_TRUE(resName.package != NULL);
+ ASSERT_TRUE(resName.type != NULL);
+ ASSERT_TRUE(resName.name != NULL);
+
+ EXPECT_EQ(String16("com.android.test.basic"), String16(resName.package, resName.packageLen));
+ EXPECT_EQ(String16("array"), String16(resName.type, resName.typeLen));
+ EXPECT_EQ(String16("integerArray1"), String16(resName.name, resName.nameLen));
+}
+
+} // namespace
diff --git a/libs/androidfw/tests/ObbFile_test.cpp b/libs/androidfw/tests/ObbFile_test.cpp
index 2c9f650..7a4dd13 100644
--- a/libs/androidfw/tests/ObbFile_test.cpp
+++ b/libs/androidfw/tests/ObbFile_test.cpp
@@ -91,7 +91,7 @@ TEST_F(ObbFileTest, WriteThenRead) {
EXPECT_EQ(sizeof(salt), saltLen)
<< "salt sizes were not the same";
- for (int i = 0; i < sizeof(salt); i++) {
+ for (size_t i = 0; i < sizeof(salt); i++) {
EXPECT_EQ(salt[i], newSalt[i])
<< "salt character " << i << " should be equal";
}
diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp
new file mode 100644
index 0000000..89d271d0
--- /dev/null
+++ b/libs/androidfw/tests/ResTable_test.cpp
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2014 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 <androidfw/ResourceTypes.h>
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+#include "data/lib/R.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+/**
+ * Include a binary resource table.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/basic/basic_arsc.h"
+
+#include "data/lib/lib_arsc.h"
+
+enum { MAY_NOT_BE_BAG = false };
+
+TEST(ResTableTest, shouldLoadSuccessfully) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+}
+
+TEST(ResTableTest, simpleTypeIsRetrievedCorrectly) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ Res_value val;
+ ssize_t block = table.getResource(base::R::string::test1, &val, MAY_NOT_BE_BAG);
+
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
+
+ const ResStringPool* pool = table.getTableStringBlock(block);
+ ASSERT_TRUE(NULL != pool);
+ ASSERT_EQ(String8("test1"), pool->string8ObjectAt(val.data));
+}
+
+TEST(ResTableTest, resourceNameIsResolved) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ String16 defPackage("com.android.test.basic");
+ String16 testName("@string/test1");
+ uint32_t resID = table.identifierForName(testName.string(), testName.size(),
+ 0, 0,
+ defPackage.string(), defPackage.size());
+ ASSERT_NE(uint32_t(0x00000000), resID);
+ ASSERT_EQ(base::R::string::test1, resID);
+}
+
+TEST(ResTableTest, noParentThemeIsAppliedCorrectly) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ ResTable::Theme theme(table);
+ ASSERT_EQ(NO_ERROR, theme.applyStyle(base::R::style::Theme1));
+
+ Res_value val;
+ uint32_t specFlags = 0;
+ ssize_t index = theme.getAttribute(base::R::attr::attr1, &val, &specFlags);
+ ASSERT_GE(index, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ ASSERT_EQ(uint32_t(100), val.data);
+
+ index = theme.getAttribute(base::R::attr::attr2, &val, &specFlags);
+ ASSERT_GE(index, 0);
+ ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+ ASSERT_EQ(base::R::integer::number1, val.data);
+}
+
+TEST(ResTableTest, parentThemeIsAppliedCorrectly) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ ResTable::Theme theme(table);
+ ASSERT_EQ(NO_ERROR, theme.applyStyle(base::R::style::Theme2));
+
+ Res_value val;
+ uint32_t specFlags = 0;
+ ssize_t index = theme.getAttribute(base::R::attr::attr1, &val, &specFlags);
+ ASSERT_GE(index, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ ASSERT_EQ(uint32_t(300), val.data);
+
+ index = theme.getAttribute(base::R::attr::attr2, &val, &specFlags);
+ ASSERT_GE(index, 0);
+ ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+ ASSERT_EQ(base::R::integer::number1, val.data);
+}
+
+TEST(ResTableTest, libraryThemeIsAppliedCorrectly) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(lib_arsc, lib_arsc_len));
+
+ ResTable::Theme theme(table);
+ ASSERT_EQ(NO_ERROR, theme.applyStyle(lib::R::style::Theme));
+
+ Res_value val;
+ uint32_t specFlags = 0;
+ ssize_t index = theme.getAttribute(lib::R::attr::attr1, &val, &specFlags);
+ ASSERT_GE(index, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ ASSERT_EQ(uint32_t(700), val.data);
+}
+
+TEST(ResTableTest, referenceToBagIsNotResolved) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ Res_value val;
+ ssize_t block = table.getResource(base::R::integer::number2, &val, MAY_NOT_BE_BAG);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+ ASSERT_EQ(base::R::array::integerArray1, val.data);
+
+ ssize_t newBlock = table.resolveReference(&val, block);
+ EXPECT_EQ(block, newBlock);
+ EXPECT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
+ EXPECT_EQ(base::R::array::integerArray1, val.data);
+}
+
+TEST(ResTableTest, resourcesStillAccessibleAfterParameterChange) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ Res_value val;
+ ssize_t block = table.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+
+ const ResTable::bag_entry* entry;
+ ssize_t count = table.lockBag(base::R::array::integerArray1, &entry);
+ ASSERT_GE(count, 0);
+ table.unlockBag(entry);
+
+ ResTable_config param;
+ memset(&param, 0, sizeof(param));
+ param.density = 320;
+ table.setParameters(&param);
+
+ block = table.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+
+ count = table.lockBag(base::R::array::integerArray1, &entry);
+ ASSERT_GE(count, 0);
+ table.unlockBag(entry);
+}
+
+TEST(ResTableTest, resourceIsOverridenWithBetterConfig) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ Res_value val;
+ ssize_t block = table.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ ASSERT_EQ(uint32_t(200), val.data);
+
+ ResTable_config param;
+ memset(&param, 0, sizeof(param));
+ param.language[0] = 's';
+ param.language[1] = 'v';
+ param.country[0] = 'S';
+ param.country[1] = 'E';
+ table.setParameters(&param);
+
+ block = table.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG);
+ ASSERT_GE(block, 0);
+ ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
+ ASSERT_EQ(uint32_t(400), val.data);
+}
+
+TEST(ResTableTest, emptyTableHasSensibleDefaults) {
+ const int32_t assetCookie = 1;
+
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.addEmpty(assetCookie));
+
+ // Adding an empty table gives us one table!
+ ASSERT_EQ(uint32_t(1), table.getTableCount());
+
+ // Adding an empty table doesn't mean we get packages.
+ ASSERT_EQ(uint32_t(0), table.getBasePackageCount());
+
+ Res_value val;
+ ASSERT_LT(table.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG), 0);
+}
+
+}
diff --git a/libs/androidfw/tests/ResourceTypes_test.cpp b/libs/androidfw/tests/ResourceTypes_test.cpp
index 139f868..f00a2d9 100644
--- a/libs/androidfw/tests/ResourceTypes_test.cpp
+++ b/libs/androidfw/tests/ResourceTypes_test.cpp
@@ -64,8 +64,8 @@ TEST(ResourceTypesTest, ResourceConfig_packAndUnpack3LetterLanguage) {
config.packLanguage("eng");
// 1-00110-01 101-00100
- EXPECT_EQ(0x99, config.language[0]);
- EXPECT_EQ(0xa4, config.language[1]);
+ EXPECT_EQ('\x99', config.language[0]);
+ EXPECT_EQ('\xA4', config.language[1]);
char out[4] = { 1, 1, 1, 1};
config.unpackLanguage(out);
diff --git a/libs/androidfw/tests/Split_test.cpp b/libs/androidfw/tests/Split_test.cpp
new file mode 100644
index 0000000..82703f9
--- /dev/null
+++ b/libs/androidfw/tests/Split_test.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2014 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 <androidfw/ResourceTypes.h>
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include "TestHelpers.h"
+#include "data/basic/R.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+namespace {
+
+/**
+ * Include a binary resource table. This table
+ * is a base table for an APK split.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/basic/basic_arsc.h"
+
+/**
+ * Include a binary resource table. This table
+ * is a configuration split table for an APK split.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/basic/split_de_fr_arsc.h"
+
+/**
+ * Include a binary resource table. This table
+ * is a feature split table for an APK split.
+ *
+ * Package: com.android.test.basic
+ */
+#include "data/feature/feature_arsc.h"
+
+enum { MAY_NOT_BE_BAG = false };
+
+void makeConfigFrench(ResTable_config* config) {
+ memset(config, 0, sizeof(*config));
+ config->language[0] = 'f';
+ config->language[1] = 'r';
+}
+
+TEST(SplitTest, TestLoadBase) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+}
+
+TEST(SplitTest, TestGetResourceFromBase) {
+ ResTable_config frenchConfig;
+ makeConfigFrench(&frenchConfig);
+
+ ResTable table;
+ table.setParameters(&frenchConfig);
+
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ ResTable_config expectedConfig;
+ memset(&expectedConfig, 0, sizeof(expectedConfig));
+
+ Res_value val;
+ ResTable_config config;
+ ssize_t block = table.getResource(base::R::string::test1, &val, MAY_NOT_BE_BAG, 0, NULL, &config);
+
+ // The returned block should tell us which string pool to get the value, if it is a string.
+ EXPECT_GE(block, 0);
+
+ // We expect the default resource to be selected since it is the only resource configuration.
+ EXPECT_EQ(0, expectedConfig.compare(config));
+
+ EXPECT_EQ(Res_value::TYPE_STRING, val.dataType);
+}
+
+TEST(SplitTest, TestGetResourceFromSplit) {
+ ResTable_config expectedConfig;
+ makeConfigFrench(&expectedConfig);
+
+ ResTable table;
+ table.setParameters(&expectedConfig);
+
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+ ASSERT_EQ(NO_ERROR, table.add(split_de_fr_arsc, split_de_fr_arsc_len));
+
+ Res_value val;
+ ResTable_config config;
+ ssize_t block = table.getResource(base::R::string::test1, &val, MAY_NOT_BE_BAG, 0, NULL, &config);
+
+ EXPECT_GE(block, 0);
+
+ EXPECT_EQ(0, expectedConfig.compare(config));
+
+ EXPECT_EQ(Res_value::TYPE_STRING, val.dataType);
+}
+
+TEST(SplitTest, ResourcesFromBaseAndSplitHaveSameNames) {
+ ResTable_config expectedConfig;
+ makeConfigFrench(&expectedConfig);
+
+ ResTable table;
+ table.setParameters(&expectedConfig);
+
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ ResTable::resource_name baseName;
+ EXPECT_TRUE(table.getResourceName(base::R::string::test1, false, &baseName));
+
+ ASSERT_EQ(NO_ERROR, table.add(split_de_fr_arsc, split_de_fr_arsc_len));
+
+ ResTable::resource_name frName;
+ EXPECT_TRUE(table.getResourceName(base::R::string::test1, false, &frName));
+
+ EXPECT_EQ(
+ String16(baseName.package, baseName.packageLen),
+ String16(frName.package, frName.packageLen));
+
+ EXPECT_EQ(
+ String16(baseName.type, baseName.typeLen),
+ String16(frName.type, frName.typeLen));
+
+ EXPECT_EQ(
+ String16(baseName.name, baseName.nameLen),
+ String16(frName.name, frName.nameLen));
+}
+
+TEST(SplitTest, TypeEntrySpecFlagsAreUpdated) {
+ ResTable_config defaultConfig;
+ memset(&defaultConfig, 0, sizeof(defaultConfig));
+
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ Res_value val;
+ uint32_t specFlags = 0;
+ ssize_t block = table.getResource(base::R::string::test1, &val, MAY_NOT_BE_BAG, 0, &specFlags, NULL);
+ EXPECT_GE(block, 0);
+
+ EXPECT_EQ(static_cast<uint32_t>(0), specFlags);
+
+ ASSERT_EQ(NO_ERROR, table.add(split_de_fr_arsc, split_de_fr_arsc_len));
+
+ uint32_t frSpecFlags = 0;
+ block = table.getResource(base::R::string::test1, &val, MAY_NOT_BE_BAG, 0, &frSpecFlags, NULL);
+ EXPECT_GE(block, 0);
+
+ EXPECT_EQ(ResTable_config::CONFIG_LOCALE, frSpecFlags);
+}
+
+TEST(SplitFeatureTest, TestNewResourceIsAccessible) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ Res_value val;
+ ssize_t block = table.getResource(base::R::string::test3, &val, MAY_NOT_BE_BAG);
+ EXPECT_LT(block, 0);
+
+ ASSERT_EQ(NO_ERROR, table.add(feature_arsc, feature_arsc_len));
+
+ block = table.getResource(base::R::string::test3, &val, MAY_NOT_BE_BAG);
+ EXPECT_GE(block, 0);
+
+ EXPECT_EQ(Res_value::TYPE_STRING, val.dataType);
+}
+
+TEST(SplitFeatureTest, TestNewResourceIsAccessibleByName) {
+ ResTable table;
+ ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len));
+
+ ResTable::resource_name name;
+ EXPECT_FALSE(table.getResourceName(base::R::string::test3, false, &name));
+
+ ASSERT_EQ(NO_ERROR, table.add(feature_arsc, feature_arsc_len));
+
+ EXPECT_TRUE(table.getResourceName(base::R::string::test3, false, &name));
+
+ EXPECT_EQ(String16("com.android.test.basic"),
+ String16(name.package, name.packageLen));
+
+ EXPECT_EQ(String16("string"),
+ String16(name.type, name.typeLen));
+
+ EXPECT_EQ(String16("test3"),
+ String16(name.name, name.nameLen));
+}
+
+} // namespace
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
new file mode 100644
index 0000000..75a233a
--- /dev/null
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -0,0 +1,17 @@
+#ifndef __TEST_HELPERS_H
+#define __TEST_HELPERS_H
+
+#include <ostream>
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+
+static inline ::std::ostream& operator<<(::std::ostream& out, const android::String8& str) {
+ return out << str.string();
+}
+
+static inline ::std::ostream& operator<<(::std::ostream& out, const android::String16& str) {
+ return out << android::String8(str).string();
+}
+
+#endif // __TEST_HELPERS_H
diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
new file mode 100644
index 0000000..d69abe5
--- /dev/null
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 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 <androidfw/ResourceTypes.h>
+#include <androidfw/TypeWrappers.h>
+#include <utils/String8.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+void* createTypeData() {
+ ResTable_type t;
+ memset(&t, 0, sizeof(t));
+ t.header.type = RES_TABLE_TYPE_TYPE;
+ t.header.headerSize = sizeof(t);
+ t.id = 1;
+ t.entryCount = 3;
+
+ uint32_t offsets[3];
+ t.entriesStart = t.header.headerSize + sizeof(offsets);
+ t.header.size = t.entriesStart;
+
+ offsets[0] = 0;
+ ResTable_entry e1;
+ memset(&e1, 0, sizeof(e1));
+ e1.size = sizeof(e1);
+ e1.key.index = 0;
+ t.header.size += sizeof(e1);
+
+ Res_value v1;
+ memset(&v1, 0, sizeof(v1));
+ t.header.size += sizeof(v1);
+
+ offsets[1] = ResTable_type::NO_ENTRY;
+
+ offsets[2] = sizeof(e1) + sizeof(v1);
+ ResTable_entry e2;
+ memset(&e2, 0, sizeof(e2));
+ e2.size = sizeof(e2);
+ e2.key.index = 1;
+ t.header.size += sizeof(e2);
+
+ Res_value v2;
+ memset(&v2, 0, sizeof(v2));
+ t.header.size += sizeof(v2);
+
+ uint8_t* data = (uint8_t*)malloc(t.header.size);
+ uint8_t* p = data;
+ memcpy(p, &t, sizeof(t));
+ p += sizeof(t);
+ memcpy(p, offsets, sizeof(offsets));
+ p += sizeof(offsets);
+ memcpy(p, &e1, sizeof(e1));
+ p += sizeof(e1);
+ memcpy(p, &v1, sizeof(v1));
+ p += sizeof(v1);
+ memcpy(p, &e2, sizeof(e2));
+ p += sizeof(e2);
+ memcpy(p, &v2, sizeof(v2));
+ p += sizeof(v2);
+ return data;
+}
+
+TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
+ ResTable_type* data = (ResTable_type*) createTypeData();
+
+ TypeVariant v(data);
+
+ TypeVariant::iterator iter = v.beginEntries();
+ ASSERT_EQ(uint32_t(0), iter.index());
+ ASSERT_TRUE(NULL != *iter);
+ ASSERT_EQ(uint32_t(0), iter->key.index);
+ ASSERT_NE(v.endEntries(), iter);
+
+ iter++;
+
+ ASSERT_EQ(uint32_t(1), iter.index());
+ ASSERT_TRUE(NULL == *iter);
+ ASSERT_NE(v.endEntries(), iter);
+
+ iter++;
+
+ ASSERT_EQ(uint32_t(2), iter.index());
+ ASSERT_TRUE(NULL != *iter);
+ ASSERT_EQ(uint32_t(1), iter->key.index);
+ ASSERT_NE(v.endEntries(), iter);
+
+ iter++;
+
+ ASSERT_EQ(v.endEntries(), iter);
+
+ free(data);
+}
+
+} // namespace android
diff --git a/libs/androidfw/tests/data/.gitignore b/libs/androidfw/tests/data/.gitignore
new file mode 100644
index 0000000..c05cfb0
--- /dev/null
+++ b/libs/androidfw/tests/data/.gitignore
@@ -0,0 +1,2 @@
+*.apk
+*.arsc
diff --git a/libs/androidfw/tests/data/basic/AndroidManifest.xml b/libs/androidfw/tests/data/basic/AndroidManifest.xml
new file mode 100644
index 0000000..a56ac18
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.basic">
+ <application>
+ </application>
+</manifest>
diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h
new file mode 100644
index 0000000..363dcb9
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/R.h
@@ -0,0 +1,55 @@
+#ifndef __BASE_R_H
+#define __BASE_R_H
+
+namespace base {
+namespace R {
+
+namespace attr {
+ enum {
+ attr1 = 0x7f010000, // default
+ attr2 = 0x7f010001, // default
+ };
+}
+
+namespace layout {
+ enum {
+ main = 0x7f020000, // default, fr-sw600dp-v13
+ };
+}
+
+namespace string {
+ enum {
+ test1 = 0x7f030000, // default
+ test2 = 0x7f030001, // default
+
+ test3 = 0x7f070000, // default (in feature)
+ test4 = 0x7f070001, // default (in feature)
+ };
+}
+
+namespace integer {
+ enum {
+ number1 = 0x7f040000, // default, sv
+ number2 = 0x7f040001, // default
+
+ test3 = 0x7f080000, // default (in feature)
+ };
+}
+
+namespace style {
+ enum {
+ Theme1 = 0x7f050000, // default
+ Theme2 = 0x7f050001, // default
+ };
+}
+
+namespace array {
+ enum {
+ integerArray1 = 0x7f060000, // default
+ };
+}
+
+} // namespace R
+} // namespace base
+
+#endif // __BASE_R_H
diff --git a/libs/androidfw/tests/data/basic/basic_arsc.h b/libs/androidfw/tests/data/basic/basic_arsc.h
new file mode 100644
index 0000000..61cb94c
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/basic_arsc.h
@@ -0,0 +1,161 @@
+unsigned char basic_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0x60, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00,
+ 0x72, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x13, 0x00, 0x72, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x2f, 0x00, 0x6d, 0x00, 0x61, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00,
+ 0x00, 0x00, 0x22, 0x00, 0x72, 0x00, 0x65, 0x00, 0x73, 0x00, 0x2f, 0x00,
+ 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00,
+ 0x2d, 0x00, 0x66, 0x00, 0x72, 0x00, 0x2d, 0x00, 0x73, 0x00, 0x77, 0x00,
+ 0x36, 0x00, 0x30, 0x00, 0x30, 0x00, 0x64, 0x00, 0x70, 0x00, 0x2d, 0x00,
+ 0x76, 0x00, 0x31, 0x00, 0x33, 0x00, 0x2f, 0x00, 0x6d, 0x00, 0x61, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x2e, 0x00, 0x78, 0x00, 0x6d, 0x00, 0x6c, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01,
+ 0x98, 0x06, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00,
+ 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00,
+ 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00,
+ 0x69, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+ 0x90, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x2c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00,
+ 0x74, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00,
+ 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00,
+ 0x65, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+ 0xec, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
+ 0x56, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00,
+ 0x88, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x74, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x32, 0x00, 0x00, 0x00, 0x04, 0x00, 0x6d, 0x00,
+ 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00,
+ 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00,
+ 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x32, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00,
+ 0x31, 0x00, 0x00, 0x00, 0x06, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00,
+ 0x6d, 0x00, 0x65, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x69, 0x00,
+ 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00,
+ 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x24, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+ 0x6c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00,
+ 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10,
+ 0xc8, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x7f, 0x01, 0x02, 0x44, 0x00,
+ 0x5c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x73, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10,
+ 0x90, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x90, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x64, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x7f,
+ 0x10, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x7f,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10,
+ 0x2c, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x03, 0x00, 0x00, 0x00
+};
+unsigned int basic_arsc_len = 1888;
diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build
new file mode 100755
index 0000000..fa4a9fe
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/build
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+aapt package -M AndroidManifest.xml -S res --split fr,de -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc basic.arsc && \
+xxd -i basic.arsc > basic_arsc.h && \
+unzip bundle_de_fr.apk resources.arsc && \
+mv resources.arsc split_de_fr.arsc && \
+xxd -i split_de_fr.arsc > split_de_fr_arsc.h
diff --git a/libs/androidfw/tests/data/basic/res/layout-fr-sw600dp/main.xml b/libs/androidfw/tests/data/basic/res/layout-fr-sw600dp/main.xml
new file mode 100644
index 0000000..05ffd58
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/layout-fr-sw600dp/main.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge>
+</merge>
diff --git a/libs/androidfw/tests/data/basic/res/layout/main.xml b/libs/androidfw/tests/data/basic/res/layout/main.xml
new file mode 100644
index 0000000..05ffd58
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/layout/main.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge>
+</merge>
diff --git a/libs/androidfw/tests/data/basic/res/values-de/values.xml b/libs/androidfw/tests/data/basic/res/values-de/values.xml
new file mode 100644
index 0000000..103c6a3
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/values-de/values.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="test1">versuch 1</string>
+ <string name="test2">versuch 2</string>
+</resources>
diff --git a/libs/androidfw/tests/data/basic/res/values-fr/values.xml b/libs/androidfw/tests/data/basic/res/values-fr/values.xml
new file mode 100644
index 0000000..1806a2d
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/values-fr/values.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="test1">essai 1</string>
+ <string name="test2">essai 2</string>
+</resources>
diff --git a/libs/androidfw/tests/data/basic/res/values-sv/values.xml b/libs/androidfw/tests/data/basic/res/values-sv/values.xml
new file mode 100644
index 0000000..9d52307
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/values-sv/values.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <integer name="number1">400</integer>
+</resources>
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
new file mode 100644
index 0000000..662eda6
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <attr name="attr1" format="reference|integer" />
+ <attr name="attr2" format="reference|integer" />
+
+ <string name="test1">test1</string>
+ <string name="test2">test2</string>
+
+ <integer name="number1">200</integer>
+ <integer name="number2">@array/integerArray1</integer>
+
+ <style name="Theme1">
+ <item name="com.android.test.basic:attr1">100</item>
+ <item name="com.android.test.basic:attr2">@integer/number1</item>
+ </style>
+
+ <style name="Theme2" parent="@com.android.test.basic:style/Theme1">
+ <item name="com.android.test.basic:attr1">300</item>
+ </style>
+
+ <integer-array name="integerArray1">
+ <item>1</item>
+ <item>2</item>
+ <item>3</item>
+ </integer-array>
+</resources>
diff --git a/libs/androidfw/tests/data/basic/split_de_fr_arsc.h b/libs/androidfw/tests/data/basic/split_de_fr_arsc.h
new file mode 100644
index 0000000..a8eaf0b
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/split_de_fr_arsc.h
@@ -0,0 +1,85 @@
+unsigned char split_de_fr_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0xd8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
+ 0x2c, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x09, 0x00, 0x76, 0x00,
+ 0x65, 0x00, 0x72, 0x00, 0x73, 0x00, 0x75, 0x00, 0x63, 0x00, 0x68, 0x00,
+ 0x20, 0x00, 0x31, 0x00, 0x00, 0x00, 0x09, 0x00, 0x76, 0x00, 0x65, 0x00,
+ 0x72, 0x00, 0x73, 0x00, 0x75, 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00,
+ 0x32, 0x00, 0x00, 0x00, 0x07, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00,
+ 0x61, 0x00, 0x69, 0x00, 0x20, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x61, 0x00, 0x69, 0x00, 0x20, 0x00,
+ 0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x50, 0x03, 0x00, 0x00,
+ 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x90, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+ 0x3e, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x6c, 0x00,
+ 0x61, 0x00, 0x79, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00,
+ 0x67, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00,
+ 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00,
+ 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+unsigned int split_de_fr_arsc_len = 984;
diff --git a/libs/androidfw/tests/data/feature/AndroidManifest.xml b/libs/androidfw/tests/data/feature/AndroidManifest.xml
new file mode 100644
index 0000000..c2343b7
--- /dev/null
+++ b/libs/androidfw/tests/data/feature/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.basic">
+</manifest>
diff --git a/libs/androidfw/tests/data/feature/build b/libs/androidfw/tests/data/feature/build
new file mode 100755
index 0000000..b547dc2
--- /dev/null
+++ b/libs/androidfw/tests/data/feature/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -M AndroidManifest.xml -S res --feature-of ../basic/bundle.apk -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc feature.arsc && \
+xxd -i feature.arsc > feature_arsc.h
diff --git a/libs/androidfw/tests/data/feature/feature_arsc.h b/libs/androidfw/tests/data/feature/feature_arsc.h
new file mode 100644
index 0000000..cf7647d
--- /dev/null
+++ b/libs/androidfw/tests/data/feature/feature_arsc.h
@@ -0,0 +1,73 @@
+unsigned char feature_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0x40, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x33, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xf4, 0x02, 0x00, 0x00,
+ 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
+ 0x2e, 0x00, 0x00, 0x00, 0x07, 0x00, 0x3c, 0x00, 0x65, 0x00, 0x6d, 0x00,
+ 0x70, 0x00, 0x74, 0x00, 0x79, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x04, 0x00,
+ 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00,
+ 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00,
+ 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00,
+ 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+ 0x58, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x33, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x34, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00,
+ 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00,
+ 0x07, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10,
+ 0xc8, 0x00, 0x00, 0x00
+};
+unsigned int feature_arsc_len = 832;
diff --git a/libs/androidfw/tests/data/feature/res/values/values.xml b/libs/androidfw/tests/data/feature/res/values/values.xml
new file mode 100644
index 0000000..d03445a
--- /dev/null
+++ b/libs/androidfw/tests/data/feature/res/values/values.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="test3">test3</string>
+ <string name="test4">test4</string>
+
+ <integer name="number3">200</integer>
+</resources>
diff --git a/libs/androidfw/tests/data/lib/AndroidManifest.xml b/libs/androidfw/tests/data/lib/AndroidManifest.xml
new file mode 100644
index 0000000..a56ac18
--- /dev/null
+++ b/libs/androidfw/tests/data/lib/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.basic">
+ <application>
+ </application>
+</manifest>
diff --git a/libs/androidfw/tests/data/lib/R.h b/libs/androidfw/tests/data/lib/R.h
new file mode 100644
index 0000000..13bf095
--- /dev/null
+++ b/libs/androidfw/tests/data/lib/R.h
@@ -0,0 +1,22 @@
+#ifndef __LIB_R_H
+#define __LIB_R_H
+
+namespace lib {
+namespace R {
+
+namespace attr {
+ enum {
+ attr1 = 0x02010000, // default
+ };
+}
+
+namespace style {
+ enum {
+ Theme = 0x02020000, // default
+ };
+}
+
+} // namespace R
+} // namespace lib
+
+#endif // __LIB_R_H
diff --git a/libs/androidfw/tests/data/lib/build b/libs/androidfw/tests/data/lib/build
new file mode 100755
index 0000000..8e6e70c
--- /dev/null
+++ b/libs/androidfw/tests/data/lib/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -M AndroidManifest.xml -S res -F bundle.apk -f --shared-lib && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc lib.arsc && \
+xxd -i lib.arsc > lib_arsc.h
diff --git a/libs/androidfw/tests/data/lib/lib_arsc.h b/libs/androidfw/tests/data/lib/lib_arsc.h
new file mode 100644
index 0000000..d670c5b
--- /dev/null
+++ b/libs/androidfw/tests/data/lib/lib_arsc.h
@@ -0,0 +1,84 @@
+unsigned char lib_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0xc8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xa0, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00,
+ 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00,
+ 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00,
+ 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00,
+ 0x03, 0x02, 0x0c, 0x00, 0x10, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x64, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x08, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00,
+ 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x64, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00,
+ 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x08, 0x00, 0x00, 0x10, 0xbc, 0x02, 0x00, 0x00
+};
+unsigned int lib_arsc_len = 968;
diff --git a/libs/androidfw/tests/data/lib/res/values/values.xml b/libs/androidfw/tests/data/lib/res/values/values.xml
new file mode 100644
index 0000000..a77f0c7
--- /dev/null
+++ b/libs/androidfw/tests/data/lib/res/values/values.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <attr name="attr1" format="integer" />
+
+ <style name="Theme">
+ <item name="com.android.test.basic:attr1">700</item>
+ </style>
+</resources>
diff --git a/libs/androidfw/tests/data/overlay/AndroidManifest.xml b/libs/androidfw/tests/data/overlay/AndroidManifest.xml
new file mode 100644
index 0000000..a56ac18
--- /dev/null
+++ b/libs/androidfw/tests/data/overlay/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.basic">
+ <application>
+ </application>
+</manifest>
diff --git a/libs/androidfw/tests/data/overlay/build b/libs/androidfw/tests/data/overlay/build
new file mode 100755
index 0000000..87cf6de
--- /dev/null
+++ b/libs/androidfw/tests/data/overlay/build
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+aapt package -M AndroidManifest.xml -S res -F bundle.apk -f && \
+unzip bundle.apk resources.arsc && \
+mv resources.arsc overlay.arsc && \
+xxd -i overlay.arsc > overlay_arsc.h
diff --git a/libs/androidfw/tests/data/overlay/overlay_arsc.h b/libs/androidfw/tests/data/overlay/overlay_arsc.h
new file mode 100644
index 0000000..5bd98b2
--- /dev/null
+++ b/libs/androidfw/tests/data/overlay/overlay_arsc.h
@@ -0,0 +1,69 @@
+unsigned char overlay_arsc[] = {
+ 0x02, 0x00, 0x0c, 0x00, 0x10, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x74, 0x00,
+ 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x6f, 0x00,
+ 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xc4, 0x02, 0x00, 0x00,
+ 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00,
+ 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00,
+ 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00,
+ 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00,
+ 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x06, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x72, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00,
+ 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x50, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00,
+ 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x69, 0x00, 0x6e, 0x00,
+ 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x41, 0x00,
+ 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00, 0x00, 0x00,
+ 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10,
+ 0x0b, 0x00, 0x00, 0x00
+};
+unsigned int overlay_arsc_len = 784;
diff --git a/libs/androidfw/tests/data/overlay/res/values/values.xml b/libs/androidfw/tests/data/overlay/res/values/values.xml
new file mode 100644
index 0000000..227e889
--- /dev/null
+++ b/libs/androidfw/tests/data/overlay/res/values/values.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="test2">test2-overlay</string>
+ <integer-array name="integerArray1">
+ <item>10</item>
+ <item>11</item>
+ </integer-array>
+</resources>
diff --git a/libs/common_time/Android.mk b/libs/common_time/Android.mk
new file mode 100644
index 0000000..75eb528
--- /dev/null
+++ b/libs/common_time/Android.mk
@@ -0,0 +1,36 @@
+LOCAL_PATH:= $(call my-dir)
+
+#
+# common_time_service
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ common_clock_service.cpp \
+ common_time_config_service.cpp \
+ common_time_server.cpp \
+ common_time_server_api.cpp \
+ common_time_server_packets.cpp \
+ clock_recovery.cpp \
+ common_clock.cpp \
+ main.cpp \
+ utils.cpp
+
+# Uncomment to enable vesbose logging and debug service.
+#TIME_SERVICE_DEBUG=true
+ifeq ($(TIME_SERVICE_DEBUG), true)
+LOCAL_SRC_FILES += diag_thread.cpp
+LOCAL_CFLAGS += -DTIME_SERVICE_DEBUG
+endif
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libcommon_time_client \
+ libutils \
+ liblog
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := common_time
+
+include $(BUILD_EXECUTABLE)
diff --git a/libs/common_time/clock_recovery.cpp b/libs/common_time/clock_recovery.cpp
new file mode 100644
index 0000000..3a7c70c
--- /dev/null
+++ b/libs/common_time/clock_recovery.cpp
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define __STDC_LIMIT_MACROS
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+#include <stdint.h>
+
+#include <common_time/local_clock.h>
+#include <assert.h>
+
+#include "clock_recovery.h"
+#include "common_clock.h"
+#ifdef TIME_SERVICE_DEBUG
+#include "diag_thread.h"
+#endif
+
+// Define log macro so we can make LOGV into LOGE when we are exclusively
+// debugging this code.
+#ifdef TIME_SERVICE_DEBUG
+#define LOG_TS ALOGE
+#else
+#define LOG_TS ALOGV
+#endif
+
+namespace android {
+
+ClockRecoveryLoop::ClockRecoveryLoop(LocalClock* local_clock,
+ CommonClock* common_clock) {
+ assert(NULL != local_clock);
+ assert(NULL != common_clock);
+
+ local_clock_ = local_clock;
+ common_clock_ = common_clock;
+
+ local_clock_can_slew_ = local_clock_->initCheck() &&
+ (local_clock_->setLocalSlew(0) == OK);
+ tgt_correction_ = 0;
+ cur_correction_ = 0;
+
+ // Precompute the max rate at which we are allowed to change the VCXO
+ // control.
+ uint64_t N = 0x10000ull * 1000ull;
+ uint64_t D = local_clock_->getLocalFreq() * kMinFullRangeSlewChange_mSec;
+ LinearTransform::reduce(&N, &D);
+ while ((N > INT32_MAX) || (D > UINT32_MAX)) {
+ N >>= 1;
+ D >>= 1;
+ LinearTransform::reduce(&N, &D);
+ }
+ time_to_cur_slew_.a_to_b_numer = static_cast<int32_t>(N);
+ time_to_cur_slew_.a_to_b_denom = static_cast<uint32_t>(D);
+
+ reset(true, true);
+
+#ifdef TIME_SERVICE_DEBUG
+ diag_thread_ = new DiagThread(common_clock_, local_clock_);
+ if (diag_thread_ != NULL) {
+ status_t res = diag_thread_->startWorkThread();
+ if (res != OK)
+ ALOGW("Failed to start A@H clock recovery diagnostic thread.");
+ } else
+ ALOGW("Failed to allocate diagnostic thread.");
+#endif
+}
+
+ClockRecoveryLoop::~ClockRecoveryLoop() {
+#ifdef TIME_SERVICE_DEBUG
+ diag_thread_->stopWorkThread();
+#endif
+}
+
+// Constants.
+const float ClockRecoveryLoop::dT = 1.0;
+const float ClockRecoveryLoop::Kc = 1.0f;
+const float ClockRecoveryLoop::Ti = 15.0f;
+const float ClockRecoveryLoop::Tf = 0.05;
+const float ClockRecoveryLoop::bias_Fc = 0.01;
+const float ClockRecoveryLoop::bias_RC = (dT / (2 * 3.14159f * bias_Fc));
+const float ClockRecoveryLoop::bias_Alpha = (dT / (bias_RC + dT));
+const int64_t ClockRecoveryLoop::panic_thresh_ = 50000;
+const int64_t ClockRecoveryLoop::control_thresh_ = 10000;
+const float ClockRecoveryLoop::COmin = -100.0f;
+const float ClockRecoveryLoop::COmax = 100.0f;
+const uint32_t ClockRecoveryLoop::kMinFullRangeSlewChange_mSec = 300;
+const int ClockRecoveryLoop::kSlewChangeStepPeriod_mSec = 10;
+
+
+void ClockRecoveryLoop::reset(bool position, bool frequency) {
+ Mutex::Autolock lock(&lock_);
+ reset_l(position, frequency);
+}
+
+uint32_t ClockRecoveryLoop::findMinRTTNdx(DisciplineDataPoint* data,
+ uint32_t count) {
+ uint32_t min_rtt = 0;
+ for (uint32_t i = 1; i < count; ++i)
+ if (data[min_rtt].rtt > data[i].rtt)
+ min_rtt = i;
+
+ return min_rtt;
+}
+
+bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time,
+ int64_t nominal_common_time,
+ int64_t rtt) {
+ Mutex::Autolock lock(&lock_);
+
+ int64_t local_common_time = 0;
+ common_clock_->localToCommon(local_time, &local_common_time);
+ int64_t raw_delta = nominal_common_time - local_common_time;
+
+#ifdef TIME_SERVICE_DEBUG
+ ALOGE("local=%lld, common=%lld, delta=%lld, rtt=%lld\n",
+ local_common_time, nominal_common_time,
+ raw_delta, rtt);
+#endif
+
+ // If we have not defined a basis for common time, then we need to use these
+ // initial points to do so. In order to avoid significant initial error
+ // from a particularly bad startup data point, we collect the first N data
+ // points and choose the best of them before moving on.
+ if (!common_clock_->isValid()) {
+ if (startup_filter_wr_ < kStartupFilterSize) {
+ DisciplineDataPoint& d = startup_filter_data_[startup_filter_wr_];
+ d.local_time = local_time;
+ d.nominal_common_time = nominal_common_time;
+ d.rtt = rtt;
+ startup_filter_wr_++;
+ }
+
+ if (startup_filter_wr_ == kStartupFilterSize) {
+ uint32_t min_rtt = findMinRTTNdx(startup_filter_data_,
+ kStartupFilterSize);
+
+ common_clock_->setBasis(
+ startup_filter_data_[min_rtt].local_time,
+ startup_filter_data_[min_rtt].nominal_common_time);
+ }
+
+ return true;
+ }
+
+ int64_t observed_common;
+ int64_t delta;
+ float delta_f, dCO;
+ int32_t tgt_correction;
+
+ if (OK != common_clock_->localToCommon(local_time, &observed_common)) {
+ // Since we just checked to make certain that this conversion was valid,
+ // and no one else in the system should be messing with it, if this
+ // conversion is suddenly invalid, it is a good reason to panic.
+ ALOGE("Failed to convert local time to common time in %s:%d",
+ __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ // Implement a filter which should match NTP filtering behavior when a
+ // client is associated with only one peer of lower stratum. Basically,
+ // always use the best of the N last data points, where best is defined as
+ // lowest round trip time. NTP uses an N of 8; we use a value of 6.
+ //
+ // TODO(johngro) : experiment with other filter strategies. The goal here
+ // is to mitigate the effects of high RTT data points which typically have
+ // large asymmetries in the TX/RX legs. Downside of the existing NTP
+ // approach (particularly because of the PID controller we are using to
+ // produce the control signal from the filtered data) are that the rate at
+ // which discipline events are actually acted upon becomes irregular and can
+ // become drawn out (the time between actionable event can go way up). If
+ // the system receives a strong high quality data point, the proportional
+ // component of the controller can produce a strong correction which is left
+ // in place for too long causing overshoot. In addition, the integral
+ // component of the system currently is an approximation based on the
+ // assumption of a more or less homogeneous sampling of the error. Its
+ // unclear what the effect of undermining this assumption would be right
+ // now.
+
+ // Two ideas which come to mind immediately would be to...
+ // 1) Keep a history of more data points (32 or so) and ignore data points
+ // whose RTT is more than a certain number of standard deviations outside
+ // of the norm.
+ // 2) Eliminate the PID controller portion of this system entirely.
+ // Instead, move to a system which uses a very wide filter (128 data
+ // points or more) with a sum-of-least-squares line fitting approach to
+ // tracking the long term drift. This would take the place of the I
+ // component in the current PID controller. Also use a much more narrow
+ // outlier-rejector filter (as described in #1) to drive a short term
+ // correction factor similar to the P component of the PID controller.
+ assert(filter_wr_ < kFilterSize);
+ filter_data_[filter_wr_].local_time = local_time;
+ filter_data_[filter_wr_].observed_common_time = observed_common;
+ filter_data_[filter_wr_].nominal_common_time = nominal_common_time;
+ filter_data_[filter_wr_].rtt = rtt;
+ filter_data_[filter_wr_].point_used = false;
+ uint32_t current_point = filter_wr_;
+ filter_wr_ = (filter_wr_ + 1) % kFilterSize;
+ if (!filter_wr_)
+ filter_full_ = true;
+
+ uint32_t scan_end = filter_full_ ? kFilterSize : filter_wr_;
+ uint32_t min_rtt = findMinRTTNdx(filter_data_, scan_end);
+ // We only use packets with low RTTs for control. If the packet RTT
+ // is less than the panic threshold, we can probably eat the jitter with the
+ // control loop. Otherwise, take the packet only if it better than all
+ // of the packets we have in the history. That way we try to track
+ // something, even if it is noisy.
+ if (current_point == min_rtt || rtt < control_thresh_) {
+ delta_f = delta = nominal_common_time - observed_common;
+
+ last_error_est_valid_ = true;
+ last_error_est_usec_ = delta;
+
+ // Compute the error then clamp to the panic threshold. If we ever
+ // exceed this amt of error, its time to panic and reset the system.
+ // Given that the error in the measurement of the error could be as
+ // high as the RTT of the data point, we don't actually panic until
+ // the implied error (delta) is greater than the absolute panic
+ // threashold plus the RTT. IOW - we don't panic until we are
+ // absoluely sure that our best case sync is worse than the absolute
+ // panic threshold.
+ int64_t effective_panic_thresh = panic_thresh_ + rtt;
+ if ((delta > effective_panic_thresh) ||
+ (delta < -effective_panic_thresh)) {
+ // PANIC!!!
+ reset_l(false, true);
+ return false;
+ }
+
+ } else {
+ // We do not have a good packet to look at, but we also do not want to
+ // free-run the clock at some crazy slew rate. So we guess the
+ // trajectory of the clock based on the last controller output and the
+ // estimated bias of our clock against the master.
+ // The net effect of this is that CO == CObias after some extended
+ // period of no feedback.
+ delta_f = last_delta_f_ - dT*(CO - CObias);
+ delta = delta_f;
+ }
+
+ // Velocity form PI control equation.
+ dCO = Kc * (1.0f + dT/Ti) * delta_f - Kc * last_delta_f_;
+ CO += dCO * Tf; // Filter CO by applying gain <1 here.
+
+ // Save error terms for later.
+ last_delta_f_ = delta_f;
+
+ // Clamp CO to +/- 100ppm.
+ if (CO < COmin)
+ CO = COmin;
+ else if (CO > COmax)
+ CO = COmax;
+
+ // Update the controller bias.
+ CObias = bias_Alpha * CO + (1.0f - bias_Alpha) * lastCObias;
+ lastCObias = CObias;
+
+ // Convert PPM to 16-bit int range. Add some guard band (-0.01) so we
+ // don't get fp weirdness.
+ tgt_correction = CO * 327.66;
+
+ // If there was a change in the amt of correction to use, update the
+ // system.
+ setTargetCorrection_l(tgt_correction);
+
+ LOG_TS("clock_loop %lld %f %f %f %d\n", raw_delta, delta_f, CO, CObias, tgt_correction);
+
+#ifdef TIME_SERVICE_DEBUG
+ diag_thread_->pushDisciplineEvent(
+ local_time,
+ observed_common,
+ nominal_common_time,
+ tgt_correction,
+ rtt);
+#endif
+
+ return true;
+}
+
+int32_t ClockRecoveryLoop::getLastErrorEstimate() {
+ Mutex::Autolock lock(&lock_);
+
+ if (last_error_est_valid_)
+ return last_error_est_usec_;
+ else
+ return ICommonClock::kErrorEstimateUnknown;
+}
+
+void ClockRecoveryLoop::reset_l(bool position, bool frequency) {
+ assert(NULL != common_clock_);
+
+ if (position) {
+ common_clock_->resetBasis();
+ startup_filter_wr_ = 0;
+ }
+
+ if (frequency) {
+ last_error_est_valid_ = false;
+ last_error_est_usec_ = 0;
+ last_delta_f_ = 0.0;
+ CO = 0.0f;
+ lastCObias = CObias = 0.0f;
+ setTargetCorrection_l(0);
+ applySlew_l();
+ }
+
+ filter_wr_ = 0;
+ filter_full_ = false;
+}
+
+void ClockRecoveryLoop::setTargetCorrection_l(int32_t tgt) {
+ // When we make a change to the slew rate, we need to be careful to not
+ // change it too quickly as it can anger some HDMI sinks out there, notably
+ // some Sony panels from the 2010-2011 timeframe. From experimenting with
+ // some of these sinks, it seems like swinging from one end of the range to
+ // another in less that 190mSec or so can start to cause trouble. Adding in
+ // a hefty margin, we limit the system to a full range sweep in no less than
+ // 300mSec.
+ if (tgt_correction_ != tgt) {
+ int64_t now = local_clock_->getLocalTime();
+ status_t res;
+
+ tgt_correction_ = tgt;
+
+ // Set up the transformation to figure out what the slew should be at
+ // any given point in time in the future.
+ time_to_cur_slew_.a_zero = now;
+ time_to_cur_slew_.b_zero = cur_correction_;
+
+ // Make sure the sign of the slope is headed in the proper direction.
+ bool needs_increase = (cur_correction_ < tgt_correction_);
+ bool is_increasing = (time_to_cur_slew_.a_to_b_numer > 0);
+ if (( needs_increase && !is_increasing) ||
+ (!needs_increase && is_increasing)) {
+ time_to_cur_slew_.a_to_b_numer = -time_to_cur_slew_.a_to_b_numer;
+ }
+
+ // Finally, figure out when the change will be finished and start the
+ // slew operation.
+ time_to_cur_slew_.doReverseTransform(tgt_correction_,
+ &slew_change_end_time_);
+
+ applySlew_l();
+ }
+}
+
+bool ClockRecoveryLoop::applySlew_l() {
+ bool ret = true;
+
+ // If cur == tgt, there is no ongoing sleq rate change and we are already
+ // finished.
+ if (cur_correction_ == tgt_correction_)
+ goto bailout;
+
+ if (local_clock_can_slew_) {
+ int64_t now = local_clock_->getLocalTime();
+ int64_t tmp;
+
+ if (now >= slew_change_end_time_) {
+ cur_correction_ = tgt_correction_;
+ next_slew_change_timeout_.setTimeout(-1);
+ } else {
+ time_to_cur_slew_.doForwardTransform(now, &tmp);
+
+ if (tmp > INT16_MAX)
+ cur_correction_ = INT16_MAX;
+ else if (tmp < INT16_MIN)
+ cur_correction_ = INT16_MIN;
+ else
+ cur_correction_ = static_cast<int16_t>(tmp);
+
+ next_slew_change_timeout_.setTimeout(kSlewChangeStepPeriod_mSec);
+ ret = false;
+ }
+
+ local_clock_->setLocalSlew(cur_correction_);
+ } else {
+ // Since we are not actually changing the rate of a HW clock, we don't
+ // need to worry to much about changing the slew rate so fast that we
+ // anger any downstream HDMI devices.
+ cur_correction_ = tgt_correction_;
+ next_slew_change_timeout_.setTimeout(-1);
+
+ // The SW clock recovery implemented by the common clock class expects
+ // values expressed in PPM. CO is in ppm.
+ common_clock_->setSlew(local_clock_->getLocalTime(), CO);
+ }
+
+bailout:
+ return ret;
+}
+
+int ClockRecoveryLoop::applyRateLimitedSlew() {
+ Mutex::Autolock lock(&lock_);
+
+ int ret = next_slew_change_timeout_.msecTillTimeout();
+ if (!ret) {
+ if (applySlew_l())
+ next_slew_change_timeout_.setTimeout(-1);
+ ret = next_slew_change_timeout_.msecTillTimeout();
+ }
+
+ return ret;
+}
+
+} // namespace android
diff --git a/libs/common_time/clock_recovery.h b/libs/common_time/clock_recovery.h
new file mode 100644
index 0000000..b6c87ff
--- /dev/null
+++ b/libs/common_time/clock_recovery.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __CLOCK_RECOVERY_H__
+#define __CLOCK_RECOVERY_H__
+
+#include <stdint.h>
+#include <common_time/ICommonClock.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+#ifdef TIME_SERVICE_DEBUG
+#include "diag_thread.h"
+#endif
+
+#include "utils.h"
+
+namespace android {
+
+class CommonClock;
+class LocalClock;
+
+class ClockRecoveryLoop {
+ public:
+ ClockRecoveryLoop(LocalClock* local_clock, CommonClock* common_clock);
+ ~ClockRecoveryLoop();
+
+ void reset(bool position, bool frequency);
+ bool pushDisciplineEvent(int64_t local_time,
+ int64_t nominal_common_time,
+ int64_t data_point_rtt);
+ int32_t getLastErrorEstimate();
+
+ // Applies the next step in any ongoing slew change operation. Returns a
+ // timeout suitable for use with poll/select indicating the number of mSec
+ // until the next change should be applied.
+ int applyRateLimitedSlew();
+
+ private:
+
+ // Tuned using the "Good Gain" method.
+ // See:
+ // http://techteach.no/publications/books/dynamics_and_control/tuning_pid_controller.pdf
+
+ // Controller period (1Hz for now).
+ static const float dT;
+
+ // Controller gain, positive and unitless. Larger values converge faster,
+ // but can cause instability.
+ static const float Kc;
+
+ // Integral reset time. Smaller values cause loop to track faster, but can
+ // also cause instability.
+ static const float Ti;
+
+ // Controller output filter time constant. Range (0-1). Smaller values make
+ // output smoother, but slow convergence.
+ static const float Tf;
+
+ // Low-pass filter for bias tracker.
+ static const float bias_Fc; // HZ
+ static const float bias_RC; // Computed in constructor.
+ static const float bias_Alpha; // Computed inconstructor.
+
+ // The maximum allowed error (as indicated by a pushDisciplineEvent) before
+ // we panic.
+ static const int64_t panic_thresh_;
+
+ // The maximum allowed error rtt time for packets to be used for control
+ // feedback, unless the packet is the best in recent memory.
+ static const int64_t control_thresh_;
+
+ typedef struct {
+ int64_t local_time;
+ int64_t observed_common_time;
+ int64_t nominal_common_time;
+ int64_t rtt;
+ bool point_used;
+ } DisciplineDataPoint;
+
+ static uint32_t findMinRTTNdx(DisciplineDataPoint* data, uint32_t count);
+
+ void reset_l(bool position, bool frequency);
+ void setTargetCorrection_l(int32_t tgt);
+ bool applySlew_l();
+
+ // The local clock HW abstraction we use as the basis for common time.
+ LocalClock* local_clock_;
+ bool local_clock_can_slew_;
+
+ // The common clock we end up controlling along with the lock used to
+ // serialize operations.
+ CommonClock* common_clock_;
+ Mutex lock_;
+
+ // parameters maintained while running and reset during a reset
+ // of the frequency correction.
+ bool last_error_est_valid_;
+ int32_t last_error_est_usec_;
+ float last_delta_f_;
+ int32_t integrated_error_;
+ int32_t tgt_correction_;
+ int32_t cur_correction_;
+ LinearTransform time_to_cur_slew_;
+ int64_t slew_change_end_time_;
+ Timeout next_slew_change_timeout_;
+
+ // Contoller Output.
+ float CO;
+
+ // Bias tracking for trajectory estimation.
+ float CObias;
+ float lastCObias;
+
+ // Controller output bounds. The controller will not try to
+ // slew faster that +/-100ppm offset from center per interation.
+ static const float COmin;
+ static const float COmax;
+
+ // State kept for filtering the discipline data.
+ static const uint32_t kFilterSize = 16;
+ DisciplineDataPoint filter_data_[kFilterSize];
+ uint32_t filter_wr_;
+ bool filter_full_;
+
+ static const uint32_t kStartupFilterSize = 4;
+ DisciplineDataPoint startup_filter_data_[kStartupFilterSize];
+ uint32_t startup_filter_wr_;
+
+ // Minimum number of milliseconds over which we allow a full range change
+ // (from rail to rail) of the VCXO control signal. This is the rate
+ // limiting factor which keeps us from changing the clock rate so fast that
+ // we get in trouble with certain HDMI sinks.
+ static const uint32_t kMinFullRangeSlewChange_mSec;
+
+ // How much time (in msec) to wait
+ static const int kSlewChangeStepPeriod_mSec;
+
+#ifdef TIME_SERVICE_DEBUG
+ sp<DiagThread> diag_thread_;
+#endif
+};
+
+} // namespace android
+
+#endif // __CLOCK_RECOVERY_H__
diff --git a/libs/common_time/common_clock.cpp b/libs/common_time/common_clock.cpp
new file mode 100644
index 0000000..c9eb388
--- /dev/null
+++ b/libs/common_time/common_clock.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#define __STDC_LIMIT_MACROS
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <stdint.h>
+
+#include <utils/Errors.h>
+#include <utils/LinearTransform.h>
+
+#include "common_clock.h"
+
+namespace android {
+
+CommonClock::CommonClock() {
+ cur_slew_ = 0;
+ cur_trans_valid_ = false;
+
+ cur_trans_.a_zero = 0;
+ cur_trans_.b_zero = 0;
+ cur_trans_.a_to_b_numer = local_to_common_freq_numer_ = 1;
+ cur_trans_.a_to_b_denom = local_to_common_freq_denom_ = 1;
+ duration_trans_ = cur_trans_;
+}
+
+bool CommonClock::init(uint64_t local_freq) {
+ Mutex::Autolock lock(&lock_);
+
+ if (!local_freq)
+ return false;
+
+ uint64_t numer = kCommonFreq;
+ uint64_t denom = local_freq;
+
+ LinearTransform::reduce(&numer, &denom);
+ if ((numer > UINT32_MAX) || (denom > UINT32_MAX)) {
+ ALOGE("Overflow in CommonClock::init while trying to reduce %lld/%lld",
+ kCommonFreq, local_freq);
+ return false;
+ }
+
+ cur_trans_.a_to_b_numer = local_to_common_freq_numer_ =
+ static_cast<uint32_t>(numer);
+ cur_trans_.a_to_b_denom = local_to_common_freq_denom_ =
+ static_cast<uint32_t>(denom);
+ duration_trans_ = cur_trans_;
+
+ return true;
+}
+
+status_t CommonClock::localToCommon(int64_t local, int64_t *common_out) const {
+ Mutex::Autolock lock(&lock_);
+
+ if (!cur_trans_valid_)
+ return INVALID_OPERATION;
+
+ if (!cur_trans_.doForwardTransform(local, common_out))
+ return INVALID_OPERATION;
+
+ return OK;
+}
+
+status_t CommonClock::commonToLocal(int64_t common, int64_t *local_out) const {
+ Mutex::Autolock lock(&lock_);
+
+ if (!cur_trans_valid_)
+ return INVALID_OPERATION;
+
+ if (!cur_trans_.doReverseTransform(common, local_out))
+ return INVALID_OPERATION;
+
+ return OK;
+}
+
+int64_t CommonClock::localDurationToCommonDuration(int64_t localDur) const {
+ int64_t ret;
+ duration_trans_.doForwardTransform(localDur, &ret);
+ return ret;
+}
+
+void CommonClock::setBasis(int64_t local, int64_t common) {
+ Mutex::Autolock lock(&lock_);
+
+ cur_trans_.a_zero = local;
+ cur_trans_.b_zero = common;
+ cur_trans_valid_ = true;
+}
+
+void CommonClock::resetBasis() {
+ Mutex::Autolock lock(&lock_);
+
+ cur_trans_.a_zero = 0;
+ cur_trans_.b_zero = 0;
+ cur_trans_valid_ = false;
+}
+
+status_t CommonClock::setSlew(int64_t change_time, int32_t ppm) {
+ Mutex::Autolock lock(&lock_);
+
+ int64_t new_local_basis;
+ int64_t new_common_basis;
+
+ if (cur_trans_valid_) {
+ new_local_basis = change_time;
+ if (!cur_trans_.doForwardTransform(change_time, &new_common_basis)) {
+ ALOGE("Overflow when attempting to set slew rate to %d", ppm);
+ return INVALID_OPERATION;
+ }
+ } else {
+ new_local_basis = 0;
+ new_common_basis = 0;
+ }
+
+ cur_slew_ = ppm;
+ uint32_t n1 = local_to_common_freq_numer_;
+ uint32_t n2 = 1000000 + cur_slew_;
+
+ uint32_t d1 = local_to_common_freq_denom_;
+ uint32_t d2 = 1000000;
+
+ // n1/d1 has already been reduced, no need to do so here.
+ LinearTransform::reduce(&n1, &d2);
+ LinearTransform::reduce(&n2, &d1);
+ LinearTransform::reduce(&n2, &d2);
+
+ cur_trans_.a_zero = new_local_basis;
+ cur_trans_.b_zero = new_common_basis;
+ cur_trans_.a_to_b_numer = n1 * n2;
+ cur_trans_.a_to_b_denom = d1 * d2;
+
+ return OK;
+}
+
+} // namespace android
diff --git a/libs/common_time/common_clock.h b/libs/common_time/common_clock.h
new file mode 100644
index 0000000..b786fdc
--- /dev/null
+++ b/libs/common_time/common_clock.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __COMMON_CLOCK_H__
+#define __COMMON_CLOCK_H__
+
+#include <stdint.h>
+
+#include <utils/Errors.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class CommonClock {
+ public:
+ CommonClock();
+
+ bool init(uint64_t local_freq);
+
+ status_t localToCommon(int64_t local, int64_t *common_out) const;
+ status_t commonToLocal(int64_t common, int64_t *local_out) const;
+ int64_t localDurationToCommonDuration(int64_t localDur) const;
+ uint64_t getCommonFreq() const { return kCommonFreq; }
+ bool isValid() const { return cur_trans_valid_; }
+ status_t setSlew(int64_t change_time, int32_t ppm);
+ void setBasis(int64_t local, int64_t common);
+ void resetBasis();
+ private:
+ mutable Mutex lock_;
+
+ int32_t cur_slew_;
+ uint32_t local_to_common_freq_numer_;
+ uint32_t local_to_common_freq_denom_;
+
+ LinearTransform duration_trans_;
+ LinearTransform cur_trans_;
+ bool cur_trans_valid_;
+
+ static const uint64_t kCommonFreq = 1000000ull;
+};
+
+} // namespace android
+#endif // __COMMON_CLOCK_H__
diff --git a/libs/common_time/common_clock_service.cpp b/libs/common_time/common_clock_service.cpp
new file mode 100644
index 0000000..9ca6f35
--- /dev/null
+++ b/libs/common_time/common_clock_service.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011 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 <common_time/local_clock.h>
+#include <utils/String8.h>
+
+#include "common_clock_service.h"
+#include "common_clock.h"
+#include "common_time_server.h"
+
+namespace android {
+
+sp<CommonClockService> CommonClockService::instantiate(
+ CommonTimeServer& timeServer) {
+ sp<CommonClockService> tcc = new CommonClockService(timeServer);
+ if (tcc == NULL)
+ return NULL;
+
+ defaultServiceManager()->addService(ICommonClock::kServiceName, tcc);
+ return tcc;
+}
+
+status_t CommonClockService::dump(int fd, const Vector<String16>& args) {
+ Mutex::Autolock lock(mRegistrationLock);
+ return mTimeServer.dumpClockInterface(fd, args, mListeners.size());
+}
+
+status_t CommonClockService::isCommonTimeValid(bool* valid,
+ uint32_t* timelineID) {
+ return mTimeServer.isCommonTimeValid(valid, timelineID);
+}
+
+status_t CommonClockService::commonTimeToLocalTime(int64_t commonTime,
+ int64_t* localTime) {
+ return mTimeServer.getCommonClock().commonToLocal(commonTime, localTime);
+}
+
+status_t CommonClockService::localTimeToCommonTime(int64_t localTime,
+ int64_t* commonTime) {
+ return mTimeServer.getCommonClock().localToCommon(localTime, commonTime);
+}
+
+status_t CommonClockService::getCommonTime(int64_t* commonTime) {
+ return localTimeToCommonTime(mTimeServer.getLocalClock().getLocalTime(), commonTime);
+}
+
+status_t CommonClockService::getCommonFreq(uint64_t* freq) {
+ *freq = mTimeServer.getCommonClock().getCommonFreq();
+ return OK;
+}
+
+status_t CommonClockService::getLocalTime(int64_t* localTime) {
+ *localTime = mTimeServer.getLocalClock().getLocalTime();
+ return OK;
+}
+
+status_t CommonClockService::getLocalFreq(uint64_t* freq) {
+ *freq = mTimeServer.getLocalClock().getLocalFreq();
+ return OK;
+}
+
+status_t CommonClockService::getEstimatedError(int32_t* estimate) {
+ *estimate = mTimeServer.getEstimatedError();
+ return OK;
+}
+
+status_t CommonClockService::getTimelineID(uint64_t* id) {
+ *id = mTimeServer.getTimelineID();
+ return OK;
+}
+
+status_t CommonClockService::getState(State* state) {
+ *state = mTimeServer.getState();
+ return OK;
+}
+
+status_t CommonClockService::getMasterAddr(struct sockaddr_storage* addr) {
+ return mTimeServer.getMasterAddr(addr);
+}
+
+status_t CommonClockService::registerListener(
+ const sp<ICommonClockListener>& listener) {
+ Mutex::Autolock lock(mRegistrationLock);
+
+ { // scoping for autolock pattern
+ Mutex::Autolock lock(mCallbackLock);
+ // check whether this is a duplicate
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == listener->asBinder())
+ return ALREADY_EXISTS;
+ }
+ }
+
+ mListeners.add(listener);
+ mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+ return listener->asBinder()->linkToDeath(this);
+}
+
+status_t CommonClockService::unregisterListener(
+ const sp<ICommonClockListener>& listener) {
+ Mutex::Autolock lock(mRegistrationLock);
+ status_t ret_val = NAME_NOT_FOUND;
+
+ { // scoping for autolock pattern
+ Mutex::Autolock lock(mCallbackLock);
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == listener->asBinder()) {
+ mListeners[i]->asBinder()->unlinkToDeath(this);
+ mListeners.removeAt(i);
+ ret_val = OK;
+ break;
+ }
+ }
+ }
+
+ mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+ return ret_val;
+}
+
+void CommonClockService::binderDied(const wp<IBinder>& who) {
+ Mutex::Autolock lock(mRegistrationLock);
+
+ { // scoping for autolock pattern
+ Mutex::Autolock lock(mCallbackLock);
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ if (mListeners[i]->asBinder() == who) {
+ mListeners.removeAt(i);
+ break;
+ }
+ }
+ }
+
+ mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+}
+
+void CommonClockService::notifyOnTimelineChanged(uint64_t timelineID) {
+ Mutex::Autolock lock(mCallbackLock);
+
+ for (size_t i = 0; i < mListeners.size(); i++) {
+ mListeners[i]->onTimelineChanged(timelineID);
+ }
+}
+
+}; // namespace android
diff --git a/libs/common_time/common_clock_service.h b/libs/common_time/common_clock_service.h
new file mode 100644
index 0000000..bd663f0
--- /dev/null
+++ b/libs/common_time/common_clock_service.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_COMMON_CLOCK_SERVICE_H
+#define ANDROID_COMMON_CLOCK_SERVICE_H
+
+#include <sys/socket.h>
+#include <common_time/ICommonClock.h>
+
+namespace android {
+
+class CommonTimeServer;
+
+class CommonClockService : public BnCommonClock,
+ public android::IBinder::DeathRecipient {
+ public:
+ static sp<CommonClockService> instantiate(CommonTimeServer& timeServer);
+
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ virtual status_t isCommonTimeValid(bool* valid, uint32_t *timelineID);
+ virtual status_t commonTimeToLocalTime(int64_t common_time,
+ int64_t* local_time);
+ virtual status_t localTimeToCommonTime(int64_t local_time,
+ int64_t* common_time);
+ virtual status_t getCommonTime(int64_t* common_time);
+ virtual status_t getCommonFreq(uint64_t* freq);
+ virtual status_t getLocalTime(int64_t* local_time);
+ virtual status_t getLocalFreq(uint64_t* freq);
+ virtual status_t getEstimatedError(int32_t* estimate);
+ virtual status_t getTimelineID(uint64_t* id);
+ virtual status_t getState(ICommonClock::State* state);
+ virtual status_t getMasterAddr(struct sockaddr_storage* addr);
+
+ virtual status_t registerListener(
+ const sp<ICommonClockListener>& listener);
+ virtual status_t unregisterListener(
+ const sp<ICommonClockListener>& listener);
+
+ void notifyOnTimelineChanged(uint64_t timelineID);
+
+ private:
+ CommonClockService(CommonTimeServer& timeServer)
+ : mTimeServer(timeServer) { };
+
+ virtual void binderDied(const wp<IBinder>& who);
+
+ CommonTimeServer& mTimeServer;
+
+ // locks used to synchronize access to the list of registered listeners.
+ // The callback lock is held whenever the list is used to perform callbacks
+ // or while the list is being modified. The registration lock used to
+ // serialize access across registerListener, unregisterListener, and
+ // binderDied.
+ //
+ // The reason for two locks is that registerListener, unregisterListener,
+ // and binderDied each call into the core service and obtain the core
+ // service thread lock when they call reevaluateAutoDisableState. The core
+ // service thread obtains the main thread lock whenever its thread is
+ // running, and sometimes needs to call notifyOnTimelineChanged which then
+ // obtains the callback lock. If callers of registration functions were
+ // holding the callback lock when they called into the core service, we
+ // would have a classic A/B, B/A ordering deadlock. To avoid this, the
+ // registration functions hold the registration lock for the duration of
+ // their call, but hold the callback lock only while they mutate the list.
+ // This way, the list's size cannot change (because of the registration
+ // lock) during the call into reevaluateAutoDisableState, but the core work
+ // thread can still safely call notifyOnTimelineChanged while holding the
+ // main thread lock.
+ Mutex mCallbackLock;
+ Mutex mRegistrationLock;
+
+ Vector<sp<ICommonClockListener> > mListeners;
+};
+
+}; // namespace android
+
+#endif // ANDROID_COMMON_CLOCK_SERVICE_H
diff --git a/libs/common_time/common_time_config_service.cpp b/libs/common_time/common_time_config_service.cpp
new file mode 100644
index 0000000..9585618
--- /dev/null
+++ b/libs/common_time/common_time_config_service.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 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/String8.h>
+
+#include "common_time_config_service.h"
+#include "common_time_server.h"
+
+namespace android {
+
+sp<CommonTimeConfigService> CommonTimeConfigService::instantiate(
+ CommonTimeServer& timeServer) {
+ sp<CommonTimeConfigService> ctcs = new CommonTimeConfigService(timeServer);
+ if (ctcs == NULL)
+ return NULL;
+
+ defaultServiceManager()->addService(ICommonTimeConfig::kServiceName, ctcs);
+ return ctcs;
+}
+
+status_t CommonTimeConfigService::dump(int fd, const Vector<String16>& args) {
+ return mTimeServer.dumpConfigInterface(fd, args);
+}
+
+status_t CommonTimeConfigService::getMasterElectionPriority(uint8_t *priority) {
+ return mTimeServer.getMasterElectionPriority(priority);
+}
+
+status_t CommonTimeConfigService::setMasterElectionPriority(uint8_t priority) {
+ return mTimeServer.setMasterElectionPriority(priority);
+}
+
+status_t CommonTimeConfigService::getMasterElectionEndpoint(
+ struct sockaddr_storage *addr) {
+ return mTimeServer.getMasterElectionEndpoint(addr);
+}
+
+status_t CommonTimeConfigService::setMasterElectionEndpoint(
+ const struct sockaddr_storage *addr) {
+ return mTimeServer.setMasterElectionEndpoint(addr);
+}
+
+status_t CommonTimeConfigService::getMasterElectionGroupId(uint64_t *id) {
+ return mTimeServer.getMasterElectionGroupId(id);
+}
+
+status_t CommonTimeConfigService::setMasterElectionGroupId(uint64_t id) {
+ return mTimeServer.setMasterElectionGroupId(id);
+}
+
+status_t CommonTimeConfigService::getInterfaceBinding(String16& ifaceName) {
+ String8 tmp;
+ status_t ret = mTimeServer.getInterfaceBinding(tmp);
+ ifaceName = String16(tmp);
+ return ret;
+}
+
+status_t CommonTimeConfigService::setInterfaceBinding(const String16& ifaceName) {
+ String8 tmp(ifaceName);
+ return mTimeServer.setInterfaceBinding(tmp);
+}
+
+status_t CommonTimeConfigService::getMasterAnnounceInterval(int *interval) {
+ return mTimeServer.getMasterAnnounceInterval(interval);
+}
+
+status_t CommonTimeConfigService::setMasterAnnounceInterval(int interval) {
+ return mTimeServer.setMasterAnnounceInterval(interval);
+}
+
+status_t CommonTimeConfigService::getClientSyncInterval(int *interval) {
+ return mTimeServer.getClientSyncInterval(interval);
+}
+
+status_t CommonTimeConfigService::setClientSyncInterval(int interval) {
+ return mTimeServer.setClientSyncInterval(interval);
+}
+
+status_t CommonTimeConfigService::getPanicThreshold(int *threshold) {
+ return mTimeServer.getPanicThreshold(threshold);
+}
+
+status_t CommonTimeConfigService::setPanicThreshold(int threshold) {
+ return mTimeServer.setPanicThreshold(threshold);
+}
+
+status_t CommonTimeConfigService::getAutoDisable(bool *autoDisable) {
+ return mTimeServer.getAutoDisable(autoDisable);
+}
+
+status_t CommonTimeConfigService::setAutoDisable(bool autoDisable) {
+ return mTimeServer.setAutoDisable(autoDisable);
+}
+
+status_t CommonTimeConfigService::forceNetworklessMasterMode() {
+ return mTimeServer.forceNetworklessMasterMode();
+}
+
+}; // namespace android
diff --git a/libs/common_time/common_time_config_service.h b/libs/common_time/common_time_config_service.h
new file mode 100644
index 0000000..89806dd
--- /dev/null
+++ b/libs/common_time/common_time_config_service.h
@@ -0,0 +1,60 @@
+/* * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_COMMON_TIME_CONFIG_SERVICE_H
+#define ANDROID_COMMON_TIME_CONFIG_SERVICE_H
+
+#include <sys/socket.h>
+#include <common_time/ICommonTimeConfig.h>
+
+namespace android {
+
+class String16;
+class CommonTimeServer;
+
+class CommonTimeConfigService : public BnCommonTimeConfig {
+ public:
+ static sp<CommonTimeConfigService> instantiate(CommonTimeServer& timeServer);
+
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ virtual status_t getMasterElectionPriority(uint8_t *priority);
+ virtual status_t setMasterElectionPriority(uint8_t priority);
+ virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr);
+ virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr);
+ virtual status_t getMasterElectionGroupId(uint64_t *id);
+ virtual status_t setMasterElectionGroupId(uint64_t id);
+ virtual status_t getInterfaceBinding(String16& ifaceName);
+ virtual status_t setInterfaceBinding(const String16& ifaceName);
+ virtual status_t getMasterAnnounceInterval(int *interval);
+ virtual status_t setMasterAnnounceInterval(int interval);
+ virtual status_t getClientSyncInterval(int *interval);
+ virtual status_t setClientSyncInterval(int interval);
+ virtual status_t getPanicThreshold(int *threshold);
+ virtual status_t setPanicThreshold(int threshold);
+ virtual status_t getAutoDisable(bool *autoDisable);
+ virtual status_t setAutoDisable(bool autoDisable);
+ virtual status_t forceNetworklessMasterMode();
+
+ private:
+ CommonTimeConfigService(CommonTimeServer& timeServer)
+ : mTimeServer(timeServer) { }
+ CommonTimeServer& mTimeServer;
+
+};
+
+}; // namespace android
+
+#endif // ANDROID_COMMON_TIME_CONFIG_SERVICE_H
diff --git a/libs/common_time/common_time_server.cpp b/libs/common_time/common_time_server.cpp
new file mode 100644
index 0000000..3e11987
--- /dev/null
+++ b/libs/common_time/common_time_server.cpp
@@ -0,0 +1,1506 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <linux/if_ether.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/ip.h>
+#include <poll.h>
+#include <stdio.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <common_time/local_clock.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <utils/Timers.h>
+
+#include "common_clock_service.h"
+#include "common_time_config_service.h"
+#include "common_time_server.h"
+#include "common_time_server_packets.h"
+#include "clock_recovery.h"
+#include "common_clock.h"
+
+#define MAX_INT ((int)0x7FFFFFFF)
+
+namespace android {
+
+const char* CommonTimeServer::kDefaultMasterElectionAddr = "255.255.255.255";
+const uint16_t CommonTimeServer::kDefaultMasterElectionPort = 8886;
+const uint64_t CommonTimeServer::kDefaultSyncGroupID = 1;
+const uint8_t CommonTimeServer::kDefaultMasterPriority = 1;
+const uint32_t CommonTimeServer::kDefaultMasterAnnounceIntervalMs = 10000;
+const uint32_t CommonTimeServer::kDefaultSyncRequestIntervalMs = 1000;
+const uint32_t CommonTimeServer::kDefaultPanicThresholdUsec = 50000;
+const bool CommonTimeServer::kDefaultAutoDisable = true;
+const int CommonTimeServer::kSetupRetryTimeoutMs = 30000;
+const int64_t CommonTimeServer::kNoGoodDataPanicThresholdUsec = 600000000ll;
+const uint32_t CommonTimeServer::kRTTDiscardPanicThreshMultiplier = 5;
+
+// timeout value representing an infinite timeout
+const int CommonTimeServer::kInfiniteTimeout = -1;
+
+/*** Initial state constants ***/
+
+// number of WhoIsMaster attempts sent before giving up
+const int CommonTimeServer::kInitial_NumWhoIsMasterRetries = 6;
+
+// timeout used when waiting for a response to a WhoIsMaster request
+const int CommonTimeServer::kInitial_WhoIsMasterTimeoutMs = 500;
+
+/*** Client state constants ***/
+
+// number of sync requests that can fail before a client assumes its master
+// is dead
+const int CommonTimeServer::kClient_NumSyncRequestRetries = 10;
+
+/*** Master state constants ***/
+
+/*** Ronin state constants ***/
+
+// number of WhoIsMaster attempts sent before declaring ourselves master
+const int CommonTimeServer::kRonin_NumWhoIsMasterRetries = 20;
+
+// timeout used when waiting for a response to a WhoIsMaster request
+const int CommonTimeServer::kRonin_WhoIsMasterTimeoutMs = 500;
+
+/*** WaitForElection state constants ***/
+
+// how long do we wait for an announcement from a master before
+// trying another election?
+const int CommonTimeServer::kWaitForElection_TimeoutMs = 12500;
+
+CommonTimeServer::CommonTimeServer()
+ : Thread(false)
+ , mState(ICommonClock::STATE_INITIAL)
+ , mClockRecovery(&mLocalClock, &mCommonClock)
+ , mSocket(-1)
+ , mLastPacketRxLocalTime(0)
+ , mTimelineID(ICommonClock::kInvalidTimelineID)
+ , mClockSynced(false)
+ , mCommonClockHasClients(false)
+ , mStateChangeLog("Recent State Change Events", 30)
+ , mElectionLog("Recent Master Election Traffic", 30)
+ , mBadPktLog("Recent Bad Packet RX Info", 8)
+ , mInitial_WhoIsMasterRequestTimeouts(0)
+ , mClient_MasterDeviceID(0)
+ , mClient_MasterDevicePriority(0)
+ , mRonin_WhoIsMasterRequestTimeouts(0) {
+ // zero out sync stats
+ resetSyncStats();
+
+ // Setup the master election endpoint to use the default.
+ struct sockaddr_in* meep =
+ reinterpret_cast<struct sockaddr_in*>(&mMasterElectionEP);
+ memset(&mMasterElectionEP, 0, sizeof(mMasterElectionEP));
+ inet_aton(kDefaultMasterElectionAddr, &meep->sin_addr);
+ meep->sin_family = AF_INET;
+ meep->sin_port = htons(kDefaultMasterElectionPort);
+
+ // Zero out the master endpoint.
+ memset(&mMasterEP, 0, sizeof(mMasterEP));
+ mMasterEPValid = false;
+ mBindIfaceValid = false;
+ setForceLowPriority(false);
+
+ // Set all remaining configuration parameters to their defaults.
+ mDeviceID = 0;
+ mSyncGroupID = kDefaultSyncGroupID;
+ mMasterPriority = kDefaultMasterPriority;
+ mMasterAnnounceIntervalMs = kDefaultMasterAnnounceIntervalMs;
+ mSyncRequestIntervalMs = kDefaultSyncRequestIntervalMs;
+ mPanicThresholdUsec = kDefaultPanicThresholdUsec;
+ mAutoDisable = kDefaultAutoDisable;
+
+ // Create the eventfd we will use to signal our thread to wake up when
+ // needed.
+ mWakeupThreadFD = eventfd(0, EFD_NONBLOCK);
+
+ // seed the random number generator (used to generated timeline IDs)
+ srand48(static_cast<unsigned int>(systemTime()));
+}
+
+CommonTimeServer::~CommonTimeServer() {
+ shutdownThread();
+
+ // No need to grab the lock here. We are in the destructor; if the the user
+ // has a thread in any of the APIs while the destructor is being called,
+ // there is a threading problem a the application level we cannot reasonably
+ // do anything about.
+ cleanupSocket_l();
+
+ if (mWakeupThreadFD >= 0) {
+ close(mWakeupThreadFD);
+ mWakeupThreadFD = -1;
+ }
+}
+
+bool CommonTimeServer::startServices() {
+ // start the ICommonClock service
+ mICommonClock = CommonClockService::instantiate(*this);
+ if (mICommonClock == NULL)
+ return false;
+
+ // start the ICommonTimeConfig service
+ mICommonTimeConfig = CommonTimeConfigService::instantiate(*this);
+ if (mICommonTimeConfig == NULL)
+ return false;
+
+ return true;
+}
+
+bool CommonTimeServer::threadLoop() {
+ // Register our service interfaces.
+ if (!startServices())
+ return false;
+
+ // Hold the lock while we are in the main thread loop. It will release the
+ // lock when it blocks, and hold the lock at all other times.
+ mLock.lock();
+ runStateMachine_l();
+ mLock.unlock();
+
+ IPCThreadState::self()->stopProcess();
+ return false;
+}
+
+bool CommonTimeServer::runStateMachine_l() {
+ if (!mLocalClock.initCheck())
+ return false;
+
+ if (!mCommonClock.init(mLocalClock.getLocalFreq()))
+ return false;
+
+ // Enter the initial state.
+ becomeInitial("startup");
+
+ // run the state machine
+ while (!exitPending()) {
+ struct pollfd pfds[2];
+ int rc, timeout;
+ int eventCnt = 0;
+ int64_t wakeupTime;
+ uint32_t t1, t2;
+ bool needHandleTimeout = false;
+
+ // We are always interested in our wakeup FD.
+ pfds[eventCnt].fd = mWakeupThreadFD;
+ pfds[eventCnt].events = POLLIN;
+ pfds[eventCnt].revents = 0;
+ eventCnt++;
+
+ // If we have a valid socket, then we are interested in what it has to
+ // say as well.
+ if (mSocket >= 0) {
+ pfds[eventCnt].fd = mSocket;
+ pfds[eventCnt].events = POLLIN;
+ pfds[eventCnt].revents = 0;
+ eventCnt++;
+ }
+
+ t1 = static_cast<uint32_t>(mCurTimeout.msecTillTimeout());
+ t2 = static_cast<uint32_t>(mClockRecovery.applyRateLimitedSlew());
+ timeout = static_cast<int>(t1 < t2 ? t1 : t2);
+
+ // Note, we were holding mLock when this function was called. We
+ // release it only while we are blocking and hold it at all other times.
+ mLock.unlock();
+ rc = poll(pfds, eventCnt, timeout);
+ wakeupTime = mLocalClock.getLocalTime();
+ mLock.lock();
+
+ // Is it time to shutdown? If so, don't hesitate... just do it.
+ if (exitPending())
+ break;
+
+ // Did the poll fail? This should never happen and is fatal if it does.
+ if (rc < 0) {
+ ALOGE("%s:%d poll failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ if (rc == 0) {
+ needHandleTimeout = !mCurTimeout.msecTillTimeout();
+ if (needHandleTimeout)
+ mCurTimeout.setTimeout(kInfiniteTimeout);
+ }
+
+ // Were we woken up on purpose? If so, clear the eventfd with a read.
+ if (pfds[0].revents)
+ clearPendingWakeupEvents_l();
+
+ // Is out bind address dirty? If so, clean up our socket (if any).
+ // Alternatively, do we have an active socket but should be auto
+ // disabled? If so, release the socket and enter the proper sync state.
+ bool droppedSocket = false;
+ if (mBindIfaceDirty || ((mSocket >= 0) && shouldAutoDisable())) {
+ cleanupSocket_l();
+ mBindIfaceDirty = false;
+ droppedSocket = true;
+ }
+
+ // Do we not have a socket but should have one? If so, try to set one
+ // up.
+ if ((mSocket < 0) && mBindIfaceValid && !shouldAutoDisable()) {
+ if (setupSocket_l()) {
+ // Success! We are now joining a new network (either coming
+ // from no network, or coming from a potentially different
+ // network). Force our priority to be lower so that we defer to
+ // any other masters which may already be on the network we are
+ // joining. Later, when we enter either the client or the
+ // master state, we will clear this flag and go back to our
+ // normal election priority.
+ setForceLowPriority(true);
+ switch (mState) {
+ // If we were in initial (whether we had a immediately
+ // before this network or not) we want to simply reset the
+ // system and start again. Forcing a transition from
+ // INITIAL to INITIAL should do the job.
+ case CommonClockService::STATE_INITIAL:
+ becomeInitial("bound interface");
+ break;
+
+ // If we were in the master state, then either we were the
+ // master in a no-network situation, or we were the master
+ // of a different network and have moved to a new interface.
+ // In either case, immediately transition to Ronin at low
+ // priority. If there is no one in the network we just
+ // joined, we will become master soon enough. If there is,
+ // we want to be certain to defer master status to the
+ // existing timeline currently running on the network.
+ //
+ case CommonClockService::STATE_MASTER:
+ becomeRonin("leaving networkless mode");
+ break;
+
+ // If we were in any other state (CLIENT, RONIN, or
+ // WAIT_FOR_ELECTION) then we must be moving from one
+ // network to another. We have lost our old master;
+ // transition to RONIN in an attempt to find a new master.
+ // If there are none out there, we will just assume
+ // responsibility for the timeline we used to be a client
+ // of.
+ default:
+ becomeRonin("bound interface");
+ break;
+ }
+ } else {
+ // That's odd... we failed to set up our socket. This could be
+ // due to some transient network change which will work itself
+ // out shortly; schedule a retry attempt in the near future.
+ mCurTimeout.setTimeout(kSetupRetryTimeoutMs);
+ }
+
+ // One way or the other, we don't have any data to process at this
+ // point (since we just tried to bulid a new socket). Loop back
+ // around and wait for the next thing to do.
+ continue;
+ } else if (droppedSocket) {
+ // We just lost our socket, and for whatever reason (either no
+ // config, or auto disable engaged) we are not supposed to rebuild
+ // one at this time. We are not going to rebuild our socket until
+ // something about our config/auto-disabled status changes, so we
+ // are basically in network-less mode. If we are already in either
+ // INITIAL or MASTER, just stay there until something changes. If
+ // we are in any other state (CLIENT, RONIN or WAIT_FOR_ELECTION),
+ // then transition to either INITIAL or MASTER depending on whether
+ // or not our timeline is valid.
+ mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG,
+ "Entering networkless mode interface is %s, "
+ "shouldAutoDisable = %s",
+ mBindIfaceValid ? "valid" : "invalid",
+ shouldAutoDisable() ? "true" : "false");
+ if ((mState != ICommonClock::STATE_INITIAL) &&
+ (mState != ICommonClock::STATE_MASTER)) {
+ if (mTimelineID == ICommonClock::kInvalidTimelineID)
+ becomeInitial("network-less mode");
+ else
+ becomeMaster("network-less mode");
+ }
+
+ continue;
+ }
+
+ // Time to handle the timeouts?
+ if (needHandleTimeout) {
+ if (!handleTimeout())
+ ALOGE("handleTimeout failed");
+ continue;
+ }
+
+ // Does our socket have data for us (assuming we still have one, we
+ // may have RXed a packet at the same time as a config change telling us
+ // to shut our socket down)? If so, process its data.
+ if ((mSocket >= 0) && (eventCnt > 1) && (pfds[1].revents)) {
+ mLastPacketRxLocalTime = wakeupTime;
+ if (!handlePacket())
+ ALOGE("handlePacket failed");
+ }
+ }
+
+ cleanupSocket_l();
+ return true;
+}
+
+void CommonTimeServer::clearPendingWakeupEvents_l() {
+ int64_t tmp;
+ read(mWakeupThreadFD, &tmp, sizeof(tmp));
+}
+
+void CommonTimeServer::wakeupThread_l() {
+ int64_t tmp = 1;
+ write(mWakeupThreadFD, &tmp, sizeof(tmp));
+}
+
+void CommonTimeServer::cleanupSocket_l() {
+ if (mSocket >= 0) {
+ close(mSocket);
+ mSocket = -1;
+ }
+}
+
+void CommonTimeServer::shutdownThread() {
+ // Flag the work thread for shutdown.
+ this->requestExit();
+
+ // Signal the thread in case its sleeping.
+ mLock.lock();
+ wakeupThread_l();
+ mLock.unlock();
+
+ // Wait for the thread to exit.
+ this->join();
+}
+
+bool CommonTimeServer::setupSocket_l() {
+ int rc;
+ bool ret_val = false;
+ struct sockaddr_in* ipv4_addr = NULL;
+ char masterElectionEPStr[64];
+ const int one = 1;
+
+ // This should never be needed, but if we happened to have an old socket
+ // lying around, be sure to not leak it before proceeding.
+ cleanupSocket_l();
+
+ // If we don't have a valid endpoint to bind to, then how did we get here in
+ // the first place? Regardless, we know that we are going to fail to bind,
+ // so don't even try.
+ if (!mBindIfaceValid)
+ return false;
+
+ sockaddrToString(mMasterElectionEP, true, masterElectionEPStr,
+ sizeof(masterElectionEPStr));
+ mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG,
+ "Building socket :: bind = %s master election = %s",
+ mBindIface.string(), masterElectionEPStr);
+
+ // TODO: add proper support for IPv6. Right now, we block IPv6 addresses at
+ // the configuration interface level.
+ if (AF_INET != mMasterElectionEP.ss_family) {
+ mStateChangeLog.log(ANDROID_LOG_WARN, LOG_TAG,
+ "TODO: add proper IPv6 support");
+ goto bailout;
+ }
+
+ // open a UDP socket for the timeline serivce
+ mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (mSocket < 0) {
+ mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG,
+ "Failed to create socket (errno = %d)", errno);
+ goto bailout;
+ }
+
+ // Bind to the selected interface using Linux's spiffy SO_BINDTODEVICE.
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", mBindIface.string());
+ ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = 0;
+ rc = setsockopt(mSocket, SOL_SOCKET, SO_BINDTODEVICE,
+ (void *)&ifr, sizeof(ifr));
+ if (rc) {
+ mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG,
+ "Failed to bind socket at to interface %s "
+ "(errno = %d)", ifr.ifr_name, errno);
+ goto bailout;
+ }
+
+ // Bind our socket to INADDR_ANY and the master election port. The
+ // interface binding we made using SO_BINDTODEVICE should limit us to
+ // traffic only on the interface we are interested in. We need to bind to
+ // INADDR_ANY and the specific master election port in order to be able to
+ // receive both unicast traffic and master election multicast traffic with
+ // just a single socket.
+ struct sockaddr_in bindAddr;
+ ipv4_addr = reinterpret_cast<struct sockaddr_in*>(&mMasterElectionEP);
+ memcpy(&bindAddr, ipv4_addr, sizeof(bindAddr));
+ bindAddr.sin_addr.s_addr = INADDR_ANY;
+ rc = bind(mSocket,
+ reinterpret_cast<const sockaddr *>(&bindAddr),
+ sizeof(bindAddr));
+ if (rc) {
+ mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG,
+ "Failed to bind socket to port %hu (errno = %d)",
+ ntohs(bindAddr.sin_port), errno);
+ goto bailout;
+ }
+
+ if (0xE0000000 == (ntohl(ipv4_addr->sin_addr.s_addr) & 0xF0000000)) {
+ // If our master election endpoint is a multicast address, be sure to join
+ // the multicast group.
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = ipv4_addr->sin_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ rc = setsockopt(mSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (rc == -1) {
+ ALOGE("Failed to join multicast group at %s. (errno = %d)",
+ masterElectionEPStr, errno);
+ goto bailout;
+ }
+
+ // disable loopback of multicast packets
+ const int zero = 0;
+ rc = setsockopt(mSocket, IPPROTO_IP, IP_MULTICAST_LOOP,
+ &zero, sizeof(zero));
+ if (rc == -1) {
+ mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG,
+ "Failed to disable multicast loopback "
+ "(errno = %d)", errno);
+ goto bailout;
+ }
+ } else
+ if (ntohl(ipv4_addr->sin_addr.s_addr) == 0xFFFFFFFF) {
+ // If the master election address is the broadcast address, then enable
+ // the broadcast socket option
+ rc = setsockopt(mSocket, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one));
+ if (rc == -1) {
+ mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG,
+ "Failed to enable broadcast (errno = %d)",
+ errno);
+ goto bailout;
+ }
+ } else {
+ // If the master election address is neither broadcast, nor multicast,
+ // then we are misconfigured. The config API layer should prevent this
+ // from ever happening.
+ goto bailout;
+ }
+
+ // Set the TTL of sent packets to 1. (Time protocol sync should never leave
+ // the local subnet)
+ rc = setsockopt(mSocket, IPPROTO_IP, IP_TTL, &one, sizeof(one));
+ if (rc == -1) {
+ mStateChangeLog.log(ANDROID_LOG_ERROR, LOG_TAG,
+ "Failed to set TTL to %d (errno = %d)", one, errno);
+ goto bailout;
+ }
+
+ // get the device's unique ID
+ if (!assignDeviceID())
+ goto bailout;
+
+ ret_val = true;
+
+bailout:
+ if (!ret_val)
+ cleanupSocket_l();
+ return ret_val;
+}
+
+// generate a unique device ID that can be used for arbitration
+bool CommonTimeServer::assignDeviceID() {
+ if (!mBindIfaceValid)
+ return false;
+
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_addr.sa_family = AF_INET;
+ strlcpy(ifr.ifr_name, mBindIface.string(), IFNAMSIZ);
+
+ int rc = ioctl(mSocket, SIOCGIFHWADDR, &ifr);
+ if (rc) {
+ ALOGE("%s:%d ioctl failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ if (ifr.ifr_addr.sa_family != ARPHRD_ETHER) {
+ ALOGE("%s:%d got non-Ethernet address", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ mDeviceID = 0;
+ for (int i = 0; i < ETH_ALEN; i++) {
+ mDeviceID = (mDeviceID << 8) | ifr.ifr_hwaddr.sa_data[i];
+ }
+
+ return true;
+}
+
+// generate a new timeline ID
+void CommonTimeServer::assignTimelineID() {
+ do {
+ mTimelineID = (static_cast<uint64_t>(lrand48()) << 32)
+ | static_cast<uint64_t>(lrand48());
+ } while (mTimelineID == ICommonClock::kInvalidTimelineID);
+}
+
+// Select a preference between the device IDs of two potential masters.
+// Returns true if the first ID wins, or false if the second ID wins.
+bool CommonTimeServer::arbitrateMaster(
+ uint64_t deviceID1, uint8_t devicePrio1,
+ uint64_t deviceID2, uint8_t devicePrio2) {
+ return ((devicePrio1 > devicePrio2) ||
+ ((devicePrio1 == devicePrio2) && (deviceID1 > deviceID2)));
+}
+
+static void hexDumpToString(const uint8_t* src, size_t src_len,
+ char* dst, size_t dst_len) {
+ size_t offset = 0;
+ size_t i;
+
+ for (i = 0; (i < src_len) && (offset < dst_len); ++i) {
+ int res;
+ if (0 == (i % 16)) {
+ res = snprintf(dst + offset, dst_len - offset, "\n%04zx :", i);
+ if (res < 0)
+ break;
+ offset += res;
+ if (offset >= dst_len)
+ break;
+ }
+
+ res = snprintf(dst + offset, dst_len - offset, " %02x", src[i]);
+ if (res < 0)
+ break;
+ offset += res;
+ }
+
+ dst[dst_len - 1] = 0;
+}
+
+bool CommonTimeServer::handlePacket() {
+ uint8_t buf[256];
+ struct sockaddr_storage srcAddr;
+ socklen_t srcAddrLen = sizeof(srcAddr);
+
+ ssize_t recvBytes = recvfrom(
+ mSocket, buf, sizeof(buf), 0,
+ reinterpret_cast<const sockaddr *>(&srcAddr), &srcAddrLen);
+
+ if (recvBytes < 0) {
+ mBadPktLog.log(ANDROID_LOG_ERROR, LOG_TAG,
+ "recvfrom failed (res %d, errno %d)",
+ recvBytes, errno);
+ return false;
+ }
+
+ UniversalTimeServicePacket pkt;
+ if (pkt.deserializePacket(buf, recvBytes, mSyncGroupID) < 0) {
+ char hex[256];
+ char srcEPStr[64];
+
+ hexDumpToString(buf, static_cast<size_t>(recvBytes), hex, sizeof(hex));
+ sockaddrToString(srcAddr, true, srcEPStr, sizeof(srcEPStr));
+
+ mBadPktLog.log("Failed to parse %d byte packet from %s.%s",
+ recvBytes, srcEPStr, hex);
+ return false;
+ }
+
+ bool result;
+ switch (pkt.packetType) {
+ case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+ result = handleWhoIsMasterRequest(&pkt.p.who_is_master_request,
+ srcAddr);
+ break;
+
+ case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+ result = handleWhoIsMasterResponse(&pkt.p.who_is_master_response,
+ srcAddr);
+ break;
+
+ case TIME_PACKET_SYNC_REQUEST:
+ result = handleSyncRequest(&pkt.p.sync_request, srcAddr);
+ break;
+
+ case TIME_PACKET_SYNC_RESPONSE:
+ result = handleSyncResponse(&pkt.p.sync_response, srcAddr);
+ break;
+
+ case TIME_PACKET_MASTER_ANNOUNCEMENT:
+ result = handleMasterAnnouncement(&pkt.p.master_announcement,
+ srcAddr);
+ break;
+
+ default: {
+ char srcEPStr[64];
+ sockaddrToString(srcAddr, true, srcEPStr, sizeof(srcEPStr));
+
+ mBadPktLog.log(ANDROID_LOG_WARN, LOG_TAG,
+ "unknown packet type (%d) from %s",
+ pkt.packetType, srcEPStr);
+
+ result = false;
+ } break;
+ }
+
+ return result;
+}
+
+bool CommonTimeServer::handleTimeout() {
+ // If we have no socket, then this must be a timeout to retry socket setup.
+ if (mSocket < 0)
+ return true;
+
+ switch (mState) {
+ case ICommonClock::STATE_INITIAL:
+ return handleTimeoutInitial();
+ case ICommonClock::STATE_CLIENT:
+ return handleTimeoutClient();
+ case ICommonClock::STATE_MASTER:
+ return handleTimeoutMaster();
+ case ICommonClock::STATE_RONIN:
+ return handleTimeoutRonin();
+ case ICommonClock::STATE_WAIT_FOR_ELECTION:
+ return handleTimeoutWaitForElection();
+ }
+
+ return false;
+}
+
+bool CommonTimeServer::handleTimeoutInitial() {
+ if (++mInitial_WhoIsMasterRequestTimeouts ==
+ kInitial_NumWhoIsMasterRetries) {
+ // none of our attempts to discover a master succeeded, so make
+ // this device the master
+ return becomeMaster("initial timeout");
+ } else {
+ // retry the WhoIsMaster request
+ return sendWhoIsMasterRequest();
+ }
+}
+
+bool CommonTimeServer::handleTimeoutClient() {
+ if (shouldPanicNotGettingGoodData())
+ return becomeInitial("timeout panic, no good data");
+
+ if (mClient_SyncRequestPending) {
+ mClient_SyncRequestPending = false;
+
+ if (++mClient_SyncRequestTimeouts < kClient_NumSyncRequestRetries) {
+ // a sync request has timed out, so retry
+ return sendSyncRequest();
+ } else {
+ // The master has failed to respond to a sync request for too many
+ // times in a row. Assume the master is dead and start electing
+ // a new master.
+ return becomeRonin("master not responding");
+ }
+ } else {
+ // initiate the next sync request
+ return sendSyncRequest();
+ }
+}
+
+bool CommonTimeServer::handleTimeoutMaster() {
+ // send another announcement from the master
+ return sendMasterAnnouncement();
+}
+
+bool CommonTimeServer::handleTimeoutRonin() {
+ if (++mRonin_WhoIsMasterRequestTimeouts == kRonin_NumWhoIsMasterRetries) {
+ // no other master is out there, so we won the election
+ return becomeMaster("no better masters detected");
+ } else {
+ return sendWhoIsMasterRequest();
+ }
+}
+
+bool CommonTimeServer::handleTimeoutWaitForElection() {
+ return becomeRonin("timeout waiting for election conclusion");
+}
+
+bool CommonTimeServer::handleWhoIsMasterRequest(
+ const WhoIsMasterRequestPacket* request,
+ const sockaddr_storage& srcAddr) {
+ // Skip our own messages which come back via broadcast loopback.
+ if (request->senderDeviceID == mDeviceID)
+ return true;
+
+ char srcEPStr[64];
+ sockaddrToString(srcAddr, true, srcEPStr, sizeof(srcEPStr));
+ mElectionLog.log("RXed WhoIs master request while in state %s. "
+ "src %s reqTID %016llx ourTID %016llx",
+ stateToString(mState), srcEPStr,
+ request->timelineID, mTimelineID);
+
+ if (mState == ICommonClock::STATE_MASTER) {
+ // is this request related to this master's timeline?
+ if (request->timelineID != ICommonClock::kInvalidTimelineID &&
+ request->timelineID != mTimelineID)
+ return true;
+
+ WhoIsMasterResponsePacket pkt;
+ pkt.initHeader(mTimelineID, mSyncGroupID);
+ pkt.deviceID = mDeviceID;
+ pkt.devicePriority = effectivePriority();
+
+ mElectionLog.log("TXing WhoIs master resp to %s while in state %s. "
+ "ourTID %016llx ourGID %016llx ourDID %016llx "
+ "ourPrio %u",
+ srcEPStr, stateToString(mState),
+ mTimelineID, mSyncGroupID,
+ pkt.deviceID, pkt.devicePriority);
+
+ uint8_t buf[256];
+ ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+ if (bufSz < 0)
+ return false;
+
+ ssize_t sendBytes = sendto(
+ mSocket, buf, bufSz, 0,
+ reinterpret_cast<const sockaddr *>(&srcAddr),
+ sizeof(srcAddr));
+ if (sendBytes == -1) {
+ ALOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+ } else if (mState == ICommonClock::STATE_RONIN) {
+ // if we hear a WhoIsMaster request from another device following
+ // the same timeline and that device wins arbitration, then we will stop
+ // trying to elect ourselves master and will instead wait for an
+ // announcement from the election winner
+ if (request->timelineID != mTimelineID)
+ return true;
+
+ if (arbitrateMaster(request->senderDeviceID,
+ request->senderDevicePriority,
+ mDeviceID,
+ effectivePriority()))
+ return becomeWaitForElection("would lose election");
+
+ return true;
+ } else if (mState == ICommonClock::STATE_INITIAL) {
+ // If a group of devices booted simultaneously (e.g. after a power
+ // outage) and all of them are in the initial state and there is no
+ // master, then each device may time out and declare itself master at
+ // the same time. To avoid this, listen for
+ // WhoIsMaster(InvalidTimeline) requests from peers. If we would lose
+ // arbitration against that peer, reset our timeout count so that the
+ // peer has a chance to become master before we time out.
+ if (request->timelineID == ICommonClock::kInvalidTimelineID &&
+ arbitrateMaster(request->senderDeviceID,
+ request->senderDevicePriority,
+ mDeviceID,
+ effectivePriority())) {
+ mInitial_WhoIsMasterRequestTimeouts = 0;
+ }
+ }
+
+ return true;
+}
+
+bool CommonTimeServer::handleWhoIsMasterResponse(
+ const WhoIsMasterResponsePacket* response,
+ const sockaddr_storage& srcAddr) {
+ // Skip our own messages which come back via broadcast loopback.
+ if (response->deviceID == mDeviceID)
+ return true;
+
+ char srcEPStr[64];
+ sockaddrToString(srcAddr, true, srcEPStr, sizeof(srcEPStr));
+ mElectionLog.log("RXed WhoIs master response while in state %s. "
+ "src %s respTID %016llx respDID %016llx respPrio %u "
+ "ourTID %016llx",
+ stateToString(mState), srcEPStr,
+ response->timelineID,
+ response->deviceID,
+ static_cast<uint32_t>(response->devicePriority),
+ mTimelineID);
+
+ if (mState == ICommonClock::STATE_INITIAL || mState == ICommonClock::STATE_RONIN) {
+ return becomeClient(srcAddr,
+ response->deviceID,
+ response->devicePriority,
+ response->timelineID,
+ "heard whois response");
+ } else if (mState == ICommonClock::STATE_CLIENT) {
+ // if we get multiple responses because there are multiple devices
+ // who believe that they are master, then follow the master that
+ // wins arbitration
+ if (arbitrateMaster(response->deviceID,
+ response->devicePriority,
+ mClient_MasterDeviceID,
+ mClient_MasterDevicePriority)) {
+ return becomeClient(srcAddr,
+ response->deviceID,
+ response->devicePriority,
+ response->timelineID,
+ "heard whois response");
+ }
+ }
+
+ return true;
+}
+
+bool CommonTimeServer::handleSyncRequest(const SyncRequestPacket* request,
+ const sockaddr_storage& srcAddr) {
+ SyncResponsePacket pkt;
+ pkt.initHeader(mTimelineID, mSyncGroupID);
+
+ if ((mState == ICommonClock::STATE_MASTER) &&
+ (mTimelineID == request->timelineID)) {
+ int64_t rxLocalTime = mLastPacketRxLocalTime;
+ int64_t rxCommonTime;
+
+ // If we are master on an actual network and have actual clients, then
+ // we are no longer low priority.
+ setForceLowPriority(false);
+
+ if (OK != mCommonClock.localToCommon(rxLocalTime, &rxCommonTime)) {
+ return false;
+ }
+
+ int64_t txLocalTime = mLocalClock.getLocalTime();;
+ int64_t txCommonTime;
+ if (OK != mCommonClock.localToCommon(txLocalTime, &txCommonTime)) {
+ return false;
+ }
+
+ pkt.nak = 0;
+ pkt.clientTxLocalTime = request->clientTxLocalTime;
+ pkt.masterRxCommonTime = rxCommonTime;
+ pkt.masterTxCommonTime = txCommonTime;
+ } else {
+ pkt.nak = 1;
+ pkt.clientTxLocalTime = 0;
+ pkt.masterRxCommonTime = 0;
+ pkt.masterTxCommonTime = 0;
+ }
+
+ uint8_t buf[256];
+ ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+ if (bufSz < 0)
+ return false;
+
+ ssize_t sendBytes = sendto(
+ mSocket, &buf, bufSz, 0,
+ reinterpret_cast<const sockaddr *>(&srcAddr),
+ sizeof(srcAddr));
+ if (sendBytes == -1) {
+ ALOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+ return false;
+ }
+
+ return true;
+}
+
+bool CommonTimeServer::handleSyncResponse(
+ const SyncResponsePacket* response,
+ const sockaddr_storage& srcAddr) {
+ if (mState != ICommonClock::STATE_CLIENT)
+ return true;
+
+ assert(mMasterEPValid);
+ if (!sockaddrMatch(srcAddr, mMasterEP, true)) {
+ char srcEP[64], expectedEP[64];
+ sockaddrToString(srcAddr, true, srcEP, sizeof(srcEP));
+ sockaddrToString(mMasterEP, true, expectedEP, sizeof(expectedEP));
+ ALOGI("Dropping sync response from unexpected address."
+ " Expected %s Got %s", expectedEP, srcEP);
+ return true;
+ }
+
+ if (response->nak) {
+ // if our master is no longer accepting requests, then we need to find
+ // a new master
+ return becomeRonin("master NAK'ed");
+ }
+
+ mClient_SyncRequestPending = 0;
+ mClient_SyncRequestTimeouts = 0;
+ mClient_PacketRTTLog.logRX(response->clientTxLocalTime,
+ mLastPacketRxLocalTime);
+
+ bool result;
+ if (!(mClient_SyncRespsRXedFromCurMaster++)) {
+ // the first request/response exchange between a client and a master
+ // may take unusually long due to ARP, so discard it.
+ result = true;
+ } else {
+ int64_t clientTxLocalTime = response->clientTxLocalTime;
+ int64_t clientRxLocalTime = mLastPacketRxLocalTime;
+ int64_t masterTxCommonTime = response->masterTxCommonTime;
+ int64_t masterRxCommonTime = response->masterRxCommonTime;
+
+ int64_t rtt = (clientRxLocalTime - clientTxLocalTime);
+ int64_t avgLocal = (clientTxLocalTime + clientRxLocalTime) >> 1;
+ int64_t avgCommon = (masterTxCommonTime + masterRxCommonTime) >> 1;
+
+ // if the RTT of the packet is significantly larger than the panic
+ // threshold, we should simply discard it. Its better to do nothing
+ // than to take cues from a packet like that.
+ int rttCommon = mCommonClock.localDurationToCommonDuration(rtt);
+ if (rttCommon > (static_cast<int64_t>(mPanicThresholdUsec) *
+ kRTTDiscardPanicThreshMultiplier)) {
+ ALOGV("Dropping sync response with RTT of %lld uSec", rttCommon);
+ mClient_ExpiredSyncRespsRXedFromCurMaster++;
+ if (shouldPanicNotGettingGoodData())
+ return becomeInitial("RX panic, no good data");
+ } else {
+ result = mClockRecovery.pushDisciplineEvent(avgLocal, avgCommon, rttCommon);
+ mClient_LastGoodSyncRX = clientRxLocalTime;
+
+ if (result) {
+ // indicate to listeners that we've synced to the common timeline
+ notifyClockSync();
+ } else {
+ ALOGE("Panic! Observed clock sync error is too high to tolerate,"
+ " resetting state machine and starting over.");
+ notifyClockSyncLoss();
+ return becomeInitial("panic");
+ }
+ }
+ }
+
+ mCurTimeout.setTimeout(mSyncRequestIntervalMs);
+ return result;
+}
+
+bool CommonTimeServer::handleMasterAnnouncement(
+ const MasterAnnouncementPacket* packet,
+ const sockaddr_storage& srcAddr) {
+ uint64_t newDeviceID = packet->deviceID;
+ uint8_t newDevicePrio = packet->devicePriority;
+ uint64_t newTimelineID = packet->timelineID;
+
+ // Skip our own messages which come back via broadcast loopback.
+ if (newDeviceID == mDeviceID)
+ return true;
+
+ char srcEPStr[64];
+ sockaddrToString(srcAddr, true, srcEPStr, sizeof(srcEPStr));
+ mElectionLog.log("RXed master announcement while in state %s. "
+ "src %s srcDevID %lld srcPrio %u srcTID %016llx",
+ stateToString(mState), srcEPStr,
+ newDeviceID, static_cast<uint32_t>(newDevicePrio),
+ newTimelineID);
+
+ if (mState == ICommonClock::STATE_INITIAL ||
+ mState == ICommonClock::STATE_RONIN ||
+ mState == ICommonClock::STATE_WAIT_FOR_ELECTION) {
+ // if we aren't currently following a master, then start following
+ // this new master
+ return becomeClient(srcAddr,
+ newDeviceID,
+ newDevicePrio,
+ newTimelineID,
+ "heard master announcement");
+ } else if (mState == ICommonClock::STATE_CLIENT) {
+ // if the new master wins arbitration against our current master,
+ // then become a client of the new master
+ if (arbitrateMaster(newDeviceID,
+ newDevicePrio,
+ mClient_MasterDeviceID,
+ mClient_MasterDevicePriority))
+ return becomeClient(srcAddr,
+ newDeviceID,
+ newDevicePrio,
+ newTimelineID,
+ "heard master announcement");
+ } else if (mState == ICommonClock::STATE_MASTER) {
+ // two masters are competing - if the new one wins arbitration, then
+ // cease acting as master
+ if (arbitrateMaster(newDeviceID, newDevicePrio,
+ mDeviceID, effectivePriority()))
+ return becomeClient(srcAddr, newDeviceID,
+ newDevicePrio, newTimelineID,
+ "heard master announcement");
+ }
+
+ return true;
+}
+
+bool CommonTimeServer::sendWhoIsMasterRequest() {
+ assert(mState == ICommonClock::STATE_INITIAL || mState == ICommonClock::STATE_RONIN);
+
+ // If we have no socket, then we must be in the unconfigured initial state.
+ // Don't report any errors, just don't try to send the initial who-is-master
+ // query. Eventually, our network will either become configured, or we will
+ // be forced into network-less master mode by higher level code.
+ if (mSocket < 0) {
+ assert(mState == ICommonClock::STATE_INITIAL);
+ return true;
+ }
+
+ bool ret = false;
+ WhoIsMasterRequestPacket pkt;
+ pkt.initHeader(mSyncGroupID);
+ pkt.senderDeviceID = mDeviceID;
+ pkt.senderDevicePriority = effectivePriority();
+
+ uint8_t buf[256];
+ ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+ if (bufSz >= 0) {
+ char dstEPStr[64];
+ sockaddrToString(mMasterElectionEP, true, dstEPStr, sizeof(dstEPStr));
+ mElectionLog.log("TXing WhoIs master request to %s while in state %s. "
+ "ourTID %016llx ourGID %016llx ourDID %016llx "
+ "ourPrio %u",
+ dstEPStr, stateToString(mState),
+ mTimelineID, mSyncGroupID,
+ pkt.senderDeviceID, pkt.senderDevicePriority);
+
+ ssize_t sendBytes = sendto(
+ mSocket, buf, bufSz, 0,
+ reinterpret_cast<const sockaddr *>(&mMasterElectionEP),
+ sizeof(mMasterElectionEP));
+ if (sendBytes < 0)
+ ALOGE("WhoIsMaster sendto failed (errno %d)", errno);
+ ret = true;
+ }
+
+ if (mState == ICommonClock::STATE_INITIAL) {
+ mCurTimeout.setTimeout(kInitial_WhoIsMasterTimeoutMs);
+ } else {
+ mCurTimeout.setTimeout(kRonin_WhoIsMasterTimeoutMs);
+ }
+
+ return ret;
+}
+
+bool CommonTimeServer::sendSyncRequest() {
+ // If we are sending sync requests, then we must be in the client state and
+ // we must have a socket (when we have no network, we are only supposed to
+ // be in INITIAL or MASTER)
+ assert(mState == ICommonClock::STATE_CLIENT);
+ assert(mSocket >= 0);
+
+ bool ret = false;
+ SyncRequestPacket pkt;
+ pkt.initHeader(mTimelineID, mSyncGroupID);
+ pkt.clientTxLocalTime = mLocalClock.getLocalTime();
+
+ if (!mClient_FirstSyncTX)
+ mClient_FirstSyncTX = pkt.clientTxLocalTime;
+
+ mClient_PacketRTTLog.logTX(pkt.clientTxLocalTime);
+
+ uint8_t buf[256];
+ ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+ if (bufSz >= 0) {
+ ssize_t sendBytes = sendto(
+ mSocket, buf, bufSz, 0,
+ reinterpret_cast<const sockaddr *>(&mMasterEP),
+ sizeof(mMasterEP));
+ if (sendBytes < 0)
+ ALOGE("SyncRequest sendto failed (errno %d)", errno);
+ ret = true;
+ }
+
+ mClient_SyncsSentToCurMaster++;
+ mCurTimeout.setTimeout(mSyncRequestIntervalMs);
+ mClient_SyncRequestPending = true;
+
+ return ret;
+}
+
+bool CommonTimeServer::sendMasterAnnouncement() {
+ bool ret = false;
+ assert(mState == ICommonClock::STATE_MASTER);
+
+ // If we are being asked to send a master announcement, but we have no
+ // socket, we must be in network-less master mode. Don't bother to send the
+ // announcement, and don't bother to schedule a timeout. When the network
+ // comes up, the work thread will get poked and start the process of
+ // figuring out who the current master should be.
+ if (mSocket < 0) {
+ mCurTimeout.setTimeout(kInfiniteTimeout);
+ return true;
+ }
+
+ MasterAnnouncementPacket pkt;
+ pkt.initHeader(mTimelineID, mSyncGroupID);
+ pkt.deviceID = mDeviceID;
+ pkt.devicePriority = effectivePriority();
+
+ uint8_t buf[256];
+ ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+ if (bufSz >= 0) {
+ char dstEPStr[64];
+ sockaddrToString(mMasterElectionEP, true, dstEPStr, sizeof(dstEPStr));
+ mElectionLog.log("TXing Master announcement to %s while in state %s. "
+ "ourTID %016llx ourGID %016llx ourDID %016llx "
+ "ourPrio %u",
+ dstEPStr, stateToString(mState),
+ mTimelineID, mSyncGroupID,
+ pkt.deviceID, pkt.devicePriority);
+
+ ssize_t sendBytes = sendto(
+ mSocket, buf, bufSz, 0,
+ reinterpret_cast<const sockaddr *>(&mMasterElectionEP),
+ sizeof(mMasterElectionEP));
+ if (sendBytes < 0)
+ ALOGE("MasterAnnouncement sendto failed (errno %d)", errno);
+ ret = true;
+ }
+
+ mCurTimeout.setTimeout(mMasterAnnounceIntervalMs);
+ return ret;
+}
+
+bool CommonTimeServer::becomeClient(const sockaddr_storage& masterEP,
+ uint64_t masterDeviceID,
+ uint8_t masterDevicePriority,
+ uint64_t timelineID,
+ const char* cause) {
+ char newEPStr[64], oldEPStr[64];
+ sockaddrToString(masterEP, true, newEPStr, sizeof(newEPStr));
+ sockaddrToString(mMasterEP, mMasterEPValid, oldEPStr, sizeof(oldEPStr));
+
+ mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG,
+ "%s --> CLIENT (%s) :%s"
+ " OldMaster: %02x-%014llx::%016llx::%s"
+ " NewMaster: %02x-%014llx::%016llx::%s",
+ stateToString(mState), cause,
+ (mTimelineID != timelineID) ? " (new timeline)" : "",
+ mClient_MasterDevicePriority, mClient_MasterDeviceID,
+ mTimelineID, oldEPStr,
+ masterDevicePriority, masterDeviceID,
+ timelineID, newEPStr);
+
+ if (mTimelineID != timelineID) {
+ // start following a new timeline
+ mTimelineID = timelineID;
+ mClockRecovery.reset(true, true);
+ notifyClockSyncLoss();
+ } else {
+ // start following a new master on the existing timeline
+ mClockRecovery.reset(false, true);
+ }
+
+ mMasterEP = masterEP;
+ mMasterEPValid = true;
+
+ // If we are on a real network as a client of a real master, then we should
+ // no longer force low priority. If our master disappears, we should have
+ // the high priority bit set during the election to replace the master
+ // because this group was a real group and not a singleton created in
+ // networkless mode.
+ setForceLowPriority(false);
+
+ mClient_MasterDeviceID = masterDeviceID;
+ mClient_MasterDevicePriority = masterDevicePriority;
+ resetSyncStats();
+
+ setState(ICommonClock::STATE_CLIENT);
+
+ // add some jitter to when the various clients send their requests
+ // in order to reduce the likelihood that a group of clients overload
+ // the master after receiving a master announcement
+ usleep((lrand48() % 100) * 1000);
+
+ return sendSyncRequest();
+}
+
+bool CommonTimeServer::becomeMaster(const char* cause) {
+ uint64_t oldTimelineID = mTimelineID;
+ if (mTimelineID == ICommonClock::kInvalidTimelineID) {
+ // this device has not been following any existing timeline,
+ // so it will create a new timeline and declare itself master
+ assert(!mCommonClock.isValid());
+
+ // set the common time basis
+ mCommonClock.setBasis(mLocalClock.getLocalTime(), 0);
+
+ // assign an arbitrary timeline iD
+ assignTimelineID();
+
+ // notify listeners that we've created a common timeline
+ notifyClockSync();
+ }
+
+ mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG,
+ "%s --> MASTER (%s) : %s timeline %016llx",
+ stateToString(mState), cause,
+ (oldTimelineID == mTimelineID) ? "taking ownership of"
+ : "creating new",
+ mTimelineID);
+
+ memset(&mMasterEP, 0, sizeof(mMasterEP));
+ mMasterEPValid = false;
+ mClient_MasterDevicePriority = effectivePriority();
+ mClient_MasterDeviceID = mDeviceID;
+ mClockRecovery.reset(false, true);
+ resetSyncStats();
+
+ setState(ICommonClock::STATE_MASTER);
+ return sendMasterAnnouncement();
+}
+
+bool CommonTimeServer::becomeRonin(const char* cause) {
+ // If we were the client of a given timeline, but had never received even a
+ // single time sync packet, then we transition back to Initial instead of
+ // Ronin. If we transition to Ronin and end up becoming the new Master, we
+ // will be unable to service requests for other clients because we never
+ // actually knew what time it was. By going to initial, we ensure that
+ // other clients who know what time it is, but would lose master arbitration
+ // in the Ronin case, will step up and become the proper new master of the
+ // old timeline.
+
+ char oldEPStr[64];
+ sockaddrToString(mMasterEP, mMasterEPValid, oldEPStr, sizeof(oldEPStr));
+ memset(&mMasterEP, 0, sizeof(mMasterEP));
+ mMasterEPValid = false;
+
+ if (mCommonClock.isValid()) {
+ mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG,
+ "%s --> RONIN (%s) : lost track of previously valid timeline "
+ "%02x-%014llx::%016llx::%s (%d TXed %d RXed %d RXExpired)",
+ stateToString(mState), cause,
+ mClient_MasterDevicePriority, mClient_MasterDeviceID,
+ mTimelineID, oldEPStr,
+ mClient_SyncsSentToCurMaster,
+ mClient_SyncRespsRXedFromCurMaster,
+ mClient_ExpiredSyncRespsRXedFromCurMaster);
+
+ mRonin_WhoIsMasterRequestTimeouts = 0;
+ setState(ICommonClock::STATE_RONIN);
+ return sendWhoIsMasterRequest();
+ } else {
+ mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG,
+ "%s --> INITIAL (%s) : never synced timeline "
+ "%02x-%014llx::%016llx::%s (%d TXed %d RXed %d RXExpired)",
+ stateToString(mState), cause,
+ mClient_MasterDevicePriority, mClient_MasterDeviceID,
+ mTimelineID, oldEPStr,
+ mClient_SyncsSentToCurMaster,
+ mClient_SyncRespsRXedFromCurMaster,
+ mClient_ExpiredSyncRespsRXedFromCurMaster);
+
+ return becomeInitial("ronin, no timeline");
+ }
+}
+
+bool CommonTimeServer::becomeWaitForElection(const char* cause) {
+ mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG,
+ "%s --> WAIT_FOR_ELECTION (%s) : dropping out of election,"
+ " waiting %d mSec for completion.",
+ stateToString(mState), cause, kWaitForElection_TimeoutMs);
+
+ setState(ICommonClock::STATE_WAIT_FOR_ELECTION);
+ mCurTimeout.setTimeout(kWaitForElection_TimeoutMs);
+ return true;
+}
+
+bool CommonTimeServer::becomeInitial(const char* cause) {
+ mStateChangeLog.log(ANDROID_LOG_INFO, LOG_TAG,
+ "Entering INITIAL (%s), total reset.",
+ cause);
+
+ setState(ICommonClock::STATE_INITIAL);
+
+ // reset clock recovery
+ mClockRecovery.reset(true, true);
+
+ // reset internal state bookkeeping.
+ mCurTimeout.setTimeout(kInfiniteTimeout);
+ memset(&mMasterEP, 0, sizeof(mMasterEP));
+ mMasterEPValid = false;
+ mLastPacketRxLocalTime = 0;
+ mTimelineID = ICommonClock::kInvalidTimelineID;
+ mClockSynced = false;
+ mInitial_WhoIsMasterRequestTimeouts = 0;
+ mClient_MasterDeviceID = 0;
+ mClient_MasterDevicePriority = 0;
+ mRonin_WhoIsMasterRequestTimeouts = 0;
+ resetSyncStats();
+
+ // send the first request to discover the master
+ return sendWhoIsMasterRequest();
+}
+
+void CommonTimeServer::notifyClockSync() {
+ if (!mClockSynced) {
+ mClockSynced = true;
+ mICommonClock->notifyOnTimelineChanged(mTimelineID);
+ }
+}
+
+void CommonTimeServer::notifyClockSyncLoss() {
+ if (mClockSynced) {
+ mClockSynced = false;
+ mICommonClock->notifyOnTimelineChanged(
+ ICommonClock::kInvalidTimelineID);
+ }
+}
+
+void CommonTimeServer::setState(ICommonClock::State s) {
+ mState = s;
+}
+
+const char* CommonTimeServer::stateToString(ICommonClock::State s) {
+ switch(s) {
+ case ICommonClock::STATE_INITIAL:
+ return "INITIAL";
+ case ICommonClock::STATE_CLIENT:
+ return "CLIENT";
+ case ICommonClock::STATE_MASTER:
+ return "MASTER";
+ case ICommonClock::STATE_RONIN:
+ return "RONIN";
+ case ICommonClock::STATE_WAIT_FOR_ELECTION:
+ return "WAIT_FOR_ELECTION";
+ default:
+ return "unknown";
+ }
+}
+
+void CommonTimeServer::sockaddrToString(const sockaddr_storage& addr,
+ bool addrValid,
+ char* buf, size_t bufLen) {
+ if (!bufLen || !buf)
+ return;
+
+ if (addrValid) {
+ switch (addr.ss_family) {
+ case AF_INET: {
+ const struct sockaddr_in* sa =
+ reinterpret_cast<const struct sockaddr_in*>(&addr);
+ unsigned long a = ntohl(sa->sin_addr.s_addr);
+ uint16_t p = ntohs(sa->sin_port);
+ snprintf(buf, bufLen, "%lu.%lu.%lu.%lu:%hu",
+ ((a >> 24) & 0xFF), ((a >> 16) & 0xFF),
+ ((a >> 8) & 0xFF), (a & 0xFF), p);
+ } break;
+
+ case AF_INET6: {
+ const struct sockaddr_in6* sa =
+ reinterpret_cast<const struct sockaddr_in6*>(&addr);
+ const uint8_t* a = sa->sin6_addr.s6_addr;
+ uint16_t p = ntohs(sa->sin6_port);
+ snprintf(buf, bufLen,
+ "%02X%02X:%02X%02X:%02X%02X:%02X%02X:"
+ "%02X%02X:%02X%02X:%02X%02X:%02X%02X port %hd",
+ a[0], a[1], a[ 2], a[ 3], a[ 4], a[ 5], a[ 6], a[ 7],
+ a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15],
+ p);
+ } break;
+
+ default:
+ snprintf(buf, bufLen,
+ "<unknown sockaddr family %d>", addr.ss_family);
+ break;
+ }
+ } else {
+ snprintf(buf, bufLen, "<none>");
+ }
+
+ buf[bufLen - 1] = 0;
+}
+
+bool CommonTimeServer::sockaddrMatch(const sockaddr_storage& a1,
+ const sockaddr_storage& a2,
+ bool matchAddressOnly) {
+ if (a1.ss_family != a2.ss_family)
+ return false;
+
+ switch (a1.ss_family) {
+ case AF_INET: {
+ const struct sockaddr_in* sa1 =
+ reinterpret_cast<const struct sockaddr_in*>(&a1);
+ const struct sockaddr_in* sa2 =
+ reinterpret_cast<const struct sockaddr_in*>(&a2);
+
+ if (sa1->sin_addr.s_addr != sa2->sin_addr.s_addr)
+ return false;
+
+ return (matchAddressOnly || (sa1->sin_port == sa2->sin_port));
+ } break;
+
+ case AF_INET6: {
+ const struct sockaddr_in6* sa1 =
+ reinterpret_cast<const struct sockaddr_in6*>(&a1);
+ const struct sockaddr_in6* sa2 =
+ reinterpret_cast<const struct sockaddr_in6*>(&a2);
+
+ if (memcmp(&sa1->sin6_addr, &sa2->sin6_addr, sizeof(sa2->sin6_addr)))
+ return false;
+
+ return (matchAddressOnly || (sa1->sin6_port == sa2->sin6_port));
+ } break;
+
+ // Huh? We don't deal in non-IPv[46] addresses. Not sure how we got
+ // here, but we don't know how to comapre these addresses and simply
+ // default to a no-match decision.
+ default: return false;
+ }
+}
+
+bool CommonTimeServer::shouldPanicNotGettingGoodData() {
+ if (mClient_FirstSyncTX) {
+ int64_t now = mLocalClock.getLocalTime();
+ int64_t delta = now - (mClient_LastGoodSyncRX
+ ? mClient_LastGoodSyncRX
+ : mClient_FirstSyncTX);
+ int64_t deltaUsec = mCommonClock.localDurationToCommonDuration(delta);
+
+ if (deltaUsec >= kNoGoodDataPanicThresholdUsec)
+ return true;
+ }
+
+ return false;
+}
+
+void CommonTimeServer::PacketRTTLog::logTX(int64_t txTime) {
+ txTimes[wrPtr] = txTime;
+ rxTimes[wrPtr] = 0;
+ wrPtr = (wrPtr + 1) % RTT_LOG_SIZE;
+ if (!wrPtr)
+ logFull = true;
+}
+
+void CommonTimeServer::PacketRTTLog::logRX(int64_t txTime, int64_t rxTime) {
+ if (!logFull && !wrPtr)
+ return;
+
+ uint32_t i = logFull ? wrPtr : 0;
+ do {
+ if (txTimes[i] == txTime) {
+ rxTimes[i] = rxTime;
+ break;
+ }
+ i = (i + 1) % RTT_LOG_SIZE;
+ } while (i != wrPtr);
+}
+
+} // namespace android
diff --git a/libs/common_time/common_time_server.h b/libs/common_time/common_time_server.h
new file mode 100644
index 0000000..6e18050
--- /dev/null
+++ b/libs/common_time/common_time_server.h
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_COMMON_TIME_SERVER_H
+#define ANDROID_COMMON_TIME_SERVER_H
+
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <sys/socket.h>
+
+#include <common_time/ICommonClock.h>
+#include <common_time/local_clock.h>
+#include <utils/String8.h>
+
+#include "clock_recovery.h"
+#include "common_clock.h"
+#include "common_time_server_packets.h"
+#include "utils.h"
+
+#define RTT_LOG_SIZE 30
+
+namespace android {
+
+class CommonClockService;
+class CommonTimeConfigService;
+
+/***** time service implementation *****/
+
+class CommonTimeServer : public Thread {
+ public:
+ CommonTimeServer();
+ ~CommonTimeServer();
+
+ bool startServices();
+
+ // Common Clock API methods
+ CommonClock& getCommonClock() { return mCommonClock; }
+ LocalClock& getLocalClock() { return mLocalClock; }
+ uint64_t getTimelineID();
+ int32_t getEstimatedError();
+ ICommonClock::State getState();
+ status_t getMasterAddr(struct sockaddr_storage* addr);
+ status_t isCommonTimeValid(bool* valid, uint32_t* timelineID);
+
+ // Config API methods
+ status_t getMasterElectionPriority(uint8_t *priority);
+ status_t setMasterElectionPriority(uint8_t priority);
+ status_t getMasterElectionEndpoint(struct sockaddr_storage *addr);
+ status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr);
+ status_t getMasterElectionGroupId(uint64_t *id);
+ status_t setMasterElectionGroupId(uint64_t id);
+ status_t getInterfaceBinding(String8& ifaceName);
+ status_t setInterfaceBinding(const String8& ifaceName);
+ status_t getMasterAnnounceInterval(int *interval);
+ status_t setMasterAnnounceInterval(int interval);
+ status_t getClientSyncInterval(int *interval);
+ status_t setClientSyncInterval(int interval);
+ status_t getPanicThreshold(int *threshold);
+ status_t setPanicThreshold(int threshold);
+ status_t getAutoDisable(bool *autoDisable);
+ status_t setAutoDisable(bool autoDisable);
+ status_t forceNetworklessMasterMode();
+
+ // Method used by the CommonClockService to notify the core service about
+ // changes in the number of active common clock clients.
+ void reevaluateAutoDisableState(bool commonClockHasClients);
+
+ status_t dumpClockInterface(int fd, const Vector<String16>& args,
+ size_t activeClients);
+ status_t dumpConfigInterface(int fd, const Vector<String16>& args);
+
+ private:
+ class PacketRTTLog {
+ public:
+ PacketRTTLog() {
+ resetLog();
+ }
+
+ void resetLog() {
+ wrPtr = 0;
+ logFull = 0;
+ }
+
+ void logTX(int64_t txTime);
+ void logRX(int64_t txTime, int64_t rxTime);
+ void dumpLog(int fd, const CommonClock& cclk);
+
+ private:
+ uint32_t wrPtr;
+ bool logFull;
+ int64_t txTimes[RTT_LOG_SIZE];
+ int64_t rxTimes[RTT_LOG_SIZE];
+ };
+
+ bool threadLoop();
+
+ bool runStateMachine_l();
+ bool setupSocket_l();
+
+ void assignTimelineID();
+ bool assignDeviceID();
+
+ static bool arbitrateMaster(uint64_t deviceID1, uint8_t devicePrio1,
+ uint64_t deviceID2, uint8_t devicePrio2);
+
+ bool handlePacket();
+ bool handleWhoIsMasterRequest (const WhoIsMasterRequestPacket* request,
+ const sockaddr_storage& srcAddr);
+ bool handleWhoIsMasterResponse(const WhoIsMasterResponsePacket* response,
+ const sockaddr_storage& srcAddr);
+ bool handleSyncRequest (const SyncRequestPacket* request,
+ const sockaddr_storage& srcAddr);
+ bool handleSyncResponse (const SyncResponsePacket* response,
+ const sockaddr_storage& srcAddr);
+ bool handleMasterAnnouncement (const MasterAnnouncementPacket* packet,
+ const sockaddr_storage& srcAddr);
+
+ bool handleTimeout();
+ bool handleTimeoutInitial();
+ bool handleTimeoutClient();
+ bool handleTimeoutMaster();
+ bool handleTimeoutRonin();
+ bool handleTimeoutWaitForElection();
+
+ bool sendWhoIsMasterRequest();
+ bool sendSyncRequest();
+ bool sendMasterAnnouncement();
+
+ bool becomeClient(const sockaddr_storage& masterAddr,
+ uint64_t masterDeviceID,
+ uint8_t masterDevicePriority,
+ uint64_t timelineID,
+ const char* cause);
+ bool becomeMaster(const char* cause);
+ bool becomeRonin(const char* cause);
+ bool becomeWaitForElection(const char* cause);
+ bool becomeInitial(const char* cause);
+
+ void notifyClockSync();
+ void notifyClockSyncLoss();
+
+ ICommonClock::State mState;
+ void setState(ICommonClock::State s);
+
+ void clearPendingWakeupEvents_l();
+ void wakeupThread_l();
+ void cleanupSocket_l();
+ void shutdownThread();
+
+ inline uint8_t effectivePriority() const {
+ return (mMasterPriority & 0x7F) |
+ (mForceLowPriority ? 0x00 : 0x80);
+ }
+
+ inline bool shouldAutoDisable() const {
+ return (mAutoDisable && !mCommonClockHasClients);
+ }
+
+ inline void resetSyncStats() {
+ mClient_SyncRequestPending = false;
+ mClient_SyncRequestTimeouts = 0;
+ mClient_SyncsSentToCurMaster = 0;
+ mClient_SyncRespsRXedFromCurMaster = 0;
+ mClient_ExpiredSyncRespsRXedFromCurMaster = 0;
+ mClient_FirstSyncTX = 0;
+ mClient_LastGoodSyncRX = 0;
+ mClient_PacketRTTLog.resetLog();
+ }
+
+ bool shouldPanicNotGettingGoodData();
+
+ // Helper to keep track of the state machine's current timeout
+ Timeout mCurTimeout;
+
+ // common clock, local clock abstraction, and clock recovery loop
+ CommonClock mCommonClock;
+ LocalClock mLocalClock;
+ ClockRecoveryLoop mClockRecovery;
+
+ // implementation of ICommonClock
+ sp<CommonClockService> mICommonClock;
+
+ // implementation of ICommonTimeConfig
+ sp<CommonTimeConfigService> mICommonTimeConfig;
+
+ // UDP socket for the time sync protocol
+ int mSocket;
+
+ // eventfd used to wakeup the work thread in response to configuration
+ // changes.
+ int mWakeupThreadFD;
+
+ // timestamp captured when a packet is received
+ int64_t mLastPacketRxLocalTime;
+
+ // ID of the timeline that this device is following
+ uint64_t mTimelineID;
+
+ // flag for whether the clock has been synced to a timeline
+ bool mClockSynced;
+
+ // flag used to indicate that clients should be considered to be lower
+ // priority than all of their peers during elections. This flag is set and
+ // cleared by the state machine. It is set when the client joins a new
+ // network. If the client had been a master in the old network (or an
+ // isolated master with no network connectivity) it should defer to any
+ // masters which may already be on the network. It will be cleared whenever
+ // the state machine transitions to the master state.
+ bool mForceLowPriority;
+ inline void setForceLowPriority(bool val) {
+ mForceLowPriority = val;
+ if (mState == ICommonClock::STATE_MASTER)
+ mClient_MasterDevicePriority = effectivePriority();
+ }
+
+ // Lock to synchronize access to internal state and configuration.
+ Mutex mLock;
+
+ // Flag updated by the common clock service to indicate that it does or does
+ // not currently have registered clients. When the the auto disable flag is
+ // cleared on the common time service, the service will participate in
+ // network synchronization whenever it has a valid network interface to bind
+ // to. When the auto disable flag is set on the common time service, it
+ // will only participate in network synchronization when it has both a valid
+ // interface AND currently active common clock clients.
+ bool mCommonClockHasClients;
+
+ // Internal logs used for dumpsys.
+ LogRing mStateChangeLog;
+ LogRing mElectionLog;
+ LogRing mBadPktLog;
+
+ // Configuration info
+ struct sockaddr_storage mMasterElectionEP; // Endpoint over which we conduct master election
+ String8 mBindIface; // Endpoint for the service to bind to.
+ bool mBindIfaceValid; // whether or not the bind Iface is valid.
+ bool mBindIfaceDirty; // whether or not the bind Iface is valid.
+ struct sockaddr_storage mMasterEP; // Endpoint of our current master (if any)
+ bool mMasterEPValid;
+ uint64_t mDeviceID; // unique ID of this device
+ uint64_t mSyncGroupID; // synchronization group ID of this device.
+ uint8_t mMasterPriority; // Priority of this device in master election.
+ uint32_t mMasterAnnounceIntervalMs;
+ uint32_t mSyncRequestIntervalMs;
+ uint32_t mPanicThresholdUsec;
+ bool mAutoDisable;
+
+ // Config defaults.
+ static const char* kDefaultMasterElectionAddr;
+ static const uint16_t kDefaultMasterElectionPort;
+ static const uint64_t kDefaultSyncGroupID;
+ static const uint8_t kDefaultMasterPriority;
+ static const uint32_t kDefaultMasterAnnounceIntervalMs;
+ static const uint32_t kDefaultSyncRequestIntervalMs;
+ static const uint32_t kDefaultPanicThresholdUsec;
+ static const bool kDefaultAutoDisable;
+
+ // Priority mask and shift fields.
+ static const uint64_t kDeviceIDMask;
+ static const uint8_t kDevicePriorityMask;
+ static const uint8_t kDevicePriorityHiLowBit;
+ static const uint32_t kDevicePriorityShift;
+
+ // Unconfgurable constants
+ static const int kSetupRetryTimeoutMs;
+ static const int64_t kNoGoodDataPanicThresholdUsec;
+ static const uint32_t kRTTDiscardPanicThreshMultiplier;
+
+ /*** status while in the Initial state ***/
+ int mInitial_WhoIsMasterRequestTimeouts;
+ static const int kInitial_NumWhoIsMasterRetries;
+ static const int kInitial_WhoIsMasterTimeoutMs;
+
+ /*** status while in the Client state ***/
+ uint64_t mClient_MasterDeviceID;
+ uint8_t mClient_MasterDevicePriority;
+ bool mClient_SyncRequestPending;
+ int mClient_SyncRequestTimeouts;
+ uint32_t mClient_SyncsSentToCurMaster;
+ uint32_t mClient_SyncRespsRXedFromCurMaster;
+ uint32_t mClient_ExpiredSyncRespsRXedFromCurMaster;
+ int64_t mClient_FirstSyncTX;
+ int64_t mClient_LastGoodSyncRX;
+ PacketRTTLog mClient_PacketRTTLog;
+ static const int kClient_NumSyncRequestRetries;
+
+
+ /*** status while in the Master state ***/
+ static const uint32_t kDefaultMaster_AnnouncementIntervalMs;
+
+ /*** status while in the Ronin state ***/
+ int mRonin_WhoIsMasterRequestTimeouts;
+ static const int kRonin_NumWhoIsMasterRetries;
+ static const int kRonin_WhoIsMasterTimeoutMs;
+
+ /*** status while in the WaitForElection state ***/
+ static const int kWaitForElection_TimeoutMs;
+
+ static const int kInfiniteTimeout;
+
+ static const char* stateToString(ICommonClock::State s);
+ static void sockaddrToString(const sockaddr_storage& addr, bool addrValid,
+ char* buf, size_t bufLen);
+ static bool sockaddrMatch(const sockaddr_storage& a1,
+ const sockaddr_storage& a2,
+ bool matchAddressOnly);
+};
+
+} // namespace android
+
+#endif // ANDROID_COMMON_TIME_SERVER_H
diff --git a/libs/common_time/common_time_server_api.cpp b/libs/common_time/common_time_server_api.cpp
new file mode 100644
index 0000000..e157071
--- /dev/null
+++ b/libs/common_time/common_time_server_api.cpp
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <binder/IServiceManager.h>
+#include <binder/IPCThreadState.h>
+
+#include "common_time_server.h"
+
+namespace android {
+
+//
+// Clock API
+//
+uint64_t CommonTimeServer::getTimelineID() {
+ AutoMutex _lock(&mLock);
+ return mTimelineID;
+}
+
+ICommonClock::State CommonTimeServer::getState() {
+ AutoMutex _lock(&mLock);
+ return mState;
+}
+
+status_t CommonTimeServer::getMasterAddr(struct sockaddr_storage* addr) {
+ AutoMutex _lock(&mLock);
+ if (mMasterEPValid) {
+ memcpy(addr, &mMasterEP, sizeof(*addr));
+ return OK;
+ }
+
+ return UNKNOWN_ERROR;
+}
+
+int32_t CommonTimeServer::getEstimatedError() {
+ AutoMutex _lock(&mLock);
+
+ if (ICommonClock::STATE_MASTER == mState)
+ return 0;
+
+ if (!mClockSynced)
+ return ICommonClock::kErrorEstimateUnknown;
+
+ return mClockRecovery.getLastErrorEstimate();
+}
+
+status_t CommonTimeServer::isCommonTimeValid(bool* valid,
+ uint32_t* timelineID) {
+ AutoMutex _lock(&mLock);
+ *valid = mCommonClock.isValid();
+ *timelineID = mTimelineID;
+ return OK;
+}
+
+//
+// Config API
+//
+status_t CommonTimeServer::getMasterElectionPriority(uint8_t *priority) {
+ AutoMutex _lock(&mLock);
+ *priority = mMasterPriority;
+ return OK;
+}
+
+status_t CommonTimeServer::setMasterElectionPriority(uint8_t priority) {
+ AutoMutex _lock(&mLock);
+
+ if (priority > 0x7F)
+ return BAD_VALUE;
+
+ mMasterPriority = priority;
+ return OK;
+}
+
+status_t CommonTimeServer::getMasterElectionEndpoint(
+ struct sockaddr_storage *addr) {
+ AutoMutex _lock(&mLock);
+ memcpy(addr, &mMasterElectionEP, sizeof(*addr));
+ return OK;
+}
+
+status_t CommonTimeServer::setMasterElectionEndpoint(
+ const struct sockaddr_storage *addr) {
+ AutoMutex _lock(&mLock);
+
+ if (!addr)
+ return BAD_VALUE;
+
+ // TODO: add proper support for IPv6
+ if (addr->ss_family != AF_INET)
+ return BAD_VALUE;
+
+ // Only multicast and broadcast endpoints with explicit ports are allowed.
+ uint16_t ipv4Port = ntohs(
+ reinterpret_cast<const struct sockaddr_in*>(addr)->sin_port);
+ if (!ipv4Port)
+ return BAD_VALUE;
+
+ uint32_t ipv4Addr = ntohl(
+ reinterpret_cast<const struct sockaddr_in*>(addr)->sin_addr.s_addr);
+ if ((ipv4Addr != 0xFFFFFFFF) && (0xE0000000 != (ipv4Addr & 0xF0000000)))
+ return BAD_VALUE;
+
+ memcpy(&mMasterElectionEP, addr, sizeof(mMasterElectionEP));
+
+ // Force a rebind in order to change election enpoints.
+ mBindIfaceDirty = true;
+ wakeupThread_l();
+ return OK;
+}
+
+status_t CommonTimeServer::getMasterElectionGroupId(uint64_t *id) {
+ AutoMutex _lock(&mLock);
+ *id = mSyncGroupID;
+ return OK;
+}
+
+status_t CommonTimeServer::setMasterElectionGroupId(uint64_t id) {
+ AutoMutex _lock(&mLock);
+ mSyncGroupID = id;
+ return OK;
+}
+
+status_t CommonTimeServer::getInterfaceBinding(String8& ifaceName) {
+ AutoMutex _lock(&mLock);
+ if (!mBindIfaceValid)
+ return INVALID_OPERATION;
+ ifaceName = mBindIface;
+ return OK;
+}
+
+status_t CommonTimeServer::setInterfaceBinding(const String8& ifaceName) {
+ AutoMutex _lock(&mLock);
+
+ mBindIfaceDirty = true;
+ if (ifaceName.size()) {
+ mBindIfaceValid = true;
+ mBindIface = ifaceName;
+ } else {
+ mBindIfaceValid = false;
+ mBindIface.clear();
+ }
+
+ wakeupThread_l();
+ return OK;
+}
+
+status_t CommonTimeServer::getMasterAnnounceInterval(int *interval) {
+ AutoMutex _lock(&mLock);
+ *interval = mMasterAnnounceIntervalMs;
+ return OK;
+}
+
+status_t CommonTimeServer::setMasterAnnounceInterval(int interval) {
+ AutoMutex _lock(&mLock);
+
+ if (interval > (6 *3600000)) // Max interval is once every 6 hrs
+ return BAD_VALUE;
+
+ if (interval < 500) // Min interval is once per 0.5 seconds
+ return BAD_VALUE;
+
+ mMasterAnnounceIntervalMs = interval;
+ if (ICommonClock::STATE_MASTER == mState) {
+ int pendingTimeout = mCurTimeout.msecTillTimeout();
+ if ((kInfiniteTimeout == pendingTimeout) ||
+ (pendingTimeout > interval)) {
+ mCurTimeout.setTimeout(mMasterAnnounceIntervalMs);
+ wakeupThread_l();
+ }
+ }
+
+ return OK;
+}
+
+status_t CommonTimeServer::getClientSyncInterval(int *interval) {
+ AutoMutex _lock(&mLock);
+ *interval = mSyncRequestIntervalMs;
+ return OK;
+}
+
+status_t CommonTimeServer::setClientSyncInterval(int interval) {
+ AutoMutex _lock(&mLock);
+
+ if (interval > (3600000)) // Max interval is once every 60 min
+ return BAD_VALUE;
+
+ if (interval < 250) // Min interval is once per 0.25 seconds
+ return BAD_VALUE;
+
+ mSyncRequestIntervalMs = interval;
+ if (ICommonClock::STATE_CLIENT == mState) {
+ int pendingTimeout = mCurTimeout.msecTillTimeout();
+ if ((kInfiniteTimeout == pendingTimeout) ||
+ (pendingTimeout > interval)) {
+ mCurTimeout.setTimeout(mSyncRequestIntervalMs);
+ wakeupThread_l();
+ }
+ }
+
+ return OK;
+}
+
+status_t CommonTimeServer::getPanicThreshold(int *threshold) {
+ AutoMutex _lock(&mLock);
+ *threshold = mPanicThresholdUsec;
+ return OK;
+}
+
+status_t CommonTimeServer::setPanicThreshold(int threshold) {
+ AutoMutex _lock(&mLock);
+
+ if (threshold < 1000) // Min threshold is 1mSec
+ return BAD_VALUE;
+
+ mPanicThresholdUsec = threshold;
+ return OK;
+}
+
+status_t CommonTimeServer::getAutoDisable(bool *autoDisable) {
+ AutoMutex _lock(&mLock);
+ *autoDisable = mAutoDisable;
+ return OK;
+}
+
+status_t CommonTimeServer::setAutoDisable(bool autoDisable) {
+ AutoMutex _lock(&mLock);
+ mAutoDisable = autoDisable;
+ wakeupThread_l();
+ return OK;
+}
+
+status_t CommonTimeServer::forceNetworklessMasterMode() {
+ AutoMutex _lock(&mLock);
+
+ // Can't force networkless master mode if we are currently bound to a
+ // network.
+ if (mSocket >= 0)
+ return INVALID_OPERATION;
+
+ becomeMaster("force networkless");
+
+ return OK;
+}
+
+void CommonTimeServer::reevaluateAutoDisableState(bool commonClockHasClients) {
+ AutoMutex _lock(&mLock);
+ bool needWakeup = (mAutoDisable && mMasterEPValid &&
+ (commonClockHasClients != mCommonClockHasClients));
+
+ mCommonClockHasClients = commonClockHasClients;
+
+ if (needWakeup) {
+ ALOGI("Waking up service, auto-disable is engaged and service now has%s"
+ " clients", mCommonClockHasClients ? "" : " no");
+ wakeupThread_l();
+ }
+}
+
+#define dump_printf(a, b...) do { \
+ int res; \
+ res = snprintf(buffer, sizeof(buffer), a, b); \
+ buffer[sizeof(buffer) - 1] = 0; \
+ if (res > 0) \
+ write(fd, buffer, res); \
+} while (0)
+#define checked_percentage(a, b) ((0 == b) ? 0.0f : ((100.0f * a) / b))
+
+status_t CommonTimeServer::dumpClockInterface(int fd,
+ const Vector<String16>& args,
+ size_t activeClients) {
+ AutoMutex _lock(&mLock);
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+
+ if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+ snprintf(buffer, SIZE, "Permission Denial: "
+ "can't dump CommonClockService from pid=%d, uid=%d\n",
+ IPCThreadState::self()->getCallingPid(),
+ IPCThreadState::self()->getCallingUid());
+ write(fd, buffer, strlen(buffer));
+ } else {
+ int64_t commonTime;
+ int64_t localTime;
+ bool synced;
+ char maStr[64];
+
+ localTime = mLocalClock.getLocalTime();
+ synced = (OK == mCommonClock.localToCommon(localTime, &commonTime));
+ sockaddrToString(mMasterEP, mMasterEPValid, maStr, sizeof(maStr));
+
+ dump_printf("Common Clock Service Status\nLocal time : %lld\n",
+ localTime);
+
+ if (synced)
+ dump_printf("Common time : %lld\n", commonTime);
+ else
+ dump_printf("Common time : %s\n", "not synced");
+
+ dump_printf("Timeline ID : %016llx\n", mTimelineID);
+ dump_printf("State : %s\n", stateToString(mState));
+ dump_printf("Master Addr : %s\n", maStr);
+
+
+ if (synced) {
+ int32_t est = (ICommonClock::STATE_MASTER != mState)
+ ? mClockRecovery.getLastErrorEstimate()
+ : 0;
+ dump_printf("Error Est. : %.3f msec\n",
+ static_cast<float>(est) / 1000.0);
+ } else {
+ dump_printf("Error Est. : %s\n", "unknown");
+ }
+
+ dump_printf("Syncs TXes : %u\n", mClient_SyncsSentToCurMaster);
+ dump_printf("Syncs RXes : %u (%.2f%%)\n",
+ mClient_SyncRespsRXedFromCurMaster,
+ checked_percentage(
+ mClient_SyncRespsRXedFromCurMaster,
+ mClient_SyncsSentToCurMaster));
+ dump_printf("RXs Expired : %u (%.2f%%)\n",
+ mClient_ExpiredSyncRespsRXedFromCurMaster,
+ checked_percentage(
+ mClient_ExpiredSyncRespsRXedFromCurMaster,
+ mClient_SyncsSentToCurMaster));
+
+ if (!mClient_LastGoodSyncRX) {
+ dump_printf("Last Good RX : %s\n", "unknown");
+ } else {
+ int64_t localDelta, usecDelta;
+ localDelta = localTime - mClient_LastGoodSyncRX;
+ usecDelta = mCommonClock.localDurationToCommonDuration(localDelta);
+ dump_printf("Last Good RX : %lld uSec ago\n", usecDelta);
+ }
+
+ dump_printf("Active Clients : %u\n", activeClients);
+ mClient_PacketRTTLog.dumpLog(fd, mCommonClock);
+ mStateChangeLog.dumpLog(fd);
+ mElectionLog.dumpLog(fd);
+ mBadPktLog.dumpLog(fd);
+ }
+
+ return NO_ERROR;
+}
+
+status_t CommonTimeServer::dumpConfigInterface(int fd,
+ const Vector<String16>& args) {
+ AutoMutex _lock(&mLock);
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+
+ if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+ snprintf(buffer, SIZE, "Permission Denial: "
+ "can't dump CommonTimeConfigService from pid=%d, uid=%d\n",
+ IPCThreadState::self()->getCallingPid(),
+ IPCThreadState::self()->getCallingUid());
+ write(fd, buffer, strlen(buffer));
+ } else {
+ char meStr[64];
+
+ sockaddrToString(mMasterElectionEP, true, meStr, sizeof(meStr));
+
+ dump_printf("Common Time Config Service Status\n"
+ "Bound Interface : %s\n",
+ mBindIfaceValid ? mBindIface.string() : "<unbound>");
+ dump_printf("Master Election Endpoint : %s\n", meStr);
+ dump_printf("Master Election Group ID : %016llx\n", mSyncGroupID);
+ dump_printf("Master Announce Interval : %d mSec\n",
+ mMasterAnnounceIntervalMs);
+ dump_printf("Client Sync Interval : %d mSec\n",
+ mSyncRequestIntervalMs);
+ dump_printf("Panic Threshold : %d uSec\n",
+ mPanicThresholdUsec);
+ dump_printf("Base ME Prio : 0x%02x\n",
+ static_cast<uint32_t>(mMasterPriority));
+ dump_printf("Effective ME Prio : 0x%02x\n",
+ static_cast<uint32_t>(effectivePriority()));
+ dump_printf("Auto Disable Allowed : %s\n",
+ mAutoDisable ? "yes" : "no");
+ dump_printf("Auto Disable Engaged : %s\n",
+ shouldAutoDisable() ? "yes" : "no");
+ }
+
+ return NO_ERROR;
+}
+
+void CommonTimeServer::PacketRTTLog::dumpLog(int fd, const CommonClock& cclk) {
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+ uint32_t avail = !logFull ? wrPtr : RTT_LOG_SIZE;
+
+ if (!avail)
+ return;
+
+ dump_printf("\nPacket Log (%d entries)\n", avail);
+
+ uint32_t ndx = 0;
+ uint32_t i = logFull ? wrPtr : 0;
+ do {
+ if (rxTimes[i]) {
+ int64_t delta = rxTimes[i] - txTimes[i];
+ int64_t deltaUsec = cclk.localDurationToCommonDuration(delta);
+ dump_printf("pkt[%2d] : localTX %12lld localRX %12lld "
+ "(%.3f msec RTT)\n",
+ ndx, txTimes[i], rxTimes[i],
+ static_cast<float>(deltaUsec) / 1000.0);
+ } else {
+ dump_printf("pkt[%2d] : localTX %12lld localRX never\n",
+ ndx, txTimes[i]);
+ }
+ i = (i + 1) % RTT_LOG_SIZE;
+ ndx++;
+ } while (i != wrPtr);
+}
+
+#undef dump_printf
+#undef checked_percentage
+
+} // namespace android
diff --git a/libs/common_time/common_time_server_packets.cpp b/libs/common_time/common_time_server_packets.cpp
new file mode 100644
index 0000000..9833c37
--- /dev/null
+++ b/libs/common_time/common_time_server_packets.cpp
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <stdint.h>
+
+#include "common_time_server_packets.h"
+
+namespace android {
+
+const uint32_t TimeServicePacketHeader::kMagic =
+ (static_cast<uint32_t>('c') << 24) |
+ (static_cast<uint32_t>('c') << 16) |
+ (static_cast<uint32_t>('l') << 8) |
+ static_cast<uint32_t>('k');
+
+const uint16_t TimeServicePacketHeader::kCurVersion = 1;
+
+#define SERIALIZE_FIELD(field_name, type, converter) \
+ do { \
+ if ((offset + sizeof(field_name)) > length) \
+ return -1; \
+ *((type*)(data + offset)) = converter(field_name); \
+ offset += sizeof(field_name); \
+ } while (0)
+#define SERIALIZE_INT16(field_name) SERIALIZE_FIELD(field_name, int16_t, htons)
+#define SERIALIZE_INT32(field_name) SERIALIZE_FIELD(field_name, int32_t, htonl)
+#define SERIALIZE_INT64(field_name) SERIALIZE_FIELD(field_name, int64_t, htonq)
+
+#define DESERIALIZE_FIELD(field_name, type, converter) \
+ do { \
+ if ((offset + sizeof(field_name)) > length) \
+ return -1; \
+ field_name = converter(*((type*)(data + offset))); \
+ offset += sizeof(field_name); \
+ } while (0)
+#define DESERIALIZE_INT16(field_name) DESERIALIZE_FIELD(field_name, int16_t, ntohs)
+#define DESERIALIZE_INT32(field_name) DESERIALIZE_FIELD(field_name, int32_t, ntohl)
+#define DESERIALIZE_INT64(field_name) DESERIALIZE_FIELD(field_name, int64_t, ntohq)
+
+#define kDevicePriorityShift 56
+#define kDeviceIDMask ((static_cast<uint64_t>(1) << kDevicePriorityShift) - 1)
+
+inline uint64_t packDeviceID(uint64_t devID, uint8_t prio) {
+ return (devID & kDeviceIDMask) |
+ (static_cast<uint64_t>(prio) << kDevicePriorityShift);
+}
+
+inline uint64_t unpackDeviceID(uint64_t packed) {
+ return (packed & kDeviceIDMask);
+}
+
+inline uint8_t unpackDevicePriority(uint64_t packed) {
+ return static_cast<uint8_t>(packed >> kDevicePriorityShift);
+}
+
+ssize_t TimeServicePacketHeader::serializeHeader(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = 0;
+ int16_t pktType = static_cast<int16_t>(packetType);
+ SERIALIZE_INT32(magic);
+ SERIALIZE_INT16(version);
+ SERIALIZE_INT16(pktType);
+ SERIALIZE_INT64(timelineID);
+ SERIALIZE_INT64(syncGroupID);
+ return offset;
+}
+
+ssize_t TimeServicePacketHeader::deserializeHeader(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = 0;
+ int16_t tmp;
+ DESERIALIZE_INT32(magic);
+ DESERIALIZE_INT16(version);
+ DESERIALIZE_INT16(tmp);
+ DESERIALIZE_INT64(timelineID);
+ DESERIALIZE_INT64(syncGroupID);
+ packetType = static_cast<TimeServicePacketType>(tmp);
+ return offset;
+}
+
+ssize_t TimeServicePacketHeader::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t ret, tmp;
+
+ ret = serializeHeader(data, length);
+ if (ret < 0)
+ return ret;
+
+ data += ret;
+ length -= ret;
+
+ switch (packetType) {
+ case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+ tmp =((WhoIsMasterRequestPacket*)(this))->serializePacket(data,
+ length);
+ break;
+ case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+ tmp =((WhoIsMasterResponsePacket*)(this))->serializePacket(data,
+ length);
+ break;
+ case TIME_PACKET_SYNC_REQUEST:
+ tmp =((SyncRequestPacket*)(this))->serializePacket(data, length);
+ break;
+ case TIME_PACKET_SYNC_RESPONSE:
+ tmp =((SyncResponsePacket*)(this))->serializePacket(data, length);
+ break;
+ case TIME_PACKET_MASTER_ANNOUNCEMENT:
+ tmp =((MasterAnnouncementPacket*)(this))->serializePacket(data,
+ length);
+ break;
+ default:
+ return -1;
+ }
+
+ if (tmp < 0)
+ return tmp;
+
+ return ret + tmp;
+}
+
+ssize_t UniversalTimeServicePacket::deserializePacket(
+ const uint8_t* data,
+ uint32_t length,
+ uint64_t expectedSyncGroupID) {
+ ssize_t ret;
+ TimeServicePacketHeader* header;
+ if (length < 8)
+ return -1;
+
+ packetType = ntohs(*((uint16_t*)(data + 6)));
+ switch (packetType) {
+ case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+ ret = p.who_is_master_request.deserializePacket(data, length);
+ header = &p.who_is_master_request;
+ break;
+ case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+ ret = p.who_is_master_response.deserializePacket(data, length);
+ header = &p.who_is_master_response;
+ break;
+ case TIME_PACKET_SYNC_REQUEST:
+ ret = p.sync_request.deserializePacket(data, length);
+ header = &p.sync_request;
+ break;
+ case TIME_PACKET_SYNC_RESPONSE:
+ ret = p.sync_response.deserializePacket(data, length);
+ header = &p.sync_response;
+ break;
+ case TIME_PACKET_MASTER_ANNOUNCEMENT:
+ ret = p.master_announcement.deserializePacket(data, length);
+ header = &p.master_announcement;
+ break;
+ default:
+ return -1;
+ }
+
+ if ((ret >= 0) && !header->checkPacket(expectedSyncGroupID))
+ ret = -1;
+
+ return ret;
+}
+
+ssize_t WhoIsMasterRequestPacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed = packDeviceID(senderDeviceID, senderDevicePriority);
+ SERIALIZE_INT64(packed);
+ }
+ return offset;
+}
+
+ssize_t WhoIsMasterRequestPacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed;
+ DESERIALIZE_INT64(packed);
+ senderDeviceID = unpackDeviceID(packed);
+ senderDevicePriority = unpackDevicePriority(packed);
+ }
+ return offset;
+}
+
+ssize_t WhoIsMasterResponsePacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed = packDeviceID(deviceID, devicePriority);
+ SERIALIZE_INT64(packed);
+ }
+ return offset;
+}
+
+ssize_t WhoIsMasterResponsePacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed;
+ DESERIALIZE_INT64(packed);
+ deviceID = unpackDeviceID(packed);
+ devicePriority = unpackDevicePriority(packed);
+ }
+ return offset;
+}
+
+ssize_t SyncRequestPacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ SERIALIZE_INT64(clientTxLocalTime);
+ }
+ return offset;
+}
+
+ssize_t SyncRequestPacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ DESERIALIZE_INT64(clientTxLocalTime);
+ }
+ return offset;
+}
+
+ssize_t SyncResponsePacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ SERIALIZE_INT64(clientTxLocalTime);
+ SERIALIZE_INT64(masterRxCommonTime);
+ SERIALIZE_INT64(masterTxCommonTime);
+ SERIALIZE_INT32(nak);
+ }
+ return offset;
+}
+
+ssize_t SyncResponsePacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ DESERIALIZE_INT64(clientTxLocalTime);
+ DESERIALIZE_INT64(masterRxCommonTime);
+ DESERIALIZE_INT64(masterTxCommonTime);
+ DESERIALIZE_INT32(nak);
+ }
+ return offset;
+}
+
+ssize_t MasterAnnouncementPacket::serializePacket(uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = serializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed = packDeviceID(deviceID, devicePriority);
+ SERIALIZE_INT64(packed);
+ }
+ return offset;
+}
+
+ssize_t MasterAnnouncementPacket::deserializePacket(const uint8_t* data,
+ uint32_t length) {
+ ssize_t offset = deserializeHeader(data, length);
+ if (offset > 0) {
+ uint64_t packed;
+ DESERIALIZE_INT64(packed);
+ deviceID = unpackDeviceID(packed);
+ devicePriority = unpackDevicePriority(packed);
+ }
+ return offset;
+}
+
+} // namespace android
+
diff --git a/libs/common_time/common_time_server_packets.h b/libs/common_time/common_time_server_packets.h
new file mode 100644
index 0000000..57ba8a2
--- /dev/null
+++ b/libs/common_time/common_time_server_packets.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_COMMON_TIME_SERVER_PACKETS_H
+#define ANDROID_COMMON_TIME_SERVER_PACKETS_H
+
+#include <stdint.h>
+#include <common_time/ICommonClock.h>
+
+namespace android {
+
+/***** time sync protocol packets *****/
+
+enum TimeServicePacketType {
+ TIME_PACKET_WHO_IS_MASTER_REQUEST = 1,
+ TIME_PACKET_WHO_IS_MASTER_RESPONSE,
+ TIME_PACKET_SYNC_REQUEST,
+ TIME_PACKET_SYNC_RESPONSE,
+ TIME_PACKET_MASTER_ANNOUNCEMENT,
+};
+
+class TimeServicePacketHeader {
+ public:
+ friend class UniversalTimeServicePacket;
+ // magic number identifying the protocol
+ uint32_t magic;
+
+ // protocol version of the packet
+ uint16_t version;
+
+ // type of the packet
+ TimeServicePacketType packetType;
+
+ // the timeline ID
+ uint64_t timelineID;
+
+ // synchronization group this packet belongs to (used to operate multiple
+ // synchronization domains which all use the same master election endpoint)
+ uint64_t syncGroupID;
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+
+ protected:
+ void initHeader(TimeServicePacketType type,
+ const uint64_t tlID,
+ const uint64_t groupID) {
+ magic = kMagic;
+ version = kCurVersion;
+ packetType = type;
+ timelineID = tlID;
+ syncGroupID = groupID;
+ }
+
+ bool checkPacket(uint64_t expectedSyncGroupID) const {
+ return ((magic == kMagic) &&
+ (version == kCurVersion) &&
+ (!expectedSyncGroupID || (syncGroupID == expectedSyncGroupID)));
+ }
+
+ ssize_t serializeHeader(uint8_t* data, uint32_t length);
+ ssize_t deserializeHeader(const uint8_t* data, uint32_t length);
+
+ private:
+ static const uint32_t kMagic;
+ static const uint16_t kCurVersion;
+};
+
+// packet querying for a suitable master
+class WhoIsMasterRequestPacket : public TimeServicePacketHeader {
+ public:
+ uint64_t senderDeviceID;
+ uint8_t senderDevicePriority;
+
+ void initHeader(const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_REQUEST,
+ ICommonClock::kInvalidTimelineID,
+ groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// response to a WhoIsMaster request
+class WhoIsMasterResponsePacket : public TimeServicePacketHeader {
+ public:
+ uint64_t deviceID;
+ uint8_t devicePriority;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_RESPONSE,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// packet sent by a client requesting correspondence between local
+// and common time
+class SyncRequestPacket : public TimeServicePacketHeader {
+ public:
+ // local time when this request was transmitted
+ int64_t clientTxLocalTime;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_REQUEST,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// response to a sync request sent by the master
+class SyncResponsePacket : public TimeServicePacketHeader {
+ public:
+ // local time when this request was transmitted by the client
+ int64_t clientTxLocalTime;
+
+ // common time when the master received the request
+ int64_t masterRxCommonTime;
+
+ // common time when the master transmitted the response
+ int64_t masterTxCommonTime;
+
+ // flag that is set if the recipient of the sync request is not acting
+ // as a master for the requested timeline
+ uint32_t nak;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_RESPONSE,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// announcement of the master's presence
+class MasterAnnouncementPacket : public TimeServicePacketHeader {
+ public:
+ // the master's device ID
+ uint64_t deviceID;
+ uint8_t devicePriority;
+
+ void initHeader(const uint64_t tlID, const uint64_t groupID) {
+ TimeServicePacketHeader::initHeader(TIME_PACKET_MASTER_ANNOUNCEMENT,
+ tlID, groupID);
+ }
+
+ ssize_t serializePacket(uint8_t* data, uint32_t length);
+ ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+class UniversalTimeServicePacket {
+ public:
+ uint16_t packetType;
+ union {
+ WhoIsMasterRequestPacket who_is_master_request;
+ WhoIsMasterResponsePacket who_is_master_response;
+ SyncRequestPacket sync_request;
+ SyncResponsePacket sync_response;
+ MasterAnnouncementPacket master_announcement;
+ } p;
+
+ ssize_t deserializePacket(const uint8_t* data,
+ uint32_t length,
+ uint64_t expectedSyncGroupID);
+};
+
+}; // namespace android
+
+#endif // ANDROID_COMMON_TIME_SERVER_PACKETS_H
+
+
diff --git a/libs/common_time/diag_thread.cpp b/libs/common_time/diag_thread.cpp
new file mode 100644
index 0000000..4cb9551
--- /dev/null
+++ b/libs/common_time/diag_thread.cpp
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utils/Errors.h>
+#include <utils/misc.h>
+
+#include <common_time/local_clock.h>
+
+#include "common_clock.h"
+#include "diag_thread.h"
+
+#define kMaxEvents 16
+#define kListenPort 9876
+
+static bool setNonblocking(int fd) {
+ int flags = fcntl(fd, F_GETFL);
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+ ALOGE("Failed to set socket (%d) to non-blocking mode (errno %d)",
+ fd, errno);
+ return false;
+ }
+
+ return true;
+}
+
+static bool setNodelay(int fd) {
+ int tmp = 1;
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof(tmp)) < 0) {
+ ALOGE("Failed to set socket (%d) to no-delay mode (errno %d)",
+ fd, errno);
+ return false;
+ }
+
+ return true;
+}
+
+namespace android {
+
+DiagThread::DiagThread(CommonClock* common_clock, LocalClock* local_clock) {
+ common_clock_ = common_clock;
+ local_clock_ = local_clock;
+ listen_fd_ = -1;
+ data_fd_ = -1;
+ kernel_logID_basis_known_ = false;
+ discipline_log_ID_ = 0;
+}
+
+DiagThread::~DiagThread() {
+}
+
+status_t DiagThread::startWorkThread() {
+ status_t res;
+ stopWorkThread();
+ res = run("Diag");
+
+ if (res != OK)
+ ALOGE("Failed to start work thread (res = %d)", res);
+
+ return res;
+}
+
+void DiagThread::stopWorkThread() {
+ status_t res;
+ res = requestExitAndWait(); // block until thread exit.
+ if (res != OK)
+ ALOGE("Failed to stop work thread (res = %d)", res);
+}
+
+bool DiagThread::openListenSocket() {
+ bool ret = false;
+ int flags;
+ cleanupListenSocket();
+
+ if ((listen_fd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+ ALOGE("Socket failed.");
+ goto bailout;
+ }
+
+ // Set non-blocking operation
+ if (!setNonblocking(listen_fd_))
+ goto bailout;
+
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = htons(kListenPort);
+
+ if (bind(listen_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+ ALOGE("Bind failed.");
+ goto bailout;
+ }
+
+ if (listen(listen_fd_, 1) < 0) {
+ ALOGE("Listen failed.");
+ goto bailout;
+ }
+
+ ret = true;
+bailout:
+ if (!ret)
+ cleanupListenSocket();
+
+ return ret;
+}
+
+void DiagThread::cleanupListenSocket() {
+ if (listen_fd_ >= 0) {
+ int res;
+
+ struct linger l;
+ l.l_onoff = 1;
+ l.l_linger = 0;
+
+ setsockopt(listen_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
+ shutdown(listen_fd_, SHUT_RDWR);
+ close(listen_fd_);
+ listen_fd_ = -1;
+ }
+}
+
+void DiagThread::cleanupDataSocket() {
+ if (data_fd_ >= 0) {
+ int res;
+
+ struct linger l;
+ l.l_onoff = 1;
+ l.l_linger = 0;
+
+ setsockopt(data_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
+ shutdown(data_fd_, SHUT_RDWR);
+ close(data_fd_);
+ data_fd_ = -1;
+ }
+}
+
+void DiagThread::resetLogIDs() {
+ // Drain and discard all of the events from the kernel
+ struct local_time_debug_event events[kMaxEvents];
+ while(local_clock_->getDebugLog(events, kMaxEvents) > 0)
+ ;
+
+ {
+ Mutex::Autolock lock(&discipline_log_lock_);
+ discipline_log_.clear();
+ discipline_log_ID_ = 0;
+ }
+
+ kernel_logID_basis_known_ = false;
+}
+
+void DiagThread::pushDisciplineEvent(int64_t observed_local_time,
+ int64_t observed_common_time,
+ int64_t nominal_common_time,
+ int32_t total_correction,
+ int32_t rtt) {
+ Mutex::Autolock lock(&discipline_log_lock_);
+
+ DisciplineEventRecord evt;
+
+ evt.event_id = discipline_log_ID_++;
+
+ evt.action_local_time = local_clock_->getLocalTime();
+ common_clock_->localToCommon(evt.action_local_time,
+ &evt.action_common_time);
+
+ evt.observed_local_time = observed_local_time;
+ evt.observed_common_time = observed_common_time;
+ evt.nominal_common_time = nominal_common_time;
+ evt.total_correction = total_correction;
+ evt.rtt = rtt;
+
+ discipline_log_.push_back(evt);
+ while (discipline_log_.size() > kMaxDisciplineLogSize)
+ discipline_log_.erase(discipline_log_.begin());
+}
+
+bool DiagThread::threadLoop() {
+ struct pollfd poll_fds[1];
+
+ if (!openListenSocket()) {
+ ALOGE("Failed to open listen socket");
+ goto bailout;
+ }
+
+ while (!exitPending()) {
+ memset(&poll_fds, 0, sizeof(poll_fds));
+
+ if (data_fd_ < 0) {
+ poll_fds[0].fd = listen_fd_;
+ poll_fds[0].events = POLLIN;
+ } else {
+ poll_fds[0].fd = data_fd_;
+ poll_fds[0].events = POLLRDHUP | POLLIN;
+ }
+
+ int poll_res = poll(poll_fds, NELEM(poll_fds), 50);
+ if (poll_res < 0) {
+ ALOGE("Fatal error (%d,%d) while waiting on events",
+ poll_res, errno);
+ goto bailout;
+ }
+
+ if (exitPending())
+ break;
+
+ if (poll_fds[0].revents) {
+ if (poll_fds[0].fd == listen_fd_) {
+ data_fd_ = accept(listen_fd_, NULL, NULL);
+
+ if (data_fd_ < 0) {
+ ALOGW("Failed accept on socket %d with err %d",
+ listen_fd_, errno);
+ } else {
+ if (!setNonblocking(data_fd_))
+ cleanupDataSocket();
+ if (!setNodelay(data_fd_))
+ cleanupDataSocket();
+ }
+ } else
+ if (poll_fds[0].fd == data_fd_) {
+ if (poll_fds[0].revents & POLLRDHUP) {
+ // Connection hung up; time to clean up.
+ cleanupDataSocket();
+ } else
+ if (poll_fds[0].revents & POLLIN) {
+ uint8_t cmd;
+ if (read(data_fd_, &cmd, sizeof(cmd)) > 0) {
+ switch(cmd) {
+ case 'r':
+ case 'R':
+ resetLogIDs();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ struct local_time_debug_event events[kMaxEvents];
+ int amt = local_clock_->getDebugLog(events, kMaxEvents);
+
+ if (amt > 0) {
+ for (int i = 0; i < amt; i++) {
+ struct local_time_debug_event& e = events[i];
+
+ if (!kernel_logID_basis_known_) {
+ kernel_logID_basis_ = e.local_timesync_event_id;
+ kernel_logID_basis_known_ = true;
+ }
+
+ char buf[1024];
+ int64_t common_time;
+ status_t res = common_clock_->localToCommon(e.local_time,
+ &common_time);
+ snprintf(buf, sizeof(buf), "E,%lld,%lld,%lld,%d\n",
+ e.local_timesync_event_id - kernel_logID_basis_,
+ e.local_time,
+ common_time,
+ (OK == res) ? 1 : 0);
+ buf[sizeof(buf) - 1] = 0;
+
+ if (data_fd_ >= 0)
+ write(data_fd_, buf, strlen(buf));
+ }
+ }
+
+ { // scope for autolock pattern
+ Mutex::Autolock lock(&discipline_log_lock_);
+
+ while (discipline_log_.size() > 0) {
+ char buf[1024];
+ DisciplineEventRecord& e = *discipline_log_.begin();
+ snprintf(buf, sizeof(buf),
+ "D,%lld,%lld,%lld,%lld,%lld,%lld,%d,%d\n",
+ e.event_id,
+ e.action_local_time,
+ e.action_common_time,
+ e.observed_local_time,
+ e.observed_common_time,
+ e.nominal_common_time,
+ e.total_correction,
+ e.rtt);
+ buf[sizeof(buf) - 1] = 0;
+
+ if (data_fd_ >= 0)
+ write(data_fd_, buf, strlen(buf));
+
+ discipline_log_.erase(discipline_log_.begin());
+ }
+ }
+ }
+
+bailout:
+ cleanupDataSocket();
+ cleanupListenSocket();
+ return false;
+}
+
+} // namespace android
diff --git a/libs/common_time/diag_thread.h b/libs/common_time/diag_thread.h
new file mode 100644
index 0000000..c630e0d
--- /dev/null
+++ b/libs/common_time/diag_thread.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __DIAG_THREAD_H__
+#define __DIAG_THREAD_H__
+
+#include <utils/List.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class CommonClock;
+class LocalClock;
+
+class DiagThread : public Thread {
+ public:
+ DiagThread(CommonClock* common_clock, LocalClock* local_clock);
+ ~DiagThread();
+
+ status_t startWorkThread();
+ void stopWorkThread();
+ virtual bool threadLoop();
+
+ void pushDisciplineEvent(int64_t observed_local_time,
+ int64_t observed_common_time,
+ int64_t nominal_common_time,
+ int32_t total_correction,
+ int32_t rtt);
+
+ private:
+ typedef struct {
+ int64_t event_id;
+ int64_t action_local_time;
+ int64_t action_common_time;
+ int64_t observed_local_time;
+ int64_t observed_common_time;
+ int64_t nominal_common_time;
+ int32_t total_correction;
+ int32_t rtt;
+ } DisciplineEventRecord;
+
+ bool openListenSocket();
+ void cleanupListenSocket();
+ void cleanupDataSocket();
+ void resetLogIDs();
+
+ CommonClock* common_clock_;
+ LocalClock* local_clock_;
+ int listen_fd_;
+ int data_fd_;
+
+ int64_t kernel_logID_basis_;
+ bool kernel_logID_basis_known_;
+
+ static const size_t kMaxDisciplineLogSize = 16;
+ Mutex discipline_log_lock_;
+ List<DisciplineEventRecord> discipline_log_;
+ int64_t discipline_log_ID_;
+};
+
+} // namespace android
+
+#endif //__ DIAG_THREAD_H__
diff --git a/libs/common_time/main.cpp b/libs/common_time/main.cpp
new file mode 100644
index 0000000..49eb30a
--- /dev/null
+++ b/libs/common_time/main.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+
+#include "common_time_server.h"
+
+int main(int argc, char *argv[]) {
+ using namespace android;
+
+ sp<CommonTimeServer> service = new CommonTimeServer();
+ if (service == NULL)
+ return 1;
+
+ ProcessState::self()->startThreadPool();
+ service->run("CommonTimeServer", ANDROID_PRIORITY_NORMAL);
+
+ IPCThreadState::self()->joinThreadPool();
+ return 0;
+}
+
diff --git a/libs/common_time/utils.cpp b/libs/common_time/utils.cpp
new file mode 100644
index 0000000..ddcdfe7
--- /dev/null
+++ b/libs/common_time/utils.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include "utils.h"
+
+namespace android {
+
+void Timeout::setTimeout(int msec) {
+ if (msec < 0) {
+ mSystemEndTime = 0;
+ return;
+ }
+
+ mSystemEndTime = systemTime() + (static_cast<nsecs_t>(msec) * 1000000);
+}
+
+int Timeout::msecTillTimeout(nsecs_t nowTime) {
+ if (!mSystemEndTime) {
+ return -1;
+ }
+
+ if (mSystemEndTime < nowTime) {
+ return 0;
+ }
+
+ nsecs_t delta = mSystemEndTime - nowTime;
+ delta += 999999;
+ delta /= 1000000;
+ if (delta > 0x7FFFFFFF) {
+ return 0x7FFFFFFF;
+ }
+
+ return static_cast<int>(delta);
+}
+
+LogRing::LogRing(const char* header, size_t entries)
+ : mSize(entries)
+ , mWr(0)
+ , mIsFull(false)
+ , mHeader(header) {
+ mRingBuffer = new Entry[mSize];
+ if (NULL == mRingBuffer)
+ ALOGE("Failed to allocate log ring with %zu entries.", mSize);
+}
+
+LogRing::~LogRing() {
+ if (NULL != mRingBuffer)
+ delete[] mRingBuffer;
+}
+
+void LogRing::log(int prio, const char* tag, const char* fmt, ...) {
+ va_list argp;
+ va_start(argp, fmt);
+ internalLog(prio, tag, fmt, argp);
+ va_end(argp);
+}
+
+void LogRing::log(const char* fmt, ...) {
+ va_list argp;
+ va_start(argp, fmt);
+ internalLog(0, NULL, fmt, argp);
+ va_end(argp);
+}
+
+void LogRing::internalLog(int prio,
+ const char* tag,
+ const char* fmt,
+ va_list argp) {
+ if (NULL != mRingBuffer) {
+ Mutex::Autolock lock(&mLock);
+ String8 s(String8::formatV(fmt, argp));
+ Entry* last = NULL;
+
+ if (mIsFull || mWr)
+ last = &(mRingBuffer[(mWr + mSize - 1) % mSize]);
+
+
+ if ((NULL != last) && !last->s.compare(s)) {
+ gettimeofday(&(last->last_ts), NULL);
+ ++last->count;
+ } else {
+ gettimeofday(&mRingBuffer[mWr].first_ts, NULL);
+ mRingBuffer[mWr].last_ts = mRingBuffer[mWr].first_ts;
+ mRingBuffer[mWr].count = 1;
+ mRingBuffer[mWr].s.setTo(s);
+
+ mWr = (mWr + 1) % mSize;
+ if (!mWr)
+ mIsFull = true;
+ }
+ }
+
+ if (NULL != tag)
+ LOG_PRI_VA(prio, tag, fmt, argp);
+}
+
+void LogRing::dumpLog(int fd) {
+ if (NULL == mRingBuffer)
+ return;
+
+ Mutex::Autolock lock(&mLock);
+
+ if (!mWr && !mIsFull)
+ return;
+
+ char buf[1024];
+ int res;
+ size_t start = mIsFull ? mWr : 0;
+ size_t count = mIsFull ? mSize : mWr;
+ static const char* kTimeFmt = "%a %b %d %Y %H:%M:%S";
+
+ res = snprintf(buf, sizeof(buf), "\n%s\n", mHeader);
+ if (res > 0)
+ write(fd, buf, res);
+
+ for (size_t i = 0; i < count; ++i) {
+ struct tm t;
+ char timebuf[64];
+ char repbuf[96];
+ size_t ndx = (start + i) % mSize;
+
+ if (1 != mRingBuffer[ndx].count) {
+ localtime_r(&mRingBuffer[ndx].last_ts.tv_sec, &t);
+ strftime(timebuf, sizeof(timebuf), kTimeFmt, &t);
+ snprintf(repbuf, sizeof(repbuf),
+ " (repeated %d times, last was %s.%03ld)",
+ mRingBuffer[ndx].count,
+ timebuf,
+ mRingBuffer[ndx].last_ts.tv_usec / 1000);
+ repbuf[sizeof(repbuf) - 1] = 0;
+ } else {
+ repbuf[0] = 0;
+ }
+
+ localtime_r(&mRingBuffer[ndx].first_ts.tv_sec, &t);
+ strftime(timebuf, sizeof(timebuf), kTimeFmt, &t);
+ res = snprintf(buf, sizeof(buf), "[%2zu] %s.%03ld :: %s%s\n",
+ i, timebuf,
+ mRingBuffer[ndx].first_ts.tv_usec / 1000,
+ mRingBuffer[ndx].s.string(),
+ repbuf);
+
+ if (res > 0)
+ write(fd, buf, res);
+ }
+}
+
+} // namespace android
diff --git a/libs/common_time/utils.h b/libs/common_time/utils.h
new file mode 100644
index 0000000..c28cf0a
--- /dev/null
+++ b/libs/common_time/utils.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __UTILS_H__
+#define __UTILS_H__
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+class Timeout {
+ public:
+ Timeout() : mSystemEndTime(0) { }
+
+ // Set a timeout which should occur msec milliseconds from now.
+ // Negative values will cancel any current timeout;
+ void setTimeout(int msec);
+
+ // Return the number of milliseconds until the timeout occurs, or -1 if
+ // no timeout is scheduled.
+ int msecTillTimeout(nsecs_t nowTime);
+ int msecTillTimeout() { return msecTillTimeout(systemTime()); }
+
+ private:
+ // The systemTime() at which the timeout will be complete, or 0 if no
+ // timeout is currently scheduled.
+ nsecs_t mSystemEndTime;
+};
+
+class LogRing {
+ public:
+ LogRing(const char* header, size_t entries);
+ ~LogRing();
+
+ // Send a log message to logcat as well as storing it in the ring buffer.
+ void log(int prio, const char* tag, const char* fmt, ...);
+
+ // Add a log message the ring buffer, do not send the message to logcat.
+ void log(const char* fmt, ...);
+
+ // Dump the log to an fd (dumpsys style)
+ void dumpLog(int fd);
+
+ private:
+ class Entry {
+ public:
+ uint32_t count;
+ struct timeval first_ts;
+ struct timeval last_ts;
+ String8 s;
+ };
+
+ Mutex mLock;
+ Entry* mRingBuffer;
+ size_t mSize;
+ size_t mWr;
+ bool mIsFull;
+ const char* mHeader;
+
+ void internalLog(int prio, const char* tag, const char* fmt, va_list va);
+};
+
+} // namespace android
+
+#endif // __UTILS_H__
diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp
new file mode 100644
index 0000000..9cc83ed
--- /dev/null
+++ b/libs/hwui/AmbientShadow.cpp
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <math.h>
+#include <utils/Log.h>
+#include <utils/Vector.h>
+
+#include "AmbientShadow.h"
+#include "ShadowTessellator.h"
+#include "Vertex.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Calculate the shadows as a triangle strips while alpha value as the
+ * shadow values.
+ *
+ * @param isCasterOpaque Whether the caster is opaque.
+ * @param vertices The shadow caster's polygon, which is represented in a Vector3
+ * array.
+ * @param vertexCount The length of caster's polygon in terms of number of
+ * vertices.
+ * @param centroid3d The centroid of the shadow caster.
+ * @param heightFactor The factor showing the higher the object, the lighter the
+ * shadow.
+ * @param geomFactor The factor scaling the geometry expansion along the normal.
+ *
+ * @param shadowVertexBuffer Return an floating point array of (x, y, a)
+ * triangle strips mode.
+ */
+void AmbientShadow::createAmbientShadow(bool isCasterOpaque,
+ const Vector3* vertices, int vertexCount, const Vector3& centroid3d,
+ float heightFactor, float geomFactor, VertexBuffer& shadowVertexBuffer) {
+ const int rays = SHADOW_RAY_COUNT;
+ // Validate the inputs.
+ if (vertexCount < 3 || heightFactor <= 0 || rays <= 0
+ || geomFactor <= 0) {
+#if DEBUG_SHADOW
+ ALOGW("Invalid input for createAmbientShadow(), early return!");
+#endif
+ return;
+ }
+
+ Vector<Vector2> dir; // TODO: use C++11 unique_ptr
+ dir.setCapacity(rays);
+ float rayDist[rays];
+ float rayHeight[rays];
+ calculateRayDirections(rays, vertices, vertexCount, centroid3d, dir.editArray());
+
+ // Calculate the length and height of the points along the edge.
+ //
+ // The math here is:
+ // Intersect each ray (starting from the centroid) with the polygon.
+ for (int i = 0; i < rays; i++) {
+ int edgeIndex;
+ float edgeFraction;
+ float rayDistance;
+ calculateIntersection(vertices, vertexCount, centroid3d, dir[i], edgeIndex,
+ edgeFraction, rayDistance);
+ rayDist[i] = rayDistance;
+ if (edgeIndex < 0 || edgeIndex >= vertexCount) {
+#if DEBUG_SHADOW
+ ALOGW("Invalid edgeIndex!");
+#endif
+ edgeIndex = 0;
+ }
+ float h1 = vertices[edgeIndex].z;
+ float h2 = vertices[((edgeIndex + 1) % vertexCount)].z;
+ rayHeight[i] = h1 + edgeFraction * (h2 - h1);
+ }
+
+ // The output buffer length basically is roughly rays * layers, but since we
+ // need triangle strips, so we need to duplicate vertices to accomplish that.
+ AlphaVertex* shadowVertices =
+ shadowVertexBuffer.alloc<AlphaVertex>(SHADOW_VERTEX_COUNT);
+
+ // Calculate the vertex of the shadows.
+ //
+ // The math here is:
+ // Along the edges of the polygon, for each intersection point P (generated above),
+ // calculate the normal N, which should be perpendicular to the edge of the
+ // polygon (represented by the neighbor intersection points) .
+ // Shadow's vertices will be generated as : P + N * scale.
+ const Vector2 centroid2d = {centroid3d.x, centroid3d.y};
+ for (int rayIndex = 0; rayIndex < rays; rayIndex++) {
+ Vector2 normal = {1.0f, 0.0f};
+ calculateNormal(rays, rayIndex, dir.array(), rayDist, normal);
+
+ // The vertex should be start from rayDist[i] then scale the
+ // normalizeNormal!
+ Vector2 intersection = dir[rayIndex] * rayDist[rayIndex] +
+ centroid2d;
+
+ // outer ring of points, expanded based upon height of each ray intersection
+ float expansionDist = rayHeight[rayIndex] * heightFactor *
+ geomFactor;
+ AlphaVertex::set(&shadowVertices[rayIndex],
+ intersection.x + normal.x * expansionDist,
+ intersection.y + normal.y * expansionDist,
+ 0.0f);
+
+ // inner ring of points
+ float opacity = 1.0 / (1 + rayHeight[rayIndex] * heightFactor);
+ // NOTE: Shadow alpha values are transformed when stored in alphavertices,
+ // so that they can be consumed directly by gFS_Main_ApplyVertexAlphaShadowInterp
+ float transformedOpacity = acos(1.0f - 2.0f * opacity);
+ AlphaVertex::set(&shadowVertices[rays + rayIndex],
+ intersection.x,
+ intersection.y,
+ transformedOpacity);
+ }
+
+ if (isCasterOpaque) {
+ // skip inner ring, calc bounds over filled portion of buffer
+ shadowVertexBuffer.computeBounds<AlphaVertex>(2 * rays);
+ shadowVertexBuffer.setMode(VertexBuffer::kOnePolyRingShadow);
+ } else {
+ // If caster isn't opaque, we need to to fill the umbra by storing the umbra's
+ // centroid in the innermost ring of vertices.
+ float centroidAlpha = 1.0 / (1 + centroid3d.z * heightFactor);
+ AlphaVertex centroidXYA;
+ AlphaVertex::set(&centroidXYA, centroid2d.x, centroid2d.y, centroidAlpha);
+ for (int rayIndex = 0; rayIndex < rays; rayIndex++) {
+ shadowVertices[2 * rays + rayIndex] = centroidXYA;
+ }
+ // calc bounds over entire buffer
+ shadowVertexBuffer.computeBounds<AlphaVertex>();
+ shadowVertexBuffer.setMode(VertexBuffer::kTwoPolyRingShadow);
+ }
+
+#if DEBUG_SHADOW
+ for (int i = 0; i < SHADOW_VERTEX_COUNT; i++) {
+ ALOGD("ambient shadow value: i %d, (x:%f, y:%f, a:%f)", i, shadowVertices[i].x,
+ shadowVertices[i].y, shadowVertices[i].alpha);
+ }
+#endif
+}
+
+/**
+ * Generate an array of rays' direction vectors.
+ * To make sure the vertices generated are clockwise, the directions are from PI
+ * to -PI.
+ *
+ * @param rays The number of rays shooting out from the centroid.
+ * @param vertices Vertices of the polygon.
+ * @param vertexCount The number of vertices.
+ * @param centroid3d The centroid of the polygon.
+ * @param dir Return the array of ray vectors.
+ */
+void AmbientShadow::calculateRayDirections(const int rays, const Vector3* vertices,
+ const int vertexCount, const Vector3& centroid3d, Vector2* dir) {
+ // If we don't have enough rays, then fall back to the uniform distribution.
+ if (vertexCount * 2 > rays) {
+ float deltaAngle = 2 * M_PI / rays;
+ for (int i = 0; i < rays; i++) {
+ dir[i].x = cosf(M_PI - deltaAngle * i);
+ dir[i].y = sinf(M_PI - deltaAngle * i);
+ }
+ return;
+ }
+
+ // If we have enough rays, then we assign each vertices a ray, and distribute
+ // the rest uniformly.
+ float rayThetas[rays];
+
+ const int uniformRayCount = rays - vertexCount;
+ const float deltaAngle = 2 * M_PI / uniformRayCount;
+
+ // We have to generate all the vertices' theta anyway and we also need to
+ // find the minimal, so let's precompute it first.
+ // Since the incoming polygon is clockwise, we can find the dip to identify
+ // the minimal theta.
+ float polyThetas[vertexCount];
+ int maxPolyThetaIndex = 0;
+ for (int i = 0; i < vertexCount; i++) {
+ polyThetas[i] = atan2(vertices[i].y - centroid3d.y,
+ vertices[i].x - centroid3d.x);
+ if (i > 0 && polyThetas[i] > polyThetas[i - 1]) {
+ maxPolyThetaIndex = i;
+ }
+ }
+
+ // Both poly's thetas and uniform thetas are in decrease order(clockwise)
+ // from PI to -PI.
+ int polyThetaIndex = maxPolyThetaIndex;
+ float polyTheta = polyThetas[maxPolyThetaIndex];
+ int uniformThetaIndex = 0;
+ float uniformTheta = M_PI;
+ for (int i = 0; i < rays; i++) {
+ // Compare both thetas and pick the smaller one and move on.
+ bool hasThetaCollision = abs(polyTheta - uniformTheta) < MINIMAL_DELTA_THETA;
+ if (polyTheta > uniformTheta || hasThetaCollision) {
+ if (hasThetaCollision) {
+ // Shift the uniformTheta to middle way between current polyTheta
+ // and next uniform theta. The next uniform theta can wrap around
+ // to exactly PI safely here.
+ // Note that neither polyTheta nor uniformTheta can be FLT_MAX
+ // due to the hasThetaCollision is true.
+ uniformTheta = (polyTheta + M_PI - deltaAngle * (uniformThetaIndex + 1)) / 2;
+#if DEBUG_SHADOW
+ ALOGD("Shifted uniformTheta to %f", uniformTheta);
+#endif
+ }
+ rayThetas[i] = polyTheta;
+ polyThetaIndex = (polyThetaIndex + 1) % vertexCount;
+ if (polyThetaIndex != maxPolyThetaIndex) {
+ polyTheta = polyThetas[polyThetaIndex];
+ } else {
+ // out of poly points.
+ polyTheta = - FLT_MAX;
+ }
+ } else {
+ rayThetas[i] = uniformTheta;
+ uniformThetaIndex++;
+ if (uniformThetaIndex < uniformRayCount) {
+ uniformTheta = M_PI - deltaAngle * uniformThetaIndex;
+ } else {
+ // out of uniform points.
+ uniformTheta = - FLT_MAX;
+ }
+ }
+ }
+
+ for (int i = 0; i < rays; i++) {
+#if DEBUG_SHADOW
+ ALOGD("No. %d : %f", i, rayThetas[i] * 180 / M_PI);
+#endif
+ // TODO: Fix the intersection precision problem and remvoe the delta added
+ // here.
+ dir[i].x = cosf(rayThetas[i] + MINIMAL_DELTA_THETA);
+ dir[i].y = sinf(rayThetas[i] + MINIMAL_DELTA_THETA);
+ }
+}
+
+/**
+ * Calculate the intersection of a ray hitting the polygon.
+ *
+ * @param vertices The shadow caster's polygon, which is represented in a
+ * Vector3 array.
+ * @param vertexCount The length of caster's polygon in terms of number of vertices.
+ * @param start The starting point of the ray.
+ * @param dir The direction vector of the ray.
+ *
+ * @param outEdgeIndex Return the index of the segment (or index of the starting
+ * vertex) that ray intersect with.
+ * @param outEdgeFraction Return the fraction offset from the segment starting
+ * index.
+ * @param outRayDist Return the ray distance from centroid to the intersection.
+ */
+void AmbientShadow::calculateIntersection(const Vector3* vertices, int vertexCount,
+ const Vector3& start, const Vector2& dir, int& outEdgeIndex,
+ float& outEdgeFraction, float& outRayDist) {
+ float startX = start.x;
+ float startY = start.y;
+ float dirX = dir.x;
+ float dirY = dir.y;
+ // Start the search from the last edge from poly[len-1] to poly[0].
+ int p1 = vertexCount - 1;
+
+ for (int p2 = 0; p2 < vertexCount; p2++) {
+ float p1x = vertices[p1].x;
+ float p1y = vertices[p1].y;
+ float p2x = vertices[p2].x;
+ float p2y = vertices[p2].y;
+
+ // The math here is derived from:
+ // f(t, v) = p1x * (1 - t) + p2x * t - (startX + dirX * v) = 0;
+ // g(t, v) = p1y * (1 - t) + p2y * t - (startY + dirY * v) = 0;
+ float div = (dirX * (p1y - p2y) + dirY * p2x - dirY * p1x);
+ if (div != 0) {
+ float t = (dirX * (p1y - startY) + dirY * startX - dirY * p1x) / (div);
+ if (t > 0 && t <= 1) {
+ float t2 = (p1x * (startY - p2y)
+ + p2x * (p1y - startY)
+ + startX * (p2y - p1y)) / div;
+ if (t2 > 0) {
+ outEdgeIndex = p1;
+ outRayDist = t2;
+ outEdgeFraction = t;
+ return;
+ }
+ }
+ }
+ p1 = p2;
+ }
+ return;
+};
+
+/**
+ * Calculate the normal at the intersection point between a ray and the polygon.
+ *
+ * @param rays The total number of rays.
+ * @param currentRayIndex The index of the ray which the normal is based on.
+ * @param dir The array of the all the rays directions.
+ * @param rayDist The pre-computed ray distances array.
+ *
+ * @param normal Return the normal.
+ */
+void AmbientShadow::calculateNormal(int rays, int currentRayIndex,
+ const Vector2* dir, const float* rayDist, Vector2& normal) {
+ int preIndex = (currentRayIndex - 1 + rays) % rays;
+ int postIndex = (currentRayIndex + 1) % rays;
+ Vector2 p1 = dir[preIndex] * rayDist[preIndex];
+ Vector2 p2 = dir[postIndex] * rayDist[postIndex];
+
+ // Now the rays are going CW around the poly.
+ Vector2 delta = p2 - p1;
+ if (delta.length() != 0) {
+ delta.normalize();
+ // Calculate the normal , which is CCW 90 rotate to the delta.
+ normal.x = - delta.y;
+ normal.y = delta.x;
+ }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/AmbientShadow.h b/libs/hwui/AmbientShadow.h
new file mode 100644
index 0000000..68df246
--- /dev/null
+++ b/libs/hwui/AmbientShadow.h
@@ -0,0 +1,57 @@
+
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_AMBIENT_SHADOW_H
+#define ANDROID_HWUI_AMBIENT_SHADOW_H
+
+#include "Debug.h"
+#include "OpenGLRenderer.h"
+#include "Vector.h"
+#include "VertexBuffer.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * AmbientShadow is used to calculate the ambient shadow value around a polygon.
+ *
+ * TODO: calculateIntersection() now is O(N*M), where N is the number of
+ * polygon's vertics and M is the number of rays. In fact, by staring tracing
+ * the vertex from the previous intersection, the algorithm can be O(N + M);
+ */
+class AmbientShadow {
+public:
+ static void createAmbientShadow(bool isCasterOpaque, const Vector3* poly,
+ int polyLength, const Vector3& centroid3d, float heightFactor,
+ float geomFactor, VertexBuffer& shadowVertexBuffer);
+
+private:
+ static void calculateRayDirections(const int rays, const Vector3* vertices,
+ const int vertexCount, const Vector3& centroid3d, Vector2* dir);
+
+ static void calculateIntersection(const Vector3* poly, int nbVertices,
+ const Vector3& start, const Vector2& dir, int& outEdgeIndex,
+ float& outEdgeFraction, float& outRayDist);
+
+ static void calculateNormal(int rays, int currentRayIndex, const Vector2* dir,
+ const float* rayDist, Vector2& normal);
+}; // AmbientShadow
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_AMBIENT_SHADOW_H
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 411c133..d9f7941 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -4,25 +4,33 @@ include $(CLEAR_VARS)
# Only build libhwui when USE_OPENGL_RENDERER is
# defined in the current device/board configuration
ifeq ($(USE_OPENGL_RENDERER),true)
- LOCAL_SRC_FILES:= \
+ LOCAL_SRC_FILES := \
utils/Blur.cpp \
+ utils/GLUtils.cpp \
utils/SortedListImpl.cpp \
thread/TaskManager.cpp \
font/CacheTexture.cpp \
font/Font.cpp \
+ AmbientShadow.cpp \
+ Animator.cpp \
+ AnimatorManager.cpp \
AssetAtlas.cpp \
+ DamageAccumulator.cpp \
FontRenderer.cpp \
GammaFontRenderer.cpp \
Caches.cpp \
DisplayList.cpp \
DeferredDisplayList.cpp \
+ DeferredLayerUpdater.cpp \
DisplayListLogBuffer.cpp \
DisplayListRenderer.cpp \
Dither.cpp \
+ DrawProfiler.cpp \
Extensions.cpp \
FboCache.cpp \
GradientCache.cpp \
Image.cpp \
+ Interpolator.cpp \
Layer.cpp \
LayerCache.cpp \
LayerRenderer.cpp \
@@ -36,43 +44,52 @@ ifeq ($(USE_OPENGL_RENDERER),true)
Program.cpp \
ProgramCache.cpp \
RenderBufferCache.cpp \
+ RenderNode.cpp \
+ RenderProperties.cpp \
+ RenderState.cpp \
ResourceCache.cpp \
- SkiaColorFilter.cpp \
+ ShadowTessellator.cpp \
SkiaShader.cpp \
Snapshot.cpp \
+ SpotShadow.cpp \
+ StatefulBaseRenderer.cpp \
Stencil.cpp \
+ TessellationCache.cpp \
Texture.cpp \
TextureCache.cpp \
TextDropShadowCache.cpp
+# RenderThread stuff
+ LOCAL_SRC_FILES += \
+ renderthread/CanvasContext.cpp \
+ renderthread/DrawFrameTask.cpp \
+ renderthread/EglManager.cpp \
+ renderthread/RenderProxy.cpp \
+ renderthread/RenderTask.cpp \
+ renderthread/RenderThread.cpp \
+ renderthread/TimeLord.cpp
+
intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
LOCAL_C_INCLUDES += \
- $(JNI_H_INCLUDE) \
- $(LOCAL_PATH)/../../include/utils \
- external/skia/include/core \
- external/skia/include/effects \
- external/skia/include/images \
- external/skia/src/core \
- external/skia/src/ports \
- external/skia/include/utils
+ external/skia/src/core
LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
+ LOCAL_CFLAGS += -Wno-unused-parameter
LOCAL_MODULE_CLASS := SHARED_LIBRARIES
- LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libEGL libGLESv2 libskia libui
+ LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libEGL libGLESv2 libskia libui libgui
LOCAL_MODULE := libhwui
LOCAL_MODULE_TAGS := optional
+ include external/stlport/libstlport.mk
+
ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT))
LOCAL_CFLAGS += -DANDROID_ENABLE_RENDERSCRIPT
- LOCAL_SHARED_LIBRARIES += libRS libRScpp libstlport
+ LOCAL_SHARED_LIBRARIES += libRS libRScpp
LOCAL_C_INCLUDES += \
$(intermediates) \
frameworks/rs/cpp \
- frameworks/rs \
- external/stlport/stlport \
- bionic/ \
- bionic/libstdc++/include
+ frameworks/rs
endif
ifndef HWUI_COMPILE_SYMBOLS
@@ -83,6 +100,9 @@ ifeq ($(USE_OPENGL_RENDERER),true)
LOCAL_CFLAGS += -fno-omit-frame-pointer -marm -mapcs
endif
+ # Defaults for ATRACE_TAG and LOG_TAG for libhwui
+ LOCAL_CFLAGS += -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\"
+
include $(BUILD_SHARED_LIBRARY)
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
new file mode 100644
index 0000000..78d569d
--- /dev/null
+++ b/libs/hwui/Animator.cpp
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2014 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 "Animator.h"
+
+#include <inttypes.h>
+#include <set>
+
+#include "RenderNode.h"
+#include "RenderProperties.h"
+
+namespace android {
+namespace uirenderer {
+
+/************************************************************
+ * BaseRenderNodeAnimator
+ ************************************************************/
+
+BaseRenderNodeAnimator::BaseRenderNodeAnimator(float finalValue)
+ : mTarget(NULL)
+ , mFinalValue(finalValue)
+ , mDeltaValue(0)
+ , mFromValue(0)
+ , mInterpolator(0)
+ , mStagingPlayState(NOT_STARTED)
+ , mPlayState(NOT_STARTED)
+ , mHasStartValue(false)
+ , mStartTime(0)
+ , mDuration(300)
+ , mStartDelay(0) {
+}
+
+BaseRenderNodeAnimator::~BaseRenderNodeAnimator() {
+ delete mInterpolator;
+}
+
+void BaseRenderNodeAnimator::checkMutable() {
+ // Should be impossible to hit as the Java-side also has guards for this
+ LOG_ALWAYS_FATAL_IF(mStagingPlayState != NOT_STARTED,
+ "Animator has already been started!");
+}
+
+void BaseRenderNodeAnimator::setInterpolator(Interpolator* interpolator) {
+ checkMutable();
+ delete mInterpolator;
+ mInterpolator = interpolator;
+}
+
+void BaseRenderNodeAnimator::setStartValue(float value) {
+ checkMutable();
+ doSetStartValue(value);
+}
+
+void BaseRenderNodeAnimator::doSetStartValue(float value) {
+ mFromValue = value;
+ mDeltaValue = (mFinalValue - mFromValue);
+ mHasStartValue = true;
+}
+
+void BaseRenderNodeAnimator::setDuration(nsecs_t duration) {
+ checkMutable();
+ mDuration = duration;
+}
+
+void BaseRenderNodeAnimator::setStartDelay(nsecs_t startDelay) {
+ checkMutable();
+ mStartDelay = startDelay;
+}
+
+void BaseRenderNodeAnimator::attach(RenderNode* target) {
+ mTarget = target;
+ onAttached();
+}
+
+void BaseRenderNodeAnimator::pushStaging(TreeInfo& info) {
+ if (!mHasStartValue) {
+ doSetStartValue(getValue(mTarget));
+ }
+ if (mStagingPlayState > mPlayState) {
+ mPlayState = mStagingPlayState;
+ // Oh boy, we're starting! Man the battle stations!
+ if (mPlayState == RUNNING) {
+ transitionToRunning(info);
+ }
+ }
+}
+
+void BaseRenderNodeAnimator::transitionToRunning(TreeInfo& info) {
+ LOG_ALWAYS_FATAL_IF(info.frameTimeMs <= 0, "%" PRId64 " isn't a real frame time!", info.frameTimeMs);
+ if (mStartDelay < 0 || mStartDelay > 50000) {
+ ALOGW("Your start delay is strange and confusing: %" PRId64, mStartDelay);
+ }
+ mStartTime = info.frameTimeMs + mStartDelay;
+ if (mStartTime < 0) {
+ ALOGW("Ended up with a really weird start time of %" PRId64
+ " with frame time %" PRId64 " and start delay %" PRId64,
+ mStartTime, info.frameTimeMs, mStartDelay);
+ // Set to 0 so that the animate() basically instantly finishes
+ mStartTime = 0;
+ }
+ // No interpolator was set, use the default
+ if (!mInterpolator) {
+ mInterpolator = Interpolator::createDefaultInterpolator();
+ }
+ if (mDuration < 0 || mDuration > 50000) {
+ ALOGW("Your duration is strange and confusing: %" PRId64, mDuration);
+ }
+}
+
+bool BaseRenderNodeAnimator::animate(TreeInfo& info) {
+ if (mPlayState < RUNNING) {
+ return false;
+ }
+ if (mPlayState == FINISHED) {
+ return true;
+ }
+
+ // If BaseRenderNodeAnimator is handling the delay (not typical), then
+ // because the staging properties reflect the final value, we always need
+ // to call setValue even if the animation isn't yet running or is still
+ // being delayed as we need to override the staging value
+ if (mStartTime > info.frameTimeMs) {
+ info.out.hasAnimations |= true;
+ setValue(mTarget, mFromValue);
+ return false;
+ }
+
+ float fraction = 1.0f;
+ if (mPlayState == RUNNING && mDuration > 0) {
+ fraction = (float)(info.frameTimeMs - mStartTime) / mDuration;
+ }
+ if (fraction >= 1.0f) {
+ fraction = 1.0f;
+ mPlayState = FINISHED;
+ }
+
+ fraction = mInterpolator->interpolate(fraction);
+ setValue(mTarget, mFromValue + (mDeltaValue * fraction));
+
+ if (mPlayState == FINISHED) {
+ callOnFinishedListener(info);
+ return true;
+ }
+
+ info.out.hasAnimations |= true;
+ return false;
+}
+
+void BaseRenderNodeAnimator::callOnFinishedListener(TreeInfo& info) {
+ if (mListener.get()) {
+ if (!info.animationHook) {
+ mListener->onAnimationFinished(this);
+ } else {
+ info.animationHook->callOnFinished(this, mListener.get());
+ }
+ }
+}
+
+/************************************************************
+ * RenderPropertyAnimator
+ ************************************************************/
+
+struct RenderPropertyAnimator::PropertyAccessors {
+ RenderNode::DirtyPropertyMask dirtyMask;
+ GetFloatProperty getter;
+ SetFloatProperty setter;
+};
+
+// Maps RenderProperty enum to accessors
+const RenderPropertyAnimator::PropertyAccessors RenderPropertyAnimator::PROPERTY_ACCESSOR_LUT[] = {
+ {RenderNode::TRANSLATION_X, &RenderProperties::getTranslationX, &RenderProperties::setTranslationX },
+ {RenderNode::TRANSLATION_Y, &RenderProperties::getTranslationY, &RenderProperties::setTranslationY },
+ {RenderNode::TRANSLATION_X, &RenderProperties::getTranslationZ, &RenderProperties::setTranslationZ },
+ {RenderNode::SCALE_X, &RenderProperties::getScaleX, &RenderProperties::setScaleX },
+ {RenderNode::SCALE_Y, &RenderProperties::getScaleY, &RenderProperties::setScaleY },
+ {RenderNode::ROTATION, &RenderProperties::getRotation, &RenderProperties::setRotation },
+ {RenderNode::ROTATION_X, &RenderProperties::getRotationX, &RenderProperties::setRotationX },
+ {RenderNode::ROTATION_Y, &RenderProperties::getRotationY, &RenderProperties::setRotationY },
+ {RenderNode::X, &RenderProperties::getX, &RenderProperties::setX },
+ {RenderNode::Y, &RenderProperties::getY, &RenderProperties::setY },
+ {RenderNode::Z, &RenderProperties::getZ, &RenderProperties::setZ },
+ {RenderNode::ALPHA, &RenderProperties::getAlpha, &RenderProperties::setAlpha },
+};
+
+RenderPropertyAnimator::RenderPropertyAnimator(RenderProperty property, float finalValue)
+ : BaseRenderNodeAnimator(finalValue)
+ , mPropertyAccess(&(PROPERTY_ACCESSOR_LUT[property])) {
+}
+
+void RenderPropertyAnimator::onAttached() {
+ if (!mHasStartValue
+ && mTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
+ setStartValue((mTarget->stagingProperties().*mPropertyAccess->getter)());
+ }
+}
+
+void RenderPropertyAnimator::onStagingPlayStateChanged() {
+ if (mStagingPlayState == RUNNING) {
+ (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
+ } else if (mStagingPlayState == FINISHED) {
+ // We're being canceled, so make sure that whatever values the UI thread
+ // is observing for us is pushed over
+ mTarget->setPropertyFieldsDirty(dirtyMask());
+ }
+}
+
+uint32_t RenderPropertyAnimator::dirtyMask() {
+ return mPropertyAccess->dirtyMask;
+}
+
+float RenderPropertyAnimator::getValue(RenderNode* target) const {
+ return (target->properties().*mPropertyAccess->getter)();
+}
+
+void RenderPropertyAnimator::setValue(RenderNode* target, float value) {
+ (target->animatorProperties().*mPropertyAccess->setter)(value);
+}
+
+/************************************************************
+ * CanvasPropertyPrimitiveAnimator
+ ************************************************************/
+
+CanvasPropertyPrimitiveAnimator::CanvasPropertyPrimitiveAnimator(
+ CanvasPropertyPrimitive* property, float finalValue)
+ : BaseRenderNodeAnimator(finalValue)
+ , mProperty(property) {
+}
+
+float CanvasPropertyPrimitiveAnimator::getValue(RenderNode* target) const {
+ return mProperty->value;
+}
+
+void CanvasPropertyPrimitiveAnimator::setValue(RenderNode* target, float value) {
+ mProperty->value = value;
+}
+
+uint32_t CanvasPropertyPrimitiveAnimator::dirtyMask() {
+ return RenderNode::DISPLAY_LIST;
+}
+
+/************************************************************
+ * CanvasPropertySkPaintAnimator
+ ************************************************************/
+
+CanvasPropertyPaintAnimator::CanvasPropertyPaintAnimator(
+ CanvasPropertyPaint* property, PaintField field, float finalValue)
+ : BaseRenderNodeAnimator(finalValue)
+ , mProperty(property)
+ , mField(field) {
+}
+
+float CanvasPropertyPaintAnimator::getValue(RenderNode* target) const {
+ switch (mField) {
+ case STROKE_WIDTH:
+ return mProperty->value.getStrokeWidth();
+ case ALPHA:
+ return mProperty->value.getAlpha();
+ }
+ LOG_ALWAYS_FATAL("Unknown field %d", (int) mField);
+ return -1;
+}
+
+static uint8_t to_uint8(float value) {
+ int c = (int) (value + .5f);
+ return static_cast<uint8_t>( c < 0 ? 0 : c > 255 ? 255 : c );
+}
+
+void CanvasPropertyPaintAnimator::setValue(RenderNode* target, float value) {
+ switch (mField) {
+ case STROKE_WIDTH:
+ mProperty->value.setStrokeWidth(value);
+ return;
+ case ALPHA:
+ mProperty->value.setAlpha(to_uint8(value));
+ return;
+ }
+ LOG_ALWAYS_FATAL("Unknown field %d", (int) mField);
+}
+
+uint32_t CanvasPropertyPaintAnimator::dirtyMask() {
+ return RenderNode::DISPLAY_LIST;
+}
+
+RevealAnimator::RevealAnimator(int centerX, int centerY,
+ float startValue, float finalValue)
+ : BaseRenderNodeAnimator(finalValue)
+ , mCenterX(centerX)
+ , mCenterY(centerY) {
+ setStartValue(startValue);
+}
+
+float RevealAnimator::getValue(RenderNode* target) const {
+ return target->properties().getRevealClip().getRadius();
+}
+
+void RevealAnimator::setValue(RenderNode* target, float value) {
+ target->animatorProperties().mutableRevealClip().set(true,
+ mCenterX, mCenterY, value);
+}
+
+uint32_t RevealAnimator::dirtyMask() {
+ return RenderNode::GENERIC;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
new file mode 100644
index 0000000..6dfe7b4
--- /dev/null
+++ b/libs/hwui/Animator.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANIMATOR_H
+#define ANIMATOR_H
+
+#include <cutils/compiler.h>
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+
+#include "CanvasProperty.h"
+#include "Interpolator.h"
+#include "TreeInfo.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+class RenderNode;
+class RenderProperties;
+
+class AnimationListener : public VirtualLightRefBase {
+public:
+ ANDROID_API virtual void onAnimationFinished(BaseRenderNodeAnimator*) = 0;
+protected:
+ ANDROID_API virtual ~AnimationListener() {}
+};
+
+class BaseRenderNodeAnimator : public VirtualLightRefBase {
+ PREVENT_COPY_AND_ASSIGN(BaseRenderNodeAnimator);
+public:
+ ANDROID_API void setStartValue(float value);
+ ANDROID_API void setInterpolator(Interpolator* interpolator);
+ ANDROID_API void setDuration(nsecs_t durationInMs);
+ ANDROID_API nsecs_t duration() { return mDuration; }
+ ANDROID_API void setStartDelay(nsecs_t startDelayInMs);
+ ANDROID_API nsecs_t startDelay() { return mStartDelay; }
+ ANDROID_API void setListener(AnimationListener* listener) {
+ mListener = listener;
+ }
+ ANDROID_API void start() { mStagingPlayState = RUNNING; onStagingPlayStateChanged(); }
+ ANDROID_API void end() { mStagingPlayState = FINISHED; onStagingPlayStateChanged(); }
+
+ void attach(RenderNode* target);
+ virtual void onAttached() {}
+ void detach() { mTarget = 0; }
+ void pushStaging(TreeInfo& info);
+ bool animate(TreeInfo& info);
+
+ bool isFinished() { return mPlayState == FINISHED; }
+ float finalValue() { return mFinalValue; }
+
+ ANDROID_API virtual uint32_t dirtyMask() = 0;
+
+protected:
+ BaseRenderNodeAnimator(float finalValue);
+ virtual ~BaseRenderNodeAnimator();
+
+ virtual float getValue(RenderNode* target) const = 0;
+ virtual void setValue(RenderNode* target, float value) = 0;
+ RenderNode* target() { return mTarget; }
+
+ void callOnFinishedListener(TreeInfo& info);
+
+ virtual void onStagingPlayStateChanged() {}
+
+ enum PlayState {
+ NOT_STARTED,
+ RUNNING,
+ FINISHED,
+ };
+
+ RenderNode* mTarget;
+
+ float mFinalValue;
+ float mDeltaValue;
+ float mFromValue;
+
+ Interpolator* mInterpolator;
+ PlayState mStagingPlayState;
+ PlayState mPlayState;
+ bool mHasStartValue;
+ nsecs_t mStartTime;
+ nsecs_t mDuration;
+ nsecs_t mStartDelay;
+
+ sp<AnimationListener> mListener;
+
+private:
+ inline void checkMutable();
+ virtual void transitionToRunning(TreeInfo& info);
+ void doSetStartValue(float value);
+};
+
+class RenderPropertyAnimator : public BaseRenderNodeAnimator {
+public:
+ enum RenderProperty {
+ TRANSLATION_X = 0,
+ TRANSLATION_Y,
+ TRANSLATION_Z,
+ SCALE_X,
+ SCALE_Y,
+ ROTATION,
+ ROTATION_X,
+ ROTATION_Y,
+ X,
+ Y,
+ Z,
+ ALPHA,
+ };
+
+ ANDROID_API RenderPropertyAnimator(RenderProperty property, float finalValue);
+
+ ANDROID_API virtual uint32_t dirtyMask();
+
+protected:
+ virtual float getValue(RenderNode* target) const;
+ virtual void setValue(RenderNode* target, float value);
+ virtual void onAttached();
+ virtual void onStagingPlayStateChanged();
+
+private:
+ typedef bool (RenderProperties::*SetFloatProperty)(float value);
+ typedef float (RenderProperties::*GetFloatProperty)() const;
+
+ struct PropertyAccessors;
+ const PropertyAccessors* mPropertyAccess;
+
+ static const PropertyAccessors PROPERTY_ACCESSOR_LUT[];
+};
+
+class CanvasPropertyPrimitiveAnimator : public BaseRenderNodeAnimator {
+public:
+ ANDROID_API CanvasPropertyPrimitiveAnimator(CanvasPropertyPrimitive* property,
+ float finalValue);
+
+ ANDROID_API virtual uint32_t dirtyMask();
+
+protected:
+ virtual float getValue(RenderNode* target) const;
+ virtual void setValue(RenderNode* target, float value);
+private:
+ sp<CanvasPropertyPrimitive> mProperty;
+};
+
+class CanvasPropertyPaintAnimator : public BaseRenderNodeAnimator {
+public:
+ enum PaintField {
+ STROKE_WIDTH = 0,
+ ALPHA,
+ };
+
+ ANDROID_API CanvasPropertyPaintAnimator(CanvasPropertyPaint* property,
+ PaintField field, float finalValue);
+
+ ANDROID_API virtual uint32_t dirtyMask();
+
+protected:
+ virtual float getValue(RenderNode* target) const;
+ virtual void setValue(RenderNode* target, float value);
+private:
+ sp<CanvasPropertyPaint> mProperty;
+ PaintField mField;
+};
+
+class RevealAnimator : public BaseRenderNodeAnimator {
+public:
+ ANDROID_API RevealAnimator(int centerX, int centerY,
+ float startValue, float finalValue);
+
+ ANDROID_API virtual uint32_t dirtyMask();
+
+protected:
+ virtual float getValue(RenderNode* target) const;
+ virtual void setValue(RenderNode* target, float value);
+
+private:
+ int mCenterX, mCenterY;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* ANIMATOR_H */
diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp
new file mode 100644
index 0000000..7221295a4
--- /dev/null
+++ b/libs/hwui/AnimatorManager.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 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 "AnimatorManager.h"
+
+#include <algorithm>
+
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+using namespace std;
+
+static void unref(BaseRenderNodeAnimator* animator) {
+ animator->detach();
+ animator->decStrong(0);
+}
+
+AnimatorManager::AnimatorManager(RenderNode& parent)
+ : mParent(parent) {
+}
+
+AnimatorManager::~AnimatorManager() {
+ for_each(mNewAnimators.begin(), mNewAnimators.end(), unref);
+ for_each(mAnimators.begin(), mAnimators.end(), unref);
+}
+
+void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
+ animator->incStrong(0);
+ animator->attach(&mParent);
+ mNewAnimators.push_back(animator.get());
+}
+
+template<typename T>
+static void move_all(T& source, T& dest) {
+ dest.reserve(source.size() + dest.size());
+ for (typename T::iterator it = source.begin(); it != source.end(); it++) {
+ dest.push_back(*it);
+ }
+ source.clear();
+}
+
+void AnimatorManager::pushStaging(TreeInfo& info) {
+ if (mNewAnimators.size()) {
+ // Since this is a straight move, we don't need to inc/dec the ref count
+ move_all(mNewAnimators, mAnimators);
+ }
+ for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) {
+ (*it)->pushStaging(info);
+ }
+}
+
+class AnimateFunctor {
+public:
+ AnimateFunctor(RenderNode& target, TreeInfo& info)
+ : dirtyMask(0), mTarget(target), mInfo(info) {}
+
+ bool operator() (BaseRenderNodeAnimator* animator) {
+ dirtyMask |= animator->dirtyMask();
+ bool remove = animator->animate(mInfo);
+ if (remove) {
+ animator->decStrong(0);
+ }
+ return remove;
+ }
+
+ uint32_t dirtyMask;
+
+private:
+ RenderNode& mTarget;
+ TreeInfo& mInfo;
+};
+
+uint32_t AnimatorManager::animate(TreeInfo& info) {
+ if (!mAnimators.size()) return 0;
+
+ // TODO: Can we target this better? For now treat it like any other staging
+ // property push and just damage self before and after animators are run
+
+ mParent.damageSelf(info);
+ info.damageAccumulator->popTransform();
+
+ AnimateFunctor functor(mParent, info);
+ std::vector< BaseRenderNodeAnimator* >::iterator newEnd;
+ newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+ mAnimators.erase(newEnd, mAnimators.end());
+
+ mParent.mProperties.updateMatrix();
+ info.damageAccumulator->pushTransform(&mParent);
+ mParent.damageSelf(info);
+
+ return functor.dirtyMask;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h
new file mode 100644
index 0000000..0d177c5
--- /dev/null
+++ b/libs/hwui/AnimatorManager.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ANIMATORMANAGER_H
+#define ANIMATORMANAGER_H
+
+#include <vector>
+
+#include <cutils/compiler.h>
+#include <utils/StrongPointer.h>
+
+#include "TreeInfo.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+class BaseRenderNodeAnimator;
+class RenderNode;
+
+// Responsible for managing the animators for a single RenderNode
+class AnimatorManager {
+ PREVENT_COPY_AND_ASSIGN(AnimatorManager);
+public:
+ AnimatorManager(RenderNode& parent);
+ ~AnimatorManager();
+
+ void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
+
+ void pushStaging(TreeInfo& info);
+ // Returns the combined dirty mask of all animators run
+ uint32_t animate(TreeInfo& info);
+
+private:
+ RenderNode& mParent;
+
+ // To improve the efficiency of resizing & removing from the vector
+ // use manual ref counting instead of sp<>.
+ std::vector<BaseRenderNodeAnimator*> mNewAnimators;
+ std::vector<BaseRenderNodeAnimator*> mAnimators;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* ANIMATORMANAGER_H */
diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp
index e8c3d3c..fc86e4f 100644
--- a/libs/hwui/AssetAtlas.cpp
+++ b/libs/hwui/AssetAtlas.cpp
@@ -74,12 +74,12 @@ void AssetAtlas::terminate() {
// Entries
///////////////////////////////////////////////////////////////////////////////
-AssetAtlas::Entry* AssetAtlas::getEntry(SkBitmap* const bitmap) const {
+AssetAtlas::Entry* AssetAtlas::getEntry(const SkBitmap* bitmap) const {
ssize_t index = mEntries.indexOfKey(bitmap);
return index >= 0 ? mEntries.valueAt(index) : NULL;
}
-Texture* AssetAtlas::getEntryTexture(SkBitmap* const bitmap) const {
+Texture* AssetAtlas::getEntryTexture(const SkBitmap* bitmap) const {
ssize_t index = mEntries.indexOfKey(bitmap);
return index >= 0 ? mEntries.valueAt(index)->texture : NULL;
}
diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h
index 163bdbc..2ec556e 100644
--- a/libs/hwui/AssetAtlas.h
+++ b/libs/hwui/AssetAtlas.h
@@ -160,13 +160,13 @@ public:
* Returns the entry in the atlas associated with the specified
* bitmap. If the bitmap is not in the atlas, return NULL.
*/
- Entry* getEntry(SkBitmap* const bitmap) const;
+ Entry* getEntry(const SkBitmap* bitmap) const;
/**
* Returns the texture for the atlas entry associated with the
* specified bitmap. If the bitmap is not in the atlas, return NULL.
*/
- Texture* getEntryTexture(SkBitmap* const bitmap) const;
+ Texture* getEntryTexture(const SkBitmap* bitmap) const;
/**
* Returns the current generation id of the atlas.
@@ -186,7 +186,7 @@ private:
const bool mBlendKey;
const bool mOpaqueKey;
- KeyedVector<SkBitmap*, Entry*> mEntries;
+ KeyedVector<const SkBitmap*, Entry*> mEntries;
}; // class AssetAtlas
}; // namespace uirenderer
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index b0f4c2c..5689e17 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -23,6 +23,7 @@
#include "DisplayListRenderer.h"
#include "Properties.h"
#include "LayerRenderer.h"
+#include "ShadowTessellator.h"
namespace android {
@@ -55,6 +56,7 @@ Caches::Caches(): Singleton<Caches>(),
initProperties();
initStaticProperties();
initExtensions();
+ initTempProperties();
mDebugLevel = readDebugLevel();
ALOGD("Enabling debug mode %d", mDebugLevel);
@@ -85,7 +87,7 @@ bool Caches::init() {
mRegionMesh = NULL;
mMeshIndices = 0;
-
+ mShadowStripsIndices = 0;
blend = false;
lastSrcMode = GL_ZERO;
lastDstMode = GL_ZERO;
@@ -196,6 +198,7 @@ bool Caches::initProperties() {
drawDeferDisabled = !strcasecmp(property, "true");
INIT_LOGD(" Draw defer %s", drawDeferDisabled ? "disabled" : "enabled");
} else {
+ drawDeferDisabled = false;
INIT_LOGD(" Draw defer enabled");
}
@@ -203,6 +206,7 @@ bool Caches::initProperties() {
drawReorderDisabled = !strcasecmp(property, "true");
INIT_LOGD(" Draw reorder %s", drawReorderDisabled ? "disabled" : "enabled");
} else {
+ drawReorderDisabled = false;
INIT_LOGD(" Draw reorder enabled");
}
@@ -222,6 +226,9 @@ void Caches::terminate() {
mMeshIndices = 0;
mRegionMesh = NULL;
+ glDeleteBuffers(1, &mShadowStripsIndices);
+ mShadowStripsIndices = 0;
+
fboCache.clear();
programCache.clear();
@@ -268,6 +275,8 @@ void Caches::dumpMemoryUsage(String8 &log) {
gradientCache.getSize(), gradientCache.getMaxSize());
log.appendFormat(" PathCache %8d / %8d\n",
pathCache.getSize(), pathCache.getMaxSize());
+ log.appendFormat(" TessellationCache %8d / %8d\n",
+ tessellationCache.getSize(), tessellationCache.getMaxSize());
log.appendFormat(" TextDropShadowCache %8d / %8d\n", dropShadowCache.getSize(),
dropShadowCache.getMaxSize());
log.appendFormat(" PatchCache %8d / %8d\n",
@@ -290,6 +299,7 @@ void Caches::dumpMemoryUsage(String8 &log) {
total += renderBufferCache.getSize();
total += gradientCache.getSize();
total += pathCache.getSize();
+ total += tessellationCache.getSize();
total += dropShadowCache.getSize();
total += patchCache.getSize();
for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) {
@@ -310,24 +320,15 @@ void Caches::clearGarbage() {
pathCache.clearGarbage();
patchCache.clearGarbage();
- Vector<DisplayList*> displayLists;
Vector<Layer*> layers;
{ // scope for the lock
Mutex::Autolock _l(mGarbageLock);
- displayLists = mDisplayListGarbage;
layers = mLayerGarbage;
- mDisplayListGarbage.clear();
mLayerGarbage.clear();
}
- size_t count = displayLists.size();
- for (size_t i = 0; i < count; i++) {
- DisplayList* displayList = displayLists.itemAt(i);
- delete displayList;
- }
-
- count = layers.size();
+ size_t count = layers.size();
for (size_t i = 0; i < count; i++) {
Layer* layer = layers.itemAt(i);
delete layer;
@@ -340,11 +341,6 @@ void Caches::deleteLayerDeferred(Layer* layer) {
mLayerGarbage.push(layer);
}
-void Caches::deleteDisplayListDeferred(DisplayList* displayList) {
- Mutex::Autolock _l(mGarbageLock);
- mDisplayListGarbage.push(displayList);
-}
-
void Caches::flush(FlushMode mode) {
FLUSH_LOGD("Flushing caches (mode %d)", mode);
@@ -367,6 +363,7 @@ void Caches::flush(FlushMode mode) {
fontRenderer->flush();
textureCache.flush();
pathCache.clear();
+ tessellationCache.clear();
// fall through
case kFlushMode_Layers:
layerCache.clear();
@@ -403,7 +400,7 @@ bool Caches::unbindMeshBuffer() {
return false;
}
-bool Caches::bindIndicesBuffer(const GLuint buffer) {
+bool Caches::bindIndicesBufferInternal(const GLuint buffer) {
if (mCurrentIndicesBuffer != buffer) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
mCurrentIndicesBuffer = buffer;
@@ -412,7 +409,7 @@ bool Caches::bindIndicesBuffer(const GLuint buffer) {
return false;
}
-bool Caches::bindIndicesBuffer() {
+bool Caches::bindQuadIndicesBuffer() {
if (!mMeshIndices) {
uint16_t* regionIndices = new uint16_t[gMaxNumberOfQuads * 6];
for (uint32_t i = 0; i < gMaxNumberOfQuads; i++) {
@@ -427,7 +424,7 @@ bool Caches::bindIndicesBuffer() {
}
glGenBuffers(1, &mMeshIndices);
- bool force = bindIndicesBuffer(mMeshIndices);
+ bool force = bindIndicesBufferInternal(mMeshIndices);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, gMaxNumberOfQuads * 6 * sizeof(uint16_t),
regionIndices, GL_STATIC_DRAW);
@@ -435,7 +432,23 @@ bool Caches::bindIndicesBuffer() {
return force;
}
- return bindIndicesBuffer(mMeshIndices);
+ return bindIndicesBufferInternal(mMeshIndices);
+}
+
+bool Caches::bindShadowIndicesBuffer() {
+ if (!mShadowStripsIndices) {
+ uint16_t* shadowIndices = new uint16_t[MAX_SHADOW_INDEX_COUNT];
+ ShadowTessellator::generateShadowIndices(shadowIndices);
+ glGenBuffers(1, &mShadowStripsIndices);
+ bool force = bindIndicesBufferInternal(mShadowStripsIndices);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_SHADOW_INDEX_COUNT * sizeof(uint16_t),
+ shadowIndices, GL_STATIC_DRAW);
+
+ delete[] shadowIndices;
+ return force;
+ }
+
+ return bindIndicesBufferInternal(mShadowStripsIndices);
}
bool Caches::unbindIndicesBuffer() {
@@ -473,7 +486,7 @@ bool Caches::unbindPixelBuffer() {
// Meshes and textures
///////////////////////////////////////////////////////////////////////////////
-void Caches::bindPositionVertexPointer(bool force, GLvoid* vertices, GLsizei stride) {
+void Caches::bindPositionVertexPointer(bool force, const GLvoid* vertices, GLsizei stride) {
if (force || vertices != mCurrentPositionPointer || stride != mCurrentPositionStride) {
GLuint slot = currentProgram->position;
glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, stride, vertices);
@@ -482,7 +495,7 @@ void Caches::bindPositionVertexPointer(bool force, GLvoid* vertices, GLsizei str
}
}
-void Caches::bindTexCoordsVertexPointer(bool force, GLvoid* vertices, GLsizei stride) {
+void Caches::bindTexCoordsVertexPointer(bool force, const GLvoid* vertices, GLsizei stride) {
if (force || vertices != mCurrentTexCoordsPointer || stride != mCurrentTexCoordsStride) {
GLuint slot = currentProgram->texCoords;
glVertexAttribPointer(slot, 2, GL_FLOAT, GL_FALSE, stride, vertices);
@@ -681,5 +694,48 @@ TextureVertex* Caches::getRegionMesh() {
return mRegionMesh;
}
+///////////////////////////////////////////////////////////////////////////////
+// Temporary Properties
+///////////////////////////////////////////////////////////////////////////////
+
+void Caches::initTempProperties() {
+ propertyLightDiameter = -1.0f;
+ propertyLightPosY = -1.0f;
+ propertyLightPosZ = -1.0f;
+ propertyAmbientRatio = -1.0f;
+ propertyAmbientShadowStrength = -1;
+ propertySpotShadowStrength = -1;
+}
+
+void Caches::setTempProperty(const char* name, const char* value) {
+ ALOGD("setting property %s to %s", name, value);
+ if (!strcmp(name, "ambientRatio")) {
+ propertyAmbientRatio = fmin(fmax(atof(value), 0.0), 10.0);
+ ALOGD("ambientRatio = %.2f", propertyAmbientRatio);
+ return;
+ } else if (!strcmp(name, "lightDiameter")) {
+ propertyLightDiameter = fmin(fmax(atof(value), 0.0), 3000.0);
+ ALOGD("lightDiameter = %.2f", propertyLightDiameter);
+ return;
+ } else if (!strcmp(name, "lightPosY")) {
+ propertyLightPosY = fmin(fmax(atof(value), 0.0), 3000.0);
+ ALOGD("lightPos Y = %.2f", propertyLightPosY);
+ return;
+ } else if (!strcmp(name, "lightPosZ")) {
+ propertyLightPosZ = fmin(fmax(atof(value), 0.0), 3000.0);
+ ALOGD("lightPos Z = %.2f", propertyLightPosZ);
+ return;
+ } else if (!strcmp(name, "ambientShadowStrength")) {
+ propertyAmbientShadowStrength = atoi(value);
+ ALOGD("ambient shadow strength = 0x%x out of 0xff", propertyAmbientShadowStrength);
+ return;
+ } else if (!strcmp(name, "spotShadowStrength")) {
+ propertySpotShadowStrength = atoi(value);
+ ALOGD("spot shadow strength = 0x%x out of 0xff", propertySpotShadowStrength);
+ return;
+ }
+ ALOGD(" failed");
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 544757a..0482430 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -21,6 +21,8 @@
#define LOG_TAG "OpenGLRenderer"
#endif
+#include <vector>
+
#include <GLES3/gl3.h>
#include <utils/KeyedVector.h>
@@ -33,6 +35,7 @@
#include "thread/TaskManager.h"
#include "AssetAtlas.h"
+#include "Extensions.h"
#include "FontRenderer.h"
#include "GammaFontRenderer.h"
#include "TextureCache.h"
@@ -42,6 +45,7 @@
#include "PatchCache.h"
#include "ProgramCache.h"
#include "PathCache.h"
+#include "TessellationCache.h"
#include "TextDropShadowCache.h"
#include "FboCache.h"
#include "ResourceCache.h"
@@ -62,7 +66,7 @@ namespace uirenderer {
static const uint32_t gMaxNumberOfQuads = 2048;
// Generates simple and textured vertices
-#define FV(x, y, u, v) { { x, y }, { u, v } }
+#define FV(x, y, u, v) { x, y, u, v }
// This array is never used directly but used as a memcpy source in the
// OpenGLRenderer constructor
@@ -102,7 +106,7 @@ struct CacheLogger {
// Caches
///////////////////////////////////////////////////////////////////////////////
-class DisplayList;
+class RenderNode;
class ANDROID_API Caches: public Singleton<Caches> {
Caches();
@@ -166,11 +170,6 @@ public:
*/
void deleteLayerDeferred(Layer* layer);
- /*
- * Can be used to delete a display list from a non EGL thread.
- */
- void deleteDisplayListDeferred(DisplayList* layer);
-
/**
* Binds the VBO used to render simple textured quads.
*/
@@ -190,8 +189,8 @@ public:
* Binds a global indices buffer that can draw up to
* gMaxNumberOfQuads quads.
*/
- bool bindIndicesBuffer();
- bool bindIndicesBuffer(const GLuint buffer);
+ bool bindQuadIndicesBuffer();
+ bool bindShadowIndicesBuffer();
bool unbindIndicesBuffer();
/**
@@ -208,13 +207,13 @@ public:
* Binds an attrib to the specified float vertex pointer.
* Assumes a stride of gMeshStride and a size of 2.
*/
- void bindPositionVertexPointer(bool force, GLvoid* vertices, GLsizei stride = gMeshStride);
+ void bindPositionVertexPointer(bool force, const GLvoid* vertices, GLsizei stride = gMeshStride);
/**
* Binds an attrib to the specified float vertex pointer.
* Assumes a stride of gMeshStride and a size of 2.
*/
- void bindTexCoordsVertexPointer(bool force, GLvoid* vertices, GLsizei stride = gMeshStride);
+ void bindTexCoordsVertexPointer(bool force, const GLvoid* vertices, GLsizei stride = gMeshStride);
/**
* Resets the vertex pointers.
@@ -335,6 +334,7 @@ public:
ProgramCache programCache;
PathCache pathCache;
PatchCache patchCache;
+ TessellationCache tessellationCache;
TextDropShadowCache dropShadowCache;
FboCache fboCache;
ResourceCache resourceCache;
@@ -358,6 +358,17 @@ public:
PFNGLLABELOBJECTEXTPROC setLabel;
PFNGLGETOBJECTLABELEXTPROC getLabel;
+ // TEMPORARY properties
+ void initTempProperties();
+ void setTempProperty(const char* name, const char* value);
+
+ float propertyLightDiameter;
+ float propertyLightPosY;
+ float propertyLightPosZ;
+ float propertyAmbientRatio;
+ int propertyAmbientShadowStrength;
+ int propertySpotShadowStrength;
+
private:
enum OverdrawColorSet {
kColorSet_Default = 0,
@@ -369,6 +380,8 @@ private:
void initConstraints();
void initStaticProperties();
+ bool bindIndicesBufferInternal(const GLuint buffer);
+
static void eventMarkNull(GLsizei length, const GLchar* marker) { }
static void startMarkNull(GLsizei length, const GLchar* marker) { }
static void endMarkNull() { }
@@ -384,9 +397,9 @@ private:
GLuint mCurrentBuffer;
GLuint mCurrentIndicesBuffer;
GLuint mCurrentPixelBuffer;
- void* mCurrentPositionPointer;
+ const void* mCurrentPositionPointer;
GLsizei mCurrentPositionStride;
- void* mCurrentTexCoordsPointer;
+ const void* mCurrentTexCoordsPointer;
GLsizei mCurrentTexCoordsStride;
bool mTexCoordsArrayEnabled;
@@ -405,10 +418,10 @@ private:
// Global index buffer
GLuint mMeshIndices;
+ GLuint mShadowStripsIndices;
mutable Mutex mGarbageLock;
Vector<Layer*> mLayerGarbage;
- Vector<DisplayList*> mDisplayListGarbage;
DebugLevel mDebugLevel;
bool mInitialized;
diff --git a/libs/hwui/CanvasProperty.h b/libs/hwui/CanvasProperty.h
new file mode 100644
index 0000000..6074394
--- /dev/null
+++ b/libs/hwui/CanvasProperty.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CANVASPROPERTY_H
+#define CANVASPROPERTY_H
+
+#include <utils/RefBase.h>
+
+#include "utils/Macros.h"
+
+#include <SkPaint.h>
+
+namespace android {
+namespace uirenderer {
+
+class CanvasPropertyPrimitive : public VirtualLightRefBase {
+ PREVENT_COPY_AND_ASSIGN(CanvasPropertyPrimitive);
+public:
+ CanvasPropertyPrimitive(float initialValue) : value(initialValue) {}
+
+ float value;
+};
+
+class CanvasPropertyPaint : public VirtualLightRefBase {
+ PREVENT_COPY_AND_ASSIGN(CanvasPropertyPaint);
+public:
+ CanvasPropertyPaint(const SkPaint& initialValue) : value(initialValue) {}
+
+ SkPaint value;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* CANVASPROPERTY_H */
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
new file mode 100644
index 0000000..054a164
--- /dev/null
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2014 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 "DamageAccumulator.h"
+
+#include <cutils/log.h>
+
+#include "RenderNode.h"
+#include "utils/MathUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+enum TransformType {
+ TransformInvalid = 0,
+ TransformRenderNode,
+ TransformMatrix4,
+ TransformNone,
+};
+
+struct DirtyStack {
+ TransformType type;
+ union {
+ const RenderNode* renderNode;
+ const Matrix4* matrix4;
+ };
+ // When this frame is pop'd, this rect is mapped through the above transform
+ // and applied to the previous (aka parent) frame
+ SkRect pendingDirty;
+ DirtyStack* prev;
+ DirtyStack* next;
+};
+
+DamageAccumulator::DamageAccumulator() {
+ mHead = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack));
+ memset(mHead, 0, sizeof(DirtyStack));
+ // Create a root that we will not pop off
+ mHead->prev = mHead;
+ mHead->type = TransformNone;
+}
+
+static void computeTransformImpl(const DirtyStack* currentFrame, Matrix4* outMatrix) {
+ if (currentFrame->prev != currentFrame) {
+ computeTransformImpl(currentFrame->prev, outMatrix);
+ }
+ switch (currentFrame->type) {
+ case TransformRenderNode:
+ currentFrame->renderNode->applyViewPropertyTransforms(*outMatrix);
+ break;
+ case TransformMatrix4:
+ outMatrix->multiply(*currentFrame->matrix4);
+ break;
+ case TransformNone:
+ // nothing to be done
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d", currentFrame->type);
+ }
+}
+
+void DamageAccumulator::computeCurrentTransform(Matrix4* outMatrix) const {
+ outMatrix->loadIdentity();
+ computeTransformImpl(mHead, outMatrix);
+}
+
+void DamageAccumulator::pushCommon() {
+ if (!mHead->next) {
+ DirtyStack* nextFrame = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack));
+ nextFrame->next = 0;
+ nextFrame->prev = mHead;
+ mHead->next = nextFrame;
+ }
+ mHead = mHead->next;
+ mHead->pendingDirty.setEmpty();
+}
+
+void DamageAccumulator::pushTransform(const RenderNode* transform) {
+ pushCommon();
+ mHead->type = TransformRenderNode;
+ mHead->renderNode = transform;
+}
+
+void DamageAccumulator::pushTransform(const Matrix4* transform) {
+ pushCommon();
+ mHead->type = TransformMatrix4;
+ mHead->matrix4 = transform;
+}
+
+void DamageAccumulator::popTransform() {
+ LOG_ALWAYS_FATAL_IF(mHead->prev == mHead, "Cannot pop the root frame!");
+ DirtyStack* dirtyFrame = mHead;
+ mHead = mHead->prev;
+ switch (dirtyFrame->type) {
+ case TransformRenderNode:
+ applyRenderNodeTransform(dirtyFrame);
+ break;
+ case TransformMatrix4:
+ applyMatrix4Transform(dirtyFrame);
+ break;
+ case TransformNone:
+ mHead->pendingDirty.join(dirtyFrame->pendingDirty);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Tried to pop an invalid type: %d", dirtyFrame->type);
+ }
+}
+
+static inline void mapRect(const Matrix4* matrix, const SkRect& in, SkRect* out) {
+ if (in.isEmpty()) return;
+ Rect temp(in);
+ matrix->mapRect(temp);
+ out->join(RECT_ARGS(temp));
+}
+
+void DamageAccumulator::applyMatrix4Transform(DirtyStack* frame) {
+ mapRect(frame->matrix4, frame->pendingDirty, &mHead->pendingDirty);
+}
+
+static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRect* out) {
+ if (in.isEmpty()) return;
+ const SkMatrix* transform = props.getTransformMatrix();
+ SkRect temp(in);
+ if (transform && !transform->isIdentity()) {
+ transform->mapRect(&temp);
+ }
+ temp.offset(props.getLeft(), props.getTop());
+ out->join(temp);
+}
+
+static DirtyStack* findParentRenderNode(DirtyStack* frame) {
+ while (frame->prev != frame) {
+ frame = frame->prev;
+ if (frame->type == TransformRenderNode) {
+ return frame;
+ }
+ }
+ return NULL;
+}
+
+static DirtyStack* findProjectionReceiver(DirtyStack* frame) {
+ if (frame) {
+ while (frame->prev != frame) {
+ frame = frame->prev;
+ if (frame->type == TransformRenderNode
+ && frame->renderNode->hasProjectionReceiver()) {
+ return frame;
+ }
+ }
+ }
+ return NULL;
+}
+
+static void applyTransforms(DirtyStack* frame, DirtyStack* end) {
+ SkRect* rect = &frame->pendingDirty;
+ while (frame != end) {
+ if (frame->type == TransformRenderNode) {
+ mapRect(frame->renderNode->properties(), *rect, rect);
+ } else {
+ mapRect(frame->matrix4, *rect, rect);
+ }
+ frame = frame->prev;
+ }
+}
+
+void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) {
+ if (frame->pendingDirty.isEmpty()) {
+ return;
+ }
+
+ const RenderProperties& props = frame->renderNode->properties();
+ if (props.getAlpha() <= 0) {
+ return;
+ }
+
+ // Perform clipping
+ if (props.getClipDamageToBounds() && !frame->pendingDirty.isEmpty()) {
+ if (!frame->pendingDirty.intersect(0, 0, props.getWidth(), props.getHeight())) {
+ frame->pendingDirty.setEmpty();
+ }
+ }
+
+ // apply all transforms
+ mapRect(props, frame->pendingDirty, &mHead->pendingDirty);
+
+ // project backwards if necessary
+ if (props.getProjectBackwards() && !frame->pendingDirty.isEmpty()) {
+ // First, find our parent RenderNode:
+ DirtyStack* parentNode = findParentRenderNode(frame);
+ // Find our parent's projection receiver, which is what we project onto
+ DirtyStack* projectionReceiver = findProjectionReceiver(parentNode);
+ if (projectionReceiver) {
+ applyTransforms(frame, projectionReceiver);
+ projectionReceiver->pendingDirty.join(frame->pendingDirty);
+ }
+
+ frame->pendingDirty.setEmpty();
+ }
+}
+
+void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
+ mHead->pendingDirty.join(left, top, right, bottom);
+}
+
+void DamageAccumulator::peekAtDirty(SkRect* dest) {
+ *dest = mHead->pendingDirty;
+}
+
+void DamageAccumulator::finish(SkRect* totalDirty) {
+ LOG_ALWAYS_FATAL_IF(mHead->prev != mHead, "Cannot finish, mismatched push/pop calls! %p vs. %p", mHead->prev, mHead);
+ // Root node never has a transform, so this is the fully mapped dirty rect
+ *totalDirty = mHead->pendingDirty;
+ totalDirty->roundOut();
+ mHead->pendingDirty.setEmpty();
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
new file mode 100644
index 0000000..6f0bd8c
--- /dev/null
+++ b/libs/hwui/DamageAccumulator.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef DAMAGEACCUMULATOR_H
+#define DAMAGEACCUMULATOR_H
+
+#include <cutils/compiler.h>
+#include <utils/LinearAllocator.h>
+
+#include <SkMatrix.h>
+#include <SkRect.h>
+
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+struct DirtyStack;
+class RenderNode;
+class Matrix4;
+
+class DamageAccumulator {
+ PREVENT_COPY_AND_ASSIGN(DamageAccumulator);
+public:
+ DamageAccumulator();
+ // mAllocator will clean everything up for us, no need for a dtor
+
+ // Push a transform node onto the stack. This should be called prior
+ // to any dirty() calls. Subsequent calls to dirty()
+ // will be affected by the transform when popTransform() is called.
+ void pushTransform(const RenderNode* transform);
+ void pushTransform(const Matrix4* transform);
+
+ // Pops a transform node from the stack, propagating the dirty rect
+ // up to the parent node. Returns the IDamageTransform that was just applied
+ void popTransform();
+
+ void dirty(float left, float top, float right, float bottom);
+
+ // Returns the current dirty area, *NOT* transformed by pushed transforms
+ void peekAtDirty(SkRect* dest);
+
+ void computeCurrentTransform(Matrix4* outMatrix) const;
+
+ void finish(SkRect* totalDirty);
+
+private:
+ void pushCommon();
+ void applyMatrix4Transform(DirtyStack* frame);
+ void applyRenderNodeTransform(DirtyStack* frame);
+
+ LinearAllocator mAllocator;
+ DirtyStack* mHead;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* DAMAGEACCUMULATOR_H */
diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h
index 786f12a..5808aac 100644
--- a/libs/hwui/Debug.h
+++ b/libs/hwui/Debug.h
@@ -82,9 +82,15 @@
// Turn on to insert an event marker for each display list op
#define DEBUG_DISPLAY_LIST_OPS_AS_EVENTS 0
+// Turn on to insert detailed event markers
+#define DEBUG_DETAILED_EVENTS 0
+
// Turn on to highlight drawing batches and merged batches with different colors
#define DEBUG_MERGE_BEHAVIOR 0
+// Turn on to enable debugging shadow
+#define DEBUG_SHADOW 0
+
#if DEBUG_INIT
#define INIT_LOGD(...) ALOGD(__VA_ARGS__)
#else
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index 7eb7028..a998594 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -28,6 +28,7 @@
#include "DeferredDisplayList.h"
#include "DisplayListOp.h"
#include "OpenGLRenderer.h"
+#include "utils/MathUtils.h"
#if DEBUG_DEFER
#define DEFER_LOGD(...) ALOGD(__VA_ARGS__)
@@ -146,10 +147,6 @@ private:
mergeid_t mMergeId;
};
-// compare alphas approximately, with a small margin
-#define NEQ_FALPHA(lhs, rhs) \
- fabs((float)lhs - (float)rhs) > 0.001f
-
class MergingDrawBatch : public DrawBatch {
public:
MergingDrawBatch(DeferInfo& deferInfo, int width, int height) :
@@ -190,13 +187,17 @@ public:
// Overlapping other operations is only allowed for text without shadow. For other ops,
// multiDraw isn't guaranteed to overdraw correctly
- if (!isTextBatch || state->mDrawModifiers.mHasShadow) {
+ if (!isTextBatch || op->hasTextShadow()) {
if (intersects(state->mBounds)) return false;
}
const DeferredDisplayState* lhs = state;
const DeferredDisplayState* rhs = mOps[0].state;
- if (NEQ_FALPHA(lhs->mAlpha, rhs->mAlpha)) return false;
+ if (!MathUtils::areEqual(lhs->mAlpha, rhs->mAlpha)) return false;
+
+ // Identical round rect clip state means both ops will clip in the same way, or not at all.
+ // As the state objects are const, we can compare their pointers to determine mergeability
+ if (lhs->mRoundRectClipState != rhs->mRoundRectClipState) return false;
/* Clipping compatibility check
*
@@ -224,6 +225,16 @@ public:
if (op->getPaintAlpha() != mOps[0].op->getPaintAlpha()) return false;
+ if (op->mPaint && mOps[0].op->mPaint &&
+ op->mPaint->getColorFilter() != mOps[0].op->mPaint->getColorFilter()) {
+ return false;
+ }
+
+ if (op->mPaint && mOps[0].op->mPaint &&
+ op->mPaint->getShader() != mOps[0].op->mPaint->getShader()) {
+ return false;
+ }
+
/* Draw Modifiers compatibility check
*
* Shadows are ignored, as only text uses them, and in that case they are drawn
@@ -238,8 +249,6 @@ public:
*/
const DrawModifiers& lhsMod = lhs->mDrawModifiers;
const DrawModifiers& rhsMod = rhs->mDrawModifiers;
- if (lhsMod.mShader != rhsMod.mShader) return false;
- if (lhsMod.mColorFilter != rhsMod.mColorFilter) return false;
// Draw filter testing expects bit fields to be clear if filter not set.
if (lhsMod.mHasDrawFilter != rhsMod.mHasDrawFilter) return false;
@@ -491,7 +500,7 @@ void DeferredDisplayList::addRestoreToCount(OpenGLRenderer& renderer, StateOp* o
void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) {
/* 1: op calculates local bounds */
DeferredDisplayState* const state = createState();
- if (op->getLocalBounds(renderer.getDrawModifiers(), state->mBounds)) {
+ if (op->getLocalBounds(state->mBounds)) {
if (state->mBounds.isEmpty()) {
// valid empty bounds, don't bother deferring
tryRecycleState(state);
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 3dcbd0b..8a015b2 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -38,7 +38,6 @@ class SaveLayerOp;
class StateOp;
class DeferredDisplayState;
-class OpenGLRenderer;
class Batch;
class DrawBatch;
@@ -64,6 +63,7 @@ public:
mat4 mMatrix;
DrawModifiers mDrawModifiers;
float mAlpha;
+ const RoundRectClipState* mRoundRectClipState;
};
class OpStatePair {
@@ -79,13 +79,13 @@ public:
};
class DeferredDisplayList {
+ friend class DeferStateStruct; // used to give access to allocator
public:
DeferredDisplayList(const Rect& bounds, bool avoidOverdraw = true) :
mBounds(bounds), mAvoidOverdraw(avoidOverdraw) {
clear();
}
~DeferredDisplayList() { clear(); }
- void reset(const Rect& bounds) { mBounds.set(bounds); }
enum OpBatchId {
kOpBatch_None = 0, // Don't batch
@@ -120,6 +120,8 @@ public:
void addDrawOp(OpenGLRenderer& renderer, DrawOp* op);
private:
+ DeferredDisplayList(const DeferredDisplayList& other); // disallow copy
+
DeferredDisplayState* createState() {
return new (mAllocator) DeferredDisplayState();
}
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
new file mode 100644
index 0000000..836de45
--- /dev/null
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 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 "DeferredLayerUpdater.h"
+
+#include "OpenGLRenderer.h"
+
+#include "LayerRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+static void defaultLayerDestroyer(Layer* layer) {
+ Caches::getInstance().resourceCache.decrementRefcount(layer);
+}
+
+DeferredLayerUpdater::DeferredLayerUpdater(Layer* layer, LayerDestroyer destroyer)
+ : mSurfaceTexture(0)
+ , mTransform(0)
+ , mNeedsGLContextAttach(false)
+ , mUpdateTexImage(false)
+ , mLayer(layer)
+ , mCaches(Caches::getInstance())
+ , mDestroyer(destroyer) {
+ mWidth = mLayer->layer.getWidth();
+ mHeight = mLayer->layer.getHeight();
+ mBlend = mLayer->isBlend();
+ mColorFilter = SkSafeRef(mLayer->getColorFilter());
+ mAlpha = mLayer->getAlpha();
+ mMode = mLayer->getMode();
+
+ if (!mDestroyer) {
+ mDestroyer = defaultLayerDestroyer;
+ }
+}
+
+DeferredLayerUpdater::~DeferredLayerUpdater() {
+ SkSafeUnref(mColorFilter);
+ setTransform(0);
+ mDestroyer(mLayer);
+}
+
+void DeferredLayerUpdater::setPaint(const SkPaint* paint) {
+ OpenGLRenderer::getAlphaAndModeDirect(paint, &mAlpha, &mMode);
+ SkColorFilter* colorFilter = (paint) ? paint->getColorFilter() : NULL;
+ SkRefCnt_SafeAssign(mColorFilter, colorFilter);
+}
+
+bool DeferredLayerUpdater::apply() {
+ bool success = true;
+ // These properties are applied the same to both layer types
+ mLayer->setColorFilter(mColorFilter);
+ mLayer->setAlpha(mAlpha, mMode);
+
+ if (mSurfaceTexture.get()) {
+ if (mNeedsGLContextAttach) {
+ mNeedsGLContextAttach = false;
+ mSurfaceTexture->attachToContext(mLayer->getTexture());
+ }
+ if (mUpdateTexImage) {
+ mUpdateTexImage = false;
+ doUpdateTexImage();
+ }
+ if (mTransform) {
+ mLayer->getTransform().load(*mTransform);
+ setTransform(0);
+ }
+ }
+ return success;
+}
+
+void DeferredLayerUpdater::doUpdateTexImage() {
+ if (mSurfaceTexture->updateTexImage() == NO_ERROR) {
+ float transform[16];
+
+ int64_t frameNumber = mSurfaceTexture->getFrameNumber();
+ // If the GLConsumer queue is in synchronous mode, need to discard all
+ // but latest frame, using the frame number to tell when we no longer
+ // have newer frames to target. Since we can't tell which mode it is in,
+ // do this unconditionally.
+ int dropCounter = 0;
+ while (mSurfaceTexture->updateTexImage() == NO_ERROR) {
+ int64_t newFrameNumber = mSurfaceTexture->getFrameNumber();
+ if (newFrameNumber == frameNumber) break;
+ frameNumber = newFrameNumber;
+ dropCounter++;
+ }
+
+ bool forceFilter = false;
+ sp<GraphicBuffer> buffer = mSurfaceTexture->getCurrentBuffer();
+ if (buffer != NULL) {
+ // force filtration if buffer size != layer size
+ forceFilter = mWidth != buffer->getWidth()
+ || mHeight != buffer->getHeight();
+ }
+
+ #if DEBUG_RENDERER
+ if (dropCounter > 0) {
+ RENDERER_LOGD("Dropped %d frames on texture layer update", dropCounter);
+ }
+ #endif
+ mSurfaceTexture->getTransformMatrix(transform);
+ GLenum renderTarget = mSurfaceTexture->getCurrentTextureTarget();
+
+ LayerRenderer::updateTextureLayer(mLayer, mWidth, mHeight,
+ !mBlend, forceFilter, renderTarget, transform);
+ }
+}
+
+void DeferredLayerUpdater::detachSurfaceTexture() {
+ if (mSurfaceTexture.get()) {
+ mSurfaceTexture->detachFromContext();
+ mSurfaceTexture = 0;
+ mLayer->clearTexture();
+ }
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
new file mode 100644
index 0000000..c838c32
--- /dev/null
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef DEFERREDLAYERUPDATE_H_
+#define DEFERREDLAYERUPDATE_H_
+
+#include <cutils/compiler.h>
+#include <gui/GLConsumer.h>
+#include <SkColorFilter.h>
+#include <SkMatrix.h>
+#include <utils/StrongPointer.h>
+
+#include "Layer.h"
+#include "Rect.h"
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+typedef void (*LayerDestroyer)(Layer* layer);
+
+// Container to hold the properties a layer should be set to at the start
+// of a render pass
+class DeferredLayerUpdater : public VirtualLightRefBase {
+public:
+ // Note that DeferredLayerUpdater assumes it is taking ownership of the layer
+ // and will not call incrementRef on it as a result.
+ ANDROID_API DeferredLayerUpdater(Layer* layer, LayerDestroyer = 0);
+ ANDROID_API ~DeferredLayerUpdater();
+
+ ANDROID_API bool setSize(uint32_t width, uint32_t height) {
+ if (mWidth != width || mHeight != height) {
+ mWidth = width;
+ mHeight = height;
+ return true;
+ }
+ return false;
+ }
+
+ ANDROID_API bool setBlend(bool blend) {
+ if (blend != mBlend) {
+ mBlend = blend;
+ return true;
+ }
+ return false;
+ }
+
+ ANDROID_API void setSurfaceTexture(const sp<GLConsumer>& texture, bool needsAttach) {
+ if (texture.get() != mSurfaceTexture.get()) {
+ mNeedsGLContextAttach = needsAttach;
+ mSurfaceTexture = texture;
+ }
+ }
+
+ ANDROID_API void updateTexImage() {
+ mUpdateTexImage = true;
+ }
+
+ ANDROID_API void setTransform(const SkMatrix* matrix) {
+ delete mTransform;
+ mTransform = matrix ? new SkMatrix(*matrix) : 0;
+ }
+
+ ANDROID_API void setPaint(const SkPaint* paint);
+
+ ANDROID_API bool apply();
+
+ ANDROID_API Layer* backingLayer() {
+ return mLayer;
+ }
+
+ ANDROID_API void detachSurfaceTexture();
+
+private:
+ // Generic properties
+ uint32_t mWidth;
+ uint32_t mHeight;
+ bool mBlend;
+ SkColorFilter* mColorFilter;
+ int mAlpha;
+ SkXfermode::Mode mMode;
+
+ sp<GLConsumer> mSurfaceTexture;
+ SkMatrix* mTransform;
+ bool mNeedsGLContextAttach;
+ bool mUpdateTexImage;
+
+ Layer* mLayer;
+ Caches& mCaches;
+
+ LayerDestroyer mDestroyer;
+
+ void doUpdateTexImage();
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* DEFERREDLAYERUPDATE_H_ */
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index bb6526e..6461ee7 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -14,7 +14,12 @@
* limitations under the License.
*/
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
#include <SkCanvas.h>
+#include <algorithm>
+
+#include <utils/Trace.h>
#include "Debug.h"
#include "DisplayList.h"
@@ -24,533 +29,71 @@
namespace android {
namespace uirenderer {
-void DisplayList::outputLogBuffer(int fd) {
- DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
- if (logBuffer.isEmpty()) {
- return;
- }
-
- FILE *file = fdopen(fd, "a");
-
- fprintf(file, "\nRecent DisplayList operations\n");
- logBuffer.outputCommands(file);
-
- String8 cachesLog;
- Caches::getInstance().dumpMemoryUsage(cachesLog);
- fprintf(file, "\nCaches:\n%s", cachesLog.string());
- fprintf(file, "\n");
-
- fflush(file);
-}
-
-DisplayList::DisplayList(const DisplayListRenderer& recorder) :
- mDestroyed(false), mTransformMatrix(NULL), mTransformCamera(NULL), mTransformMatrix3D(NULL),
- mStaticMatrix(NULL), mAnimationMatrix(NULL) {
-
- initFromDisplayListRenderer(recorder);
-}
-
-DisplayList::~DisplayList() {
- mDestroyed = true;
- clearResources();
-}
-
-void DisplayList::destroyDisplayListDeferred(DisplayList* displayList) {
- if (displayList) {
- DISPLAY_LIST_LOGD("Deferring display list destruction");
- Caches::getInstance().deleteDisplayListDeferred(displayList);
- }
-}
-
-void DisplayList::clearResources() {
- mDisplayListData = NULL;
-
- mClipRectOp = NULL;
- mSaveLayerOp = NULL;
- mSaveOp = NULL;
- mRestoreToCountOp = NULL;
-
- delete mTransformMatrix;
- delete mTransformCamera;
- delete mTransformMatrix3D;
- delete mStaticMatrix;
- delete mAnimationMatrix;
-
- mTransformMatrix = NULL;
- mTransformCamera = NULL;
- mTransformMatrix3D = NULL;
- mStaticMatrix = NULL;
- mAnimationMatrix = NULL;
-
- Caches& caches = Caches::getInstance();
- caches.unregisterFunctors(mFunctorCount);
- caches.resourceCache.lock();
-
- for (size_t i = 0; i < mBitmapResources.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mBitmapResources.itemAt(i));
- }
-
- for (size_t i = 0; i < mOwnedBitmapResources.size(); i++) {
- SkBitmap* bitmap = mOwnedBitmapResources.itemAt(i);
- caches.resourceCache.decrementRefcountLocked(bitmap);
- caches.resourceCache.destructorLocked(bitmap);
- }
-
- for (size_t i = 0; i < mFilterResources.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i));
- }
-
- for (size_t i = 0; i < mPatchResources.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mPatchResources.itemAt(i));
- }
-
- for (size_t i = 0; i < mShaders.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i));
- caches.resourceCache.destructorLocked(mShaders.itemAt(i));
- }
-
- for (size_t i = 0; i < mSourcePaths.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mSourcePaths.itemAt(i));
- }
-
- for (size_t i = 0; i < mLayers.size(); i++) {
- caches.resourceCache.decrementRefcountLocked(mLayers.itemAt(i));
- }
-
- caches.resourceCache.unlock();
-
- for (size_t i = 0; i < mPaints.size(); i++) {
- delete mPaints.itemAt(i);
- }
-
- for (size_t i = 0; i < mRegions.size(); i++) {
- delete mRegions.itemAt(i);
- }
-
- for (size_t i = 0; i < mPaths.size(); i++) {
- delete mPaths.itemAt(i);
- }
-
- for (size_t i = 0; i < mMatrices.size(); i++) {
- delete mMatrices.itemAt(i);
- }
-
- mBitmapResources.clear();
- mOwnedBitmapResources.clear();
- mFilterResources.clear();
- mPatchResources.clear();
- mShaders.clear();
- mSourcePaths.clear();
- mPaints.clear();
- mRegions.clear();
- mPaths.clear();
- mMatrices.clear();
- mLayers.clear();
+DisplayListData::DisplayListData()
+ : projectionReceiveIndex(-1)
+ , hasDrawOps(false) {
}
-void DisplayList::reset() {
- clearResources();
- init();
+DisplayListData::~DisplayListData() {
+ cleanupResources();
}
-void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorder, bool reusing) {
- if (reusing) {
- // re-using display list - clear out previous allocations
- clearResources();
- }
-
- init();
-
- mDisplayListData = recorder.getDisplayListData();
- mSize = mDisplayListData->allocator.usedSize();
-
- if (mSize == 0) {
- return;
- }
-
- // allocate reusable ops for state-deferral
- LinearAllocator& alloc = mDisplayListData->allocator;
- mClipRectOp = new (alloc) ClipRectOp();
- mSaveLayerOp = new (alloc) SaveLayerOp();
- mSaveOp = new (alloc) SaveOp();
- mRestoreToCountOp = new (alloc) RestoreToCountOp();
- if (CC_UNLIKELY(!mSaveOp)) { // temporary debug logging
- ALOGW("Error: %s's SaveOp not allocated, size %d", getName(), mSize);
- CRASH();
- }
-
- mFunctorCount = recorder.getFunctorCount();
-
+void DisplayListData::cleanupResources() {
Caches& caches = Caches::getInstance();
- caches.registerFunctors(mFunctorCount);
+ caches.unregisterFunctors(functors.size());
caches.resourceCache.lock();
- const Vector<SkBitmap*>& bitmapResources = recorder.getBitmapResources();
for (size_t i = 0; i < bitmapResources.size(); i++) {
- SkBitmap* resource = bitmapResources.itemAt(i);
- mBitmapResources.add(resource);
- caches.resourceCache.incrementRefcountLocked(resource);
+ caches.resourceCache.decrementRefcountLocked(bitmapResources.itemAt(i));
}
- const Vector<SkBitmap*> &ownedBitmapResources = recorder.getOwnedBitmapResources();
for (size_t i = 0; i < ownedBitmapResources.size(); i++) {
- SkBitmap* resource = ownedBitmapResources.itemAt(i);
- mOwnedBitmapResources.add(resource);
- caches.resourceCache.incrementRefcountLocked(resource);
- }
-
- const Vector<SkiaColorFilter*>& filterResources = recorder.getFilterResources();
- for (size_t i = 0; i < filterResources.size(); i++) {
- SkiaColorFilter* resource = filterResources.itemAt(i);
- mFilterResources.add(resource);
- caches.resourceCache.incrementRefcountLocked(resource);
+ const SkBitmap* bitmap = ownedBitmapResources.itemAt(i);
+ caches.resourceCache.decrementRefcountLocked(bitmap);
+ caches.resourceCache.destructorLocked(bitmap);
}
- const Vector<Res_png_9patch*>& patchResources = recorder.getPatchResources();
for (size_t i = 0; i < patchResources.size(); i++) {
- Res_png_9patch* resource = patchResources.itemAt(i);
- mPatchResources.add(resource);
- caches.resourceCache.incrementRefcountLocked(resource);
- }
-
- const Vector<SkiaShader*>& shaders = recorder.getShaders();
- for (size_t i = 0; i < shaders.size(); i++) {
- SkiaShader* resource = shaders.itemAt(i);
- mShaders.add(resource);
- caches.resourceCache.incrementRefcountLocked(resource);
+ caches.resourceCache.decrementRefcountLocked(patchResources.itemAt(i));
}
- const SortedVector<SkPath*>& sourcePaths = recorder.getSourcePaths();
for (size_t i = 0; i < sourcePaths.size(); i++) {
- mSourcePaths.add(sourcePaths.itemAt(i));
- caches.resourceCache.incrementRefcountLocked(sourcePaths.itemAt(i));
+ caches.resourceCache.decrementRefcountLocked(sourcePaths.itemAt(i));
}
- const Vector<Layer*>& layers = recorder.getLayers();
for (size_t i = 0; i < layers.size(); i++) {
- mLayers.add(layers.itemAt(i));
- caches.resourceCache.incrementRefcountLocked(layers.itemAt(i));
+ caches.resourceCache.decrementRefcountLocked(layers.itemAt(i));
}
caches.resourceCache.unlock();
- mPaints.appendVector(recorder.getPaints());
- mRegions.appendVector(recorder.getRegions());
- mPaths.appendVector(recorder.getPaths());
- mMatrices.appendVector(recorder.getMatrices());
-}
-
-void DisplayList::init() {
- mSize = 0;
- mIsRenderable = true;
- mFunctorCount = 0;
- mLeft = 0;
- mTop = 0;
- mRight = 0;
- mBottom = 0;
- mClipToBounds = true;
- mAlpha = 1;
- mHasOverlappingRendering = true;
- mTranslationX = 0;
- mTranslationY = 0;
- mRotation = 0;
- mRotationX = 0;
- mRotationY= 0;
- mScaleX = 1;
- mScaleY = 1;
- mPivotX = 0;
- mPivotY = 0;
- mCameraDistance = 0;
- mMatrixDirty = false;
- mMatrixFlags = 0;
- mPrevWidth = -1;
- mPrevHeight = -1;
- mWidth = 0;
- mHeight = 0;
- mPivotExplicitlySet = false;
- mCaching = false;
-}
-
-size_t DisplayList::getSize() {
- return mSize;
-}
-
-/**
- * This function is a simplified version of replay(), where we simply retrieve and log the
- * display list. This function should remain in sync with the replay() function.
- */
-void DisplayList::output(uint32_t level) {
- ALOGD("%*sStart display list (%p, %s, render=%d)", (level - 1) * 2, "", this,
- mName.string(), isRenderable());
- ALOGD("%*s%s %d", level * 2, "", "Save",
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
-
- outputViewProperties(level);
- int flags = DisplayListOp::kOpLogFlag_Recurse;
- for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
- mDisplayListData->displayListOps[i]->output(level, flags);
+ for (size_t i = 0; i < paints.size(); i++) {
+ delete paints.itemAt(i);
}
- ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, mName.string());
-}
-
-float DisplayList::getPivotX() {
- updateMatrix();
- return mPivotX;
-}
-
-float DisplayList::getPivotY() {
- updateMatrix();
- return mPivotY;
-}
-
-void DisplayList::updateMatrix() {
- if (mMatrixDirty) {
- if (!mTransformMatrix) {
- mTransformMatrix = new SkMatrix();
- }
- if (mMatrixFlags == 0 || mMatrixFlags == TRANSLATION) {
- mTransformMatrix->reset();
- } else {
- if (!mPivotExplicitlySet) {
- if (mWidth != mPrevWidth || mHeight != mPrevHeight) {
- mPrevWidth = mWidth;
- mPrevHeight = mHeight;
- mPivotX = mPrevWidth / 2.0f;
- mPivotY = mPrevHeight / 2.0f;
- }
- }
- if ((mMatrixFlags & ROTATION_3D) == 0) {
- mTransformMatrix->setTranslate(mTranslationX, mTranslationY);
- mTransformMatrix->preRotate(mRotation, mPivotX, mPivotY);
- mTransformMatrix->preScale(mScaleX, mScaleY, mPivotX, mPivotY);
- } else {
- if (!mTransformCamera) {
- mTransformCamera = new Sk3DView();
- mTransformMatrix3D = new SkMatrix();
- }
- mTransformMatrix->reset();
- mTransformCamera->save();
- mTransformMatrix->preScale(mScaleX, mScaleY, mPivotX, mPivotY);
- mTransformCamera->rotateX(mRotationX);
- mTransformCamera->rotateY(mRotationY);
- mTransformCamera->rotateZ(-mRotation);
- mTransformCamera->getMatrix(mTransformMatrix3D);
- mTransformMatrix3D->preTranslate(-mPivotX, -mPivotY);
- mTransformMatrix3D->postTranslate(mPivotX + mTranslationX,
- mPivotY + mTranslationY);
- mTransformMatrix->postConcat(*mTransformMatrix3D);
- mTransformCamera->restore();
- }
- }
- mMatrixDirty = false;
+ for (size_t i = 0; i < regions.size(); i++) {
+ delete regions.itemAt(i);
}
-}
-void DisplayList::outputViewProperties(const int level) {
- updateMatrix();
- if (mLeft != 0 || mTop != 0) {
- ALOGD("%*sTranslate (left, top) %d, %d", level * 2, "", mLeft, mTop);
- }
- if (mStaticMatrix) {
- ALOGD("%*sConcatMatrix (static) %p: " MATRIX_STRING,
- level * 2, "", mStaticMatrix, MATRIX_ARGS(mStaticMatrix));
- }
- if (mAnimationMatrix) {
- ALOGD("%*sConcatMatrix (animation) %p: " MATRIX_STRING,
- level * 2, "", mAnimationMatrix, MATRIX_ARGS(mAnimationMatrix));
- }
- if (mMatrixFlags != 0) {
- if (mMatrixFlags == TRANSLATION) {
- ALOGD("%*sTranslate %f, %f", level * 2, "", mTranslationX, mTranslationY);
- } else {
- ALOGD("%*sConcatMatrix %p: " MATRIX_STRING,
- level * 2, "", mTransformMatrix, MATRIX_ARGS(mTransformMatrix));
- }
+ for (size_t i = 0; i < paths.size(); i++) {
+ delete paths.itemAt(i);
}
- bool clipToBoundsNeeded = mCaching ? false : mClipToBounds;
- if (mAlpha < 1) {
- if (mCaching) {
- ALOGD("%*sSetOverrideLayerAlpha %.2f", level * 2, "", mAlpha);
- } else if (!mHasOverlappingRendering) {
- ALOGD("%*sScaleAlpha %.2f", level * 2, "", mAlpha);
- } else {
- int flags = SkCanvas::kHasAlphaLayer_SaveFlag;
- if (clipToBoundsNeeded) {
- flags |= SkCanvas::kClipToLayer_SaveFlag;
- clipToBoundsNeeded = false; // clipping done by save layer
- }
- ALOGD("%*sSaveLayerAlpha %.2f, %.2f, %.2f, %.2f, %d, 0x%x", level * 2, "",
- (float) 0, (float) 0, (float) mRight - mLeft, (float) mBottom - mTop,
- (int)(mAlpha * 255), flags);
- }
- }
- if (clipToBoundsNeeded) {
- ALOGD("%*sClipRect %.2f, %.2f, %.2f, %.2f", level * 2, "", 0.0f, 0.0f,
- (float) mRight - mLeft, (float) mBottom - mTop);
- }
+ bitmapResources.clear();
+ ownedBitmapResources.clear();
+ patchResources.clear();
+ sourcePaths.clear();
+ paints.clear();
+ regions.clear();
+ paths.clear();
+ layers.clear();
}
-/*
- * For property operations, we pass a savecount of 0, since the operations aren't part of the
- * displaylist, and thus don't have to compensate for the record-time/playback-time discrepancy in
- * base saveCount (i.e., how RestoreToCount uses saveCount + mCount)
- */
-#define PROPERTY_SAVECOUNT 0
-
-template <class T>
-void DisplayList::setViewProperties(OpenGLRenderer& renderer, T& handler,
- const int level) {
-#if DEBUG_DISPLAY_LIST
- outputViewProperties(level);
-#endif
- updateMatrix();
- if (mLeft != 0 || mTop != 0) {
- renderer.translate(mLeft, mTop);
- }
- if (mStaticMatrix) {
- renderer.concatMatrix(mStaticMatrix);
- } else if (mAnimationMatrix) {
- renderer.concatMatrix(mAnimationMatrix);
- }
- if (mMatrixFlags != 0) {
- if (mMatrixFlags == TRANSLATION) {
- renderer.translate(mTranslationX, mTranslationY);
- } else {
- renderer.concatMatrix(mTransformMatrix);
- }
- }
- bool clipToBoundsNeeded = mCaching ? false : mClipToBounds;
- if (mAlpha < 1) {
- if (mCaching) {
- renderer.setOverrideLayerAlpha(mAlpha);
- } else if (!mHasOverlappingRendering) {
- renderer.scaleAlpha(mAlpha);
- } else {
- // TODO: should be able to store the size of a DL at record time and not
- // have to pass it into this call. In fact, this information might be in the
- // location/size info that we store with the new native transform data.
- int saveFlags = SkCanvas::kHasAlphaLayer_SaveFlag;
- if (clipToBoundsNeeded) {
- saveFlags |= SkCanvas::kClipToLayer_SaveFlag;
- clipToBoundsNeeded = false; // clipping done by saveLayer
- }
- handler(mSaveLayerOp->reinit(0, 0, mRight - mLeft, mBottom - mTop,
- mAlpha * 255, SkXfermode::kSrcOver_Mode, saveFlags), PROPERTY_SAVECOUNT,
- mClipToBounds);
- }
- }
- if (clipToBoundsNeeded) {
- handler(mClipRectOp->reinit(0, 0, mRight - mLeft, mBottom - mTop, SkRegion::kIntersect_Op),
- PROPERTY_SAVECOUNT, mClipToBounds);
- }
-}
-
-class DeferOperationHandler {
-public:
- DeferOperationHandler(DeferStateStruct& deferStruct, int level)
- : mDeferStruct(deferStruct), mLevel(level) {}
- inline void operator()(DisplayListOp* operation, int saveCount, bool clipToBounds) {
- operation->defer(mDeferStruct, saveCount, mLevel, clipToBounds);
- }
-private:
- DeferStateStruct& mDeferStruct;
- const int mLevel;
-};
-
-void DisplayList::defer(DeferStateStruct& deferStruct, const int level) {
- DeferOperationHandler handler(deferStruct, level);
- iterate<DeferOperationHandler>(deferStruct.mRenderer, handler, level);
-}
-
-class ReplayOperationHandler {
-public:
- ReplayOperationHandler(ReplayStateStruct& replayStruct, int level)
- : mReplayStruct(replayStruct), mLevel(level) {}
- inline void operator()(DisplayListOp* operation, int saveCount, bool clipToBounds) {
-#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
- mReplayStruct.mRenderer.eventMark(operation->name());
-#endif
- operation->replay(mReplayStruct, saveCount, mLevel, clipToBounds);
- }
-private:
- ReplayStateStruct& mReplayStruct;
- const int mLevel;
-};
-
-void DisplayList::replay(ReplayStateStruct& replayStruct, const int level) {
- ReplayOperationHandler handler(replayStruct, level);
-
- replayStruct.mRenderer.startMark(mName.string());
- iterate<ReplayOperationHandler>(replayStruct.mRenderer, handler, level);
- replayStruct.mRenderer.endMark();
-
- DISPLAY_LIST_LOGD("%*sDone (%p, %s), returning %d", level * 2, "", this, mName.string(),
- replayStruct.mDrawGlStatus);
-}
-
-/**
- * This function serves both defer and replay modes, and will organize the displayList's component
- * operations for a single frame:
- *
- * Every 'simple' operation that affects just the matrix and alpha (or other factors of
- * DeferredDisplayState) may be issued directly to the renderer, but complex operations (with custom
- * defer logic) and operations in displayListOps are issued through the 'handler' which handles the
- * defer vs replay logic, per operation
- */
-template <class T>
-void DisplayList::iterate(OpenGLRenderer& renderer, T& handler, const int level) {
- if (CC_UNLIKELY(mDestroyed)) { // temporary debug logging
- ALOGW("Error: %s is drawing after destruction, size %d", getName(), mSize);
- CRASH();
- }
- if (mSize == 0 || mAlpha <= 0) {
- DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, mName.string());
- return;
- }
-
-#if DEBUG_DISPLAY_LIST
- Rect* clipRect = renderer.getClipRect();
- DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), clipRect: %.0f, %.0f, %.0f, %.0f",
- level * 2, "", this, mName.string(), clipRect->left, clipRect->top,
- clipRect->right, clipRect->bottom);
-#endif
-
- int restoreTo = renderer.getSaveCount();
- handler(mSaveOp->reinit(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
- PROPERTY_SAVECOUNT, mClipToBounds);
-
- DISPLAY_LIST_LOGD("%*sSave %d %d", (level + 1) * 2, "",
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag, restoreTo);
-
- setViewProperties<T>(renderer, handler, level + 1);
-
- if (mClipToBounds && renderer.quickRejectNoScissor(0, 0, mWidth, mHeight)) {
- DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (level + 1) * 2, "", restoreTo);
- handler(mRestoreToCountOp->reinit(restoreTo), PROPERTY_SAVECOUNT, mClipToBounds);
- renderer.restoreToCount(restoreTo);
- renderer.setOverrideLayerAlpha(1.0f);
- return;
- }
-
- DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
- int saveCount = renderer.getSaveCount() - 1;
- for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
- DisplayListOp *op = mDisplayListData->displayListOps[i];
-
-#if DEBUG_DISPLAY_LIST
- op->output(level + 1);
-#endif
-
- logBuffer.writeCommand(level, op->name());
- handler(op, saveCount, mClipToBounds);
- }
+void DisplayListData::addChild(DrawRenderNodeOp* op) {
+ LOG_ALWAYS_FATAL_IF(!op->renderNode(), "DrawRenderNodeOp with no render node!");
- DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (level + 1) * 2, "", restoreTo);
- handler(mRestoreToCountOp->reinit(restoreTo), PROPERTY_SAVECOUNT, mClipToBounds);
- renderer.restoreToCount(restoreTo);
- renderer.setOverrideLayerAlpha(1.0f);
+ mChildren.push(op);
+ mReferenceHolders.push(op->renderNode());
}
}; // namespace uirenderer
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 1cd5f1c..acfa98e 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -26,6 +26,7 @@
#include <private/hwui/DrawGlInfo.h>
+#include <utils/KeyedVector.h>
#include <utils/LinearAllocator.h>
#include <utils/RefBase.h>
#include <utils/SortedVector.h>
@@ -37,12 +38,9 @@
#include <androidfw/ResourceTypes.h>
#include "Debug.h"
-
-#define TRANSLATION 0x0001
-#define ROTATION 0x0002
-#define ROTATION_3D 0x0004
-#define SCALE 0x0008
-#define PIVOT 0x0010
+#include "Matrix.h"
+#include "DeferredDisplayList.h"
+#include "RenderProperties.h"
class SkBitmap;
class SkPaint;
@@ -58,492 +56,113 @@ class DisplayListRenderer;
class OpenGLRenderer;
class Rect;
class Layer;
-class SkiaColorFilter;
-class SkiaShader;
class ClipRectOp;
class SaveLayerOp;
class SaveOp;
class RestoreToCountOp;
-
-struct DeferStateStruct {
- DeferStateStruct(DeferredDisplayList& deferredList, OpenGLRenderer& renderer, int replayFlags)
- : mDeferredList(deferredList), mRenderer(renderer), mReplayFlags(replayFlags) {}
- DeferredDisplayList& mDeferredList;
- OpenGLRenderer& mRenderer;
- const int mReplayFlags;
-};
-
-struct ReplayStateStruct {
- ReplayStateStruct(OpenGLRenderer& renderer, Rect& dirty, int replayFlags)
- : mRenderer(renderer), mDirty(dirty), mReplayFlags(replayFlags),
- mDrawGlStatus(DrawGlInfo::kStatusDone) {}
- OpenGLRenderer& mRenderer;
- Rect& mDirty;
- const int mReplayFlags;
- status_t mDrawGlStatus;
-};
+class DrawRenderNodeOp;
/**
- * Refcounted structure that holds data used in display list stream
+ * Holds data used in the playback a tree of DisplayLists.
*/
-class DisplayListData : public LightRefBase<DisplayListData> {
-public:
- LinearAllocator allocator;
- Vector<DisplayListOp*> displayListOps;
-};
+class PlaybackStateStruct {
+protected:
+ PlaybackStateStruct(OpenGLRenderer& renderer, int replayFlags, LinearAllocator* allocator)
+ : mRenderer(renderer)
+ , mReplayFlags(replayFlags)
+ , mAllocator(allocator) {}
-/**
- * Replays recorded drawing commands.
- */
-class DisplayList {
public:
- DisplayList(const DisplayListRenderer& recorder);
- ANDROID_API ~DisplayList();
-
- // See flags defined in DisplayList.java
- enum ReplayFlag {
- kReplayFlag_ClipChildren = 0x1
- };
-
-
- ANDROID_API size_t getSize();
- ANDROID_API static void destroyDisplayListDeferred(DisplayList* displayList);
- ANDROID_API static void outputLogBuffer(int fd);
-
- void initFromDisplayListRenderer(const DisplayListRenderer& recorder, bool reusing = false);
-
- void defer(DeferStateStruct& deferStruct, const int level);
- void replay(ReplayStateStruct& replayStruct, const int level);
-
- void output(uint32_t level = 0);
-
- ANDROID_API void reset();
-
- void setRenderable(bool renderable) {
- mIsRenderable = renderable;
- }
-
- bool isRenderable() const {
- return mIsRenderable;
- }
-
- void setName(const char* name) {
- if (name) {
- char* lastPeriod = strrchr(name, '.');
- if (lastPeriod) {
- mName.setTo(lastPeriod + 1);
- } else {
- mName.setTo(name);
- }
- }
- }
-
- const char* getName() const {
- return mName.string();
- }
-
- void setClipToBounds(bool clipToBounds) {
- mClipToBounds = clipToBounds;
- }
-
- void setStaticMatrix(SkMatrix* matrix) {
- delete mStaticMatrix;
- mStaticMatrix = new SkMatrix(*matrix);
- }
-
- // Can return NULL
- SkMatrix* getStaticMatrix() {
- return mStaticMatrix;
- }
-
- void setAnimationMatrix(SkMatrix* matrix) {
- delete mAnimationMatrix;
- if (matrix) {
- mAnimationMatrix = new SkMatrix(*matrix);
- } else {
- mAnimationMatrix = NULL;
- }
- }
-
- void setAlpha(float alpha) {
- alpha = fminf(1.0f, fmaxf(0.0f, alpha));
- if (alpha != mAlpha) {
- mAlpha = alpha;
- }
- }
-
- float getAlpha() const {
- return mAlpha;
- }
-
- void setHasOverlappingRendering(bool hasOverlappingRendering) {
- mHasOverlappingRendering = hasOverlappingRendering;
- }
-
- bool hasOverlappingRendering() const {
- return mHasOverlappingRendering;
- }
-
- void setTranslationX(float translationX) {
- if (translationX != mTranslationX) {
- mTranslationX = translationX;
- mMatrixDirty = true;
- if (mTranslationX == 0.0f && mTranslationY == 0.0f) {
- mMatrixFlags &= ~TRANSLATION;
- } else {
- mMatrixFlags |= TRANSLATION;
- }
- }
- }
-
- float getTranslationX() const {
- return mTranslationX;
- }
-
- void setTranslationY(float translationY) {
- if (translationY != mTranslationY) {
- mTranslationY = translationY;
- mMatrixDirty = true;
- if (mTranslationX == 0.0f && mTranslationY == 0.0f) {
- mMatrixFlags &= ~TRANSLATION;
- } else {
- mMatrixFlags |= TRANSLATION;
- }
- }
- }
-
- float getTranslationY() const {
- return mTranslationY;
- }
-
- void setRotation(float rotation) {
- if (rotation != mRotation) {
- mRotation = rotation;
- mMatrixDirty = true;
- if (mRotation == 0.0f) {
- mMatrixFlags &= ~ROTATION;
- } else {
- mMatrixFlags |= ROTATION;
- }
- }
- }
-
- float getRotation() const {
- return mRotation;
- }
-
- void setRotationX(float rotationX) {
- if (rotationX != mRotationX) {
- mRotationX = rotationX;
- mMatrixDirty = true;
- if (mRotationX == 0.0f && mRotationY == 0.0f) {
- mMatrixFlags &= ~ROTATION_3D;
- } else {
- mMatrixFlags |= ROTATION_3D;
- }
- }
- }
-
- float getRotationX() const {
- return mRotationX;
- }
-
- void setRotationY(float rotationY) {
- if (rotationY != mRotationY) {
- mRotationY = rotationY;
- mMatrixDirty = true;
- if (mRotationX == 0.0f && mRotationY == 0.0f) {
- mMatrixFlags &= ~ROTATION_3D;
- } else {
- mMatrixFlags |= ROTATION_3D;
- }
- }
- }
-
- float getRotationY() const {
- return mRotationY;
- }
-
- void setScaleX(float scaleX) {
- if (scaleX != mScaleX) {
- mScaleX = scaleX;
- mMatrixDirty = true;
- if (mScaleX == 1.0f && mScaleY == 1.0f) {
- mMatrixFlags &= ~SCALE;
- } else {
- mMatrixFlags |= SCALE;
- }
- }
- }
-
- float getScaleX() const {
- return mScaleX;
- }
-
- void setScaleY(float scaleY) {
- if (scaleY != mScaleY) {
- mScaleY = scaleY;
- mMatrixDirty = true;
- if (mScaleX == 1.0f && mScaleY == 1.0f) {
- mMatrixFlags &= ~SCALE;
- } else {
- mMatrixFlags |= SCALE;
- }
- }
- }
-
- float getScaleY() const {
- return mScaleY;
- }
+ OpenGLRenderer& mRenderer;
+ const int mReplayFlags;
- void setPivotX(float pivotX) {
- mPivotX = pivotX;
- mMatrixDirty = true;
- if (mPivotX == 0.0f && mPivotY == 0.0f) {
- mMatrixFlags &= ~PIVOT;
- } else {
- mMatrixFlags |= PIVOT;
- }
- mPivotExplicitlySet = true;
- }
+ // Allocator with the lifetime of a single frame.
+ // replay uses an Allocator owned by the struct, while defer shares the DeferredDisplayList's Allocator
+ LinearAllocator * const mAllocator;
- ANDROID_API float getPivotX();
-
- void setPivotY(float pivotY) {
- mPivotY = pivotY;
- mMatrixDirty = true;
- if (mPivotX == 0.0f && mPivotY == 0.0f) {
- mMatrixFlags &= ~PIVOT;
- } else {
- mMatrixFlags |= PIVOT;
- }
- mPivotExplicitlySet = true;
+ SkPath* allocPathForFrame() {
+ mTempPaths.push_back();
+ return &mTempPaths.back();
}
- ANDROID_API float getPivotY();
-
- void setCameraDistance(float distance) {
- if (distance != mCameraDistance) {
- mCameraDistance = distance;
- mMatrixDirty = true;
- if (!mTransformCamera) {
- mTransformCamera = new Sk3DView();
- mTransformMatrix3D = new SkMatrix();
- }
- mTransformCamera->setCameraLocation(0, 0, distance);
- }
- }
+private:
+ // Paths kept alive for the duration of the frame
+ std::vector<SkPath> mTempPaths;
+};
- float getCameraDistance() const {
- return mCameraDistance;
- }
+class DeferStateStruct : public PlaybackStateStruct {
+public:
+ DeferStateStruct(DeferredDisplayList& deferredList, OpenGLRenderer& renderer, int replayFlags)
+ : PlaybackStateStruct(renderer, replayFlags, &(deferredList.mAllocator)),
+ mDeferredList(deferredList) {}
- void setLeft(int left) {
- if (left != mLeft) {
- mLeft = left;
- mWidth = mRight - mLeft;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
+ DeferredDisplayList& mDeferredList;
+};
- float getLeft() const {
- return mLeft;
- }
+class ReplayStateStruct : public PlaybackStateStruct {
+public:
+ ReplayStateStruct(OpenGLRenderer& renderer, Rect& dirty, int replayFlags)
+ : PlaybackStateStruct(renderer, replayFlags, &mReplayAllocator),
+ mDirty(dirty), mDrawGlStatus(DrawGlInfo::kStatusDone) {}
- void setTop(int top) {
- if (top != mTop) {
- mTop = top;
- mHeight = mBottom - mTop;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
+ Rect& mDirty;
+ status_t mDrawGlStatus;
+ LinearAllocator mReplayAllocator;
+};
- float getTop() const {
- return mTop;
- }
+/**
+ * Data structure that holds the list of commands used in display list stream
+ */
+class DisplayListData {
+public:
+ DisplayListData();
+ ~DisplayListData();
- void setRight(int right) {
- if (right != mRight) {
- mRight = right;
- mWidth = mRight - mLeft;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
+ // allocator into which all ops were allocated
+ LinearAllocator allocator;
- float getRight() const {
- return mRight;
- }
+ // pointers to all ops within display list, pointing into allocator data
+ Vector<DisplayListOp*> displayListOps;
- void setBottom(int bottom) {
- if (bottom != mBottom) {
- mBottom = bottom;
- mHeight = mBottom - mTop;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
+ // index of DisplayListOp restore, after which projected descendents should be drawn
+ int projectionReceiveIndex;
- float getBottom() const {
- return mBottom;
- }
+ Vector<const SkBitmap*> bitmapResources;
+ Vector<const SkBitmap*> ownedBitmapResources;
+ Vector<const Res_png_9patch*> patchResources;
- void setLeftTop(int left, int top) {
- if (left != mLeft || top != mTop) {
- mLeft = left;
- mTop = top;
- mWidth = mRight - mLeft;
- mHeight = mBottom - mTop;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
+ Vector<const SkPaint*> paints;
+ Vector<const SkPath*> paths;
+ SortedVector<const SkPath*> sourcePaths;
+ Vector<const SkRegion*> regions;
+ Vector<Layer*> layers;
+ Vector<Functor*> functors;
+ bool hasDrawOps;
- void setLeftTopRightBottom(int left, int top, int right, int bottom) {
- if (left != mLeft || top != mTop || right != mRight || bottom != mBottom) {
- mLeft = left;
- mTop = top;
- mRight = right;
- mBottom = bottom;
- mWidth = mRight - mLeft;
- mHeight = mBottom - mTop;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
+ bool isEmpty() {
+ return !displayListOps.size();
}
- void offsetLeftRight(float offset) {
- if (offset != 0) {
- mLeft += offset;
- mRight += offset;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
- }
+ void addChild(DrawRenderNodeOp* childOp);
+ const Vector<DrawRenderNodeOp*>& children() { return mChildren; }
- void offsetTopBottom(float offset) {
- if (offset != 0) {
- mTop += offset;
- mBottom += offset;
- if (mMatrixFlags > TRANSLATION && !mPivotExplicitlySet) {
- mMatrixDirty = true;
- }
- }
+ void refProperty(CanvasPropertyPrimitive* prop) {
+ mReferenceHolders.push(prop);
}
- void setCaching(bool caching) {
- mCaching = caching;
+ void refProperty(CanvasPropertyPaint* prop) {
+ mReferenceHolders.push(prop);
}
- int getWidth() {
- return mWidth;
- }
+private:
+ Vector< sp<VirtualLightRefBase> > mReferenceHolders;
- int getHeight() {
- return mHeight;
- }
+ // list of children display lists for quick, non-drawing traversal
+ Vector<DrawRenderNodeOp*> mChildren;
-private:
- void outputViewProperties(const int level);
-
- template <class T>
- inline void setViewProperties(OpenGLRenderer& renderer, T& handler, const int level);
-
- template <class T>
- inline void iterate(OpenGLRenderer& renderer, T& handler, const int level);
-
- void init();
-
- void clearResources();
-
- void updateMatrix();
-
- class TextContainer {
- public:
- size_t length() const {
- return mByteLength;
- }
-
- const char* text() const {
- return (const char*) mText;
- }
-
- size_t mByteLength;
- const char* mText;
- };
-
- Vector<SkBitmap*> mBitmapResources;
- Vector<SkBitmap*> mOwnedBitmapResources;
- Vector<SkiaColorFilter*> mFilterResources;
- Vector<Res_png_9patch*> mPatchResources;
-
- Vector<SkPaint*> mPaints;
- Vector<SkPath*> mPaths;
- SortedVector<SkPath*> mSourcePaths;
- Vector<SkRegion*> mRegions;
- Vector<SkMatrix*> mMatrices;
- Vector<SkiaShader*> mShaders;
- Vector<Layer*> mLayers;
-
- sp<DisplayListData> mDisplayListData;
-
- size_t mSize;
-
- bool mIsRenderable;
- uint32_t mFunctorCount;
-
- String8 mName;
- bool mDestroyed; // used for debugging crash, TODO: remove once invalid state crash fixed
-
- // View properties
- bool mClipToBounds;
- float mAlpha;
- bool mHasOverlappingRendering;
- float mTranslationX, mTranslationY;
- float mRotation, mRotationX, mRotationY;
- float mScaleX, mScaleY;
- float mPivotX, mPivotY;
- float mCameraDistance;
- int mLeft, mTop, mRight, mBottom;
- int mWidth, mHeight;
- int mPrevWidth, mPrevHeight;
- bool mPivotExplicitlySet;
- bool mMatrixDirty;
- bool mMatrixIsIdentity;
- uint32_t mMatrixFlags;
- SkMatrix* mTransformMatrix;
- Sk3DView* mTransformCamera;
- SkMatrix* mTransformMatrix3D;
- SkMatrix* mStaticMatrix;
- SkMatrix* mAnimationMatrix;
- bool mCaching;
-
- /**
- * State operations - needed to defer displayList property operations (for example, when setting
- * an alpha causes a SaveLayerAlpha to occur). These operations point into mDisplayListData's
- * allocation, or null if uninitialized.
- *
- * These are initialized (via friend re-constructors) when a displayList is issued in either
- * replay or deferred mode. If replaying, the ops are not used until the next frame. If
- * deferring, the ops may be stored in the DeferredDisplayList to be played back a second time.
- *
- * They should be used at most once per frame (one call to 'iterate') to avoid overwriting data
- */
- ClipRectOp* mClipRectOp;
- SaveLayerOp* mSaveLayerOp;
- SaveOp* mSaveOp;
- RestoreToCountOp* mRestoreToCountOp;
-}; // class DisplayList
+ void cleanupResources();
+};
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 842e028..c6d3db7 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -21,6 +21,9 @@
#define LOG_TAG "OpenGLRenderer"
#endif
+#include <SkColor.h>
+#include <SkPath.h>
+#include <SkPathOps.h>
#include <SkXfermode.h>
#include <private/hwui/DrawGlInfo.h>
@@ -111,7 +114,7 @@ public:
class DrawOp : public DisplayListOp {
friend class MergingDrawBatch;
public:
- DrawOp(SkPaint* paint)
+ DrawOp(const SkPaint* paint)
: mPaint(paint), mQuickRejected(false) {}
virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
@@ -169,7 +172,7 @@ public:
*
* returns true if bounds exist
*/
- virtual bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) {
+ virtual bool getLocalBounds(Rect& localBounds) {
return false;
}
@@ -181,14 +184,21 @@ public:
return OpenGLRenderer::getAlphaDirect(mPaint);
}
+ virtual bool hasTextShadow() const {
+ return false;
+ }
+
inline float strokeWidthOutset() {
- float width = mPaint->getStrokeWidth();
- if (width == 0) return 0.5f; // account for hairline
- return width * 0.5f;
+ // since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced
+ // 1.0 stroke, treat 1.0 as minimum.
+
+ // TODO: it would be nice if this could take scale into account, but scale isn't stable
+ // since higher levels of the view hierarchy can change scale out from underneath it.
+ return fmaxf(mPaint->getStrokeWidth(), 1) * 0.5f;
}
protected:
- SkPaint* getPaint(OpenGLRenderer& renderer) {
+ const SkPaint* getPaint(OpenGLRenderer& renderer) {
return renderer.filterPaint(mPaint);
}
@@ -199,9 +209,16 @@ protected:
if (!state.mMatrix.isSimple()) return false;
// check state/paint for transparency
- if (state.mDrawModifiers.mShader ||
- state.mAlpha != 1.0f ||
- (mPaint && mPaint->getAlpha() != 0xFF)) return false;
+ if (mPaint) {
+ if (mPaint->getShader() && !mPaint->getShader()->isOpaque()) {
+ return false;
+ }
+ if (mPaint->getAlpha() != 0xFF) {
+ return false;
+ }
+ }
+
+ if (state.mAlpha != 1.0f) return false;
SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint);
return (mode == SkXfermode::kSrcOver_Mode ||
@@ -209,22 +226,22 @@ protected:
}
- SkPaint* mPaint; // should be accessed via getPaint() when applying
+ const SkPaint* mPaint; // should be accessed via getPaint() when applying
bool mQuickRejected;
};
class DrawBoundedOp : public DrawOp {
public:
- DrawBoundedOp(float left, float top, float right, float bottom, SkPaint* paint)
+ DrawBoundedOp(float left, float top, float right, float bottom, const SkPaint* paint)
: DrawOp(paint), mLocalBounds(left, top, right, bottom) {}
- DrawBoundedOp(const Rect& localBounds, SkPaint* paint)
+ DrawBoundedOp(const Rect& localBounds, const SkPaint* paint)
: DrawOp(paint), mLocalBounds(localBounds) {}
// Calculates bounds as smallest rect encompassing all points
// NOTE: requires at least 1 vertex, and doesn't account for stroke size (should be handled in
// subclass' constructor)
- DrawBoundedOp(const float* points, int count, SkPaint* paint)
+ DrawBoundedOp(const float* points, int count, const SkPaint* paint)
: DrawOp(paint), mLocalBounds(points[0], points[1], points[0], points[1]) {
for (int i = 2; i < count; i += 2) {
mLocalBounds.left = fminf(mLocalBounds.left, points[i]);
@@ -235,15 +252,15 @@ public:
}
// default empty constructor for bounds, to be overridden in child constructor body
- DrawBoundedOp(SkPaint* paint): DrawOp(paint) { }
+ DrawBoundedOp(const SkPaint* paint): DrawOp(paint) { }
- bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) {
+ virtual bool getLocalBounds(Rect& localBounds) {
localBounds.set(mLocalBounds);
- if (drawModifiers.mHasShadow) {
- // TODO: inspect paint's looper directly
+ OpenGLRenderer::TextShadow textShadow;
+ if (OpenGLRenderer::getTextShadow(mPaint, &textShadow)) {
Rect shadow(mLocalBounds);
- shadow.translate(drawModifiers.mShadowDx, drawModifiers.mShadowDy);
- shadow.outset(drawModifiers.mShadowRadius);
+ shadow.translate(textShadow.dx, textShadow.dx);
+ shadow.outset(textShadow.radius);
localBounds.unionWith(shadow);
}
return true;
@@ -259,7 +276,6 @@ protected:
///////////////////////////////////////////////////////////////////////////////
class SaveOp : public StateOp {
- friend class DisplayList; // give DisplayList private constructor/reinit access
public:
SaveOp(int flags)
: mFlags(flags) {}
@@ -282,17 +298,10 @@ public:
int getFlags() const { return mFlags; }
private:
- SaveOp() {}
- DisplayListOp* reinit(int flags) {
- mFlags = flags;
- return this;
- }
-
int mFlags;
};
class RestoreToCountOp : public StateOp {
- friend class DisplayList; // give DisplayList private constructor/reinit access
public:
RestoreToCountOp(int count)
: mCount(count) {}
@@ -315,21 +324,25 @@ public:
virtual const char* name() { return "RestoreToCount"; }
private:
- RestoreToCountOp() {}
- DisplayListOp* reinit(int count) {
- mCount = count;
- return this;
- }
-
int mCount;
};
class SaveLayerOp : public StateOp {
- friend class DisplayList; // give DisplayList private constructor/reinit access
public:
- SaveLayerOp(float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags)
- : mArea(left, top, right, bottom), mAlpha(alpha), mMode(mode), mFlags(flags) {}
+ SaveLayerOp(float left, float top, float right, float bottom, int alpha, int flags)
+ : mArea(left, top, right, bottom)
+ , mPaint(&mCachedPaint)
+ , mFlags(flags)
+ , mConvexMask(NULL) {
+ mCachedPaint.setAlpha(alpha);
+ }
+
+ SaveLayerOp(float left, float top, float right, float bottom, const SkPaint* paint, int flags)
+ : mArea(left, top, right, bottom)
+ , mPaint(paint)
+ , mFlags(flags)
+ , mConvexMask(NULL)
+ {}
virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
bool useQuickReject) {
@@ -340,11 +353,12 @@ public:
// NOTE: don't issue full saveLayer, since that has side effects/is costly. instead just
// setup the snapshot for deferral, and re-issue the op at flush time
deferStruct.mRenderer.saveLayerDeferred(mArea.left, mArea.top, mArea.right, mArea.bottom,
- mAlpha, mMode, mFlags);
+ mPaint, mFlags);
}
virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
- renderer.saveLayer(mArea.left, mArea.top, mArea.right, mArea.bottom, mAlpha, mMode, mFlags);
+ renderer.saveLayer(mArea.left, mArea.top, mArea.right, mArea.bottom,
+ mPaint, mFlags, mConvexMask);
}
virtual void output(int level, uint32_t logFlags) const {
@@ -356,23 +370,26 @@ public:
int getFlags() { return mFlags; }
+ // Called to make SaveLayerOp clip to the provided mask when drawing back/restored
+ void setMask(const SkPath* convexMask) {
+ mConvexMask = convexMask;
+ }
+
private:
- // Special case, reserved for direct DisplayList usage
- SaveLayerOp() {}
- DisplayListOp* reinit(float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags) {
- mArea.set(left, top, right, bottom);
- mAlpha = alpha;
- mMode = mode;
- mFlags = flags;
- return this;
- }
-
- bool isSaveLayerAlpha() const { return mAlpha < 255 && mMode == SkXfermode::kSrcOver_Mode; }
+ bool isSaveLayerAlpha() const {
+ SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint);
+ int alpha = OpenGLRenderer::getAlphaDirect(mPaint);
+ return alpha < 255 && mode == SkXfermode::kSrcOver_Mode;
+ }
+
Rect mArea;
- int mAlpha;
- SkXfermode::Mode mMode;
+ const SkPaint* mPaint;
+ SkPaint mCachedPaint;
int mFlags;
+
+ // Convex path, points at data in RenderNode, valid for the duration of the frame only
+ // Only used for masking the SaveLayer which wraps projected RenderNodes
+ const SkPath* mConvexMask;
};
class TranslateOp : public StateOp {
@@ -456,7 +473,7 @@ private:
class SetMatrixOp : public StateOp {
public:
- SetMatrixOp(SkMatrix* matrix)
+ SetMatrixOp(const SkMatrix& matrix)
: mMatrix(matrix) {}
virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
@@ -464,22 +481,22 @@ public:
}
virtual void output(int level, uint32_t logFlags) const {
- if (mMatrix) {
- OP_LOG("SetMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix));
- } else {
+ if (mMatrix.isIdentity()) {
OP_LOGS("SetMatrix (reset)");
+ } else {
+ OP_LOG("SetMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(&mMatrix));
}
}
virtual const char* name() { return "SetMatrix"; }
private:
- SkMatrix* mMatrix;
+ const SkMatrix mMatrix;
};
class ConcatMatrixOp : public StateOp {
public:
- ConcatMatrixOp(SkMatrix* matrix)
+ ConcatMatrixOp(const SkMatrix& matrix)
: mMatrix(matrix) {}
virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
@@ -487,13 +504,13 @@ public:
}
virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("ConcatMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix));
+ OP_LOG("ConcatMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(&mMatrix));
}
virtual const char* name() { return "ConcatMatrix"; }
private:
- SkMatrix* mMatrix;
+ const SkMatrix mMatrix;
};
class ClipOp : public StateOp {
@@ -514,14 +531,12 @@ public:
}
protected:
- ClipOp() {}
virtual bool isRect() { return false; }
SkRegion::Op mOp;
};
class ClipRectOp : public ClipOp {
- friend class DisplayList; // give DisplayList private constructor/reinit access
public:
ClipRectOp(float left, float top, float right, float bottom, SkRegion::Op op)
: ClipOp(op), mArea(left, top, right, bottom) {}
@@ -540,19 +555,12 @@ protected:
virtual bool isRect() { return true; }
private:
- ClipRectOp() {}
- DisplayListOp* reinit(float left, float top, float right, float bottom, SkRegion::Op op) {
- mOp = op;
- mArea.set(left, top, right, bottom);
- return this;
- }
-
Rect mArea;
};
class ClipPathOp : public ClipOp {
public:
- ClipPathOp(SkPath* path, SkRegion::Op op)
+ ClipPathOp(const SkPath* path, SkRegion::Op op)
: ClipOp(op), mPath(path) {}
virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
@@ -568,12 +576,12 @@ public:
virtual const char* name() { return "ClipPath"; }
private:
- SkPath* mPath;
+ const SkPath* mPath;
};
class ClipRegionOp : public ClipOp {
public:
- ClipRegionOp(SkRegion* region, SkRegion::Op op)
+ ClipRegionOp(const SkRegion* region, SkRegion::Op op)
: ClipOp(op), mRegion(region) {}
virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
@@ -589,105 +597,7 @@ public:
virtual const char* name() { return "ClipRegion"; }
private:
- SkRegion* mRegion;
-};
-
-class ResetShaderOp : public StateOp {
-public:
- virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
- renderer.resetShader();
- }
-
- virtual void output(int level, uint32_t logFlags) const {
- OP_LOGS("ResetShader");
- }
-
- virtual const char* name() { return "ResetShader"; }
-};
-
-class SetupShaderOp : public StateOp {
-public:
- SetupShaderOp(SkiaShader* shader)
- : mShader(shader) {}
- virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
- renderer.setupShader(mShader);
- }
-
- virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("SetupShader, shader %p", mShader);
- }
-
- virtual const char* name() { return "SetupShader"; }
-
-private:
- SkiaShader* mShader;
-};
-
-class ResetColorFilterOp : public StateOp {
-public:
- virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
- renderer.resetColorFilter();
- }
-
- virtual void output(int level, uint32_t logFlags) const {
- OP_LOGS("ResetColorFilter");
- }
-
- virtual const char* name() { return "ResetColorFilter"; }
-};
-
-class SetupColorFilterOp : public StateOp {
-public:
- SetupColorFilterOp(SkiaColorFilter* colorFilter)
- : mColorFilter(colorFilter) {}
-
- virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
- renderer.setupColorFilter(mColorFilter);
- }
-
- virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("SetupColorFilter, filter %p", mColorFilter);
- }
-
- virtual const char* name() { return "SetupColorFilter"; }
-
-private:
- SkiaColorFilter* mColorFilter;
-};
-
-class ResetShadowOp : public StateOp {
-public:
- virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
- renderer.resetShadow();
- }
-
- virtual void output(int level, uint32_t logFlags) const {
- OP_LOGS("ResetShadow");
- }
-
- virtual const char* name() { return "ResetShadow"; }
-};
-
-class SetupShadowOp : public StateOp {
-public:
- SetupShadowOp(float radius, float dx, float dy, int color)
- : mRadius(radius), mDx(dx), mDy(dy), mColor(color) {}
-
- virtual void applyState(OpenGLRenderer& renderer, int saveCount) const {
- renderer.setupShadow(mRadius, mDx, mDy, mColor);
- }
-
- virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("SetupShadow, radius %f, %f, %f, color %#x", mRadius, mDx, mDy, mColor);
- }
-
- virtual const char* name() { return "SetupShadow"; }
-
-private:
- float mRadius;
- float mDx;
- float mDy;
- int mColor;
+ const SkRegion* mRegion;
};
class ResetPaintFilterOp : public StateOp {
@@ -729,9 +639,10 @@ private:
class DrawBitmapOp : public DrawBoundedOp {
public:
- DrawBitmapOp(SkBitmap* bitmap, float left, float top, SkPaint* paint)
- : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(), paint),
- mBitmap(bitmap), mAtlas(Caches::getInstance().assetAtlas) {
+ DrawBitmapOp(const SkBitmap* bitmap, const SkPaint* paint)
+ : DrawBoundedOp(0, 0, bitmap->width(), bitmap->height(), paint)
+ , mBitmap(bitmap)
+ , mAtlas(Caches::getInstance().assetAtlas) {
mEntry = mAtlas.getEntry(bitmap);
if (mEntry) {
mEntryGenerationId = mAtlas.getGenerationId();
@@ -740,8 +651,7 @@ public:
}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
- return renderer.drawBitmap(mBitmap, mLocalBounds.left, mLocalBounds.top,
- getPaint(renderer));
+ return renderer.drawBitmap(mBitmap, getPaint(renderer));
}
AssetAtlas::Entry* getAtlasEntry() {
@@ -823,51 +733,23 @@ public:
deferInfo.mergeable = state.mMatrix.isSimple() && state.mMatrix.positiveScale() &&
!state.mClipSideFlags &&
OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode &&
- (mBitmap->getConfig() != SkBitmap::kA8_Config);
+ (mBitmap->colorType() != kAlpha_8_SkColorType);
}
const SkBitmap* bitmap() { return mBitmap; }
protected:
- SkBitmap* mBitmap;
+ const SkBitmap* mBitmap;
const AssetAtlas& mAtlas;
uint32_t mEntryGenerationId;
AssetAtlas::Entry* mEntry;
UvMapper mUvMapper;
};
-class DrawBitmapMatrixOp : public DrawBoundedOp {
-public:
- DrawBitmapMatrixOp(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint)
- : DrawBoundedOp(paint), mBitmap(bitmap), mMatrix(matrix) {
- mLocalBounds.set(0, 0, bitmap->width(), bitmap->height());
- const mat4 transform(*matrix);
- transform.mapRect(mLocalBounds);
- }
-
- virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
- return renderer.drawBitmap(mBitmap, mMatrix, getPaint(renderer));
- }
-
- virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("Draw bitmap %p matrix " MATRIX_STRING, mBitmap, MATRIX_ARGS(mMatrix));
- }
-
- virtual const char* name() { return "DrawBitmapMatrix"; }
-
- virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
- const DeferredDisplayState& state) {
- deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap;
- }
-
-private:
- SkBitmap* mBitmap;
- SkMatrix* mMatrix;
-};
-
class DrawBitmapRectOp : public DrawBoundedOp {
public:
- DrawBitmapRectOp(SkBitmap* bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom,
- float dstLeft, float dstTop, float dstRight, float dstBottom, SkPaint* paint)
+ DrawBitmapRectOp(const SkBitmap* bitmap,
+ float srcLeft, float srcTop, float srcRight, float srcBottom,
+ float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint)
: DrawBoundedOp(dstLeft, dstTop, dstRight, dstBottom, paint),
mBitmap(bitmap), mSrc(srcLeft, srcTop, srcRight, srcBottom) {}
@@ -878,7 +760,7 @@ public:
}
virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("Draw bitmap %p src="RECT_STRING", dst="RECT_STRING,
+ OP_LOG("Draw bitmap %p src=" RECT_STRING ", dst=" RECT_STRING,
mBitmap, RECT_ARGS(mSrc), RECT_ARGS(mLocalBounds));
}
@@ -890,18 +772,17 @@ public:
}
private:
- SkBitmap* mBitmap;
+ const SkBitmap* mBitmap;
Rect mSrc;
};
class DrawBitmapDataOp : public DrawBitmapOp {
public:
- DrawBitmapDataOp(SkBitmap* bitmap, float left, float top, SkPaint* paint)
- : DrawBitmapOp(bitmap, left, top, paint) {}
+ DrawBitmapDataOp(const SkBitmap* bitmap, const SkPaint* paint)
+ : DrawBitmapOp(bitmap, paint) {}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
- return renderer.drawBitmapData(mBitmap, mLocalBounds.left,
- mLocalBounds.top, getPaint(renderer));
+ return renderer.drawBitmapData(mBitmap, getPaint(renderer));
}
virtual void output(int level, uint32_t logFlags) const {
@@ -918,8 +799,8 @@ public:
class DrawBitmapMeshOp : public DrawBoundedOp {
public:
- DrawBitmapMeshOp(SkBitmap* bitmap, int meshWidth, int meshHeight,
- float* vertices, int* colors, SkPaint* paint)
+ DrawBitmapMeshOp(const SkBitmap* bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint)
: DrawBoundedOp(vertices, 2 * (meshWidth + 1) * (meshHeight + 1), paint),
mBitmap(bitmap), mMeshWidth(meshWidth), mMeshHeight(meshHeight),
mVertices(vertices), mColors(colors) {}
@@ -941,17 +822,17 @@ public:
}
private:
- SkBitmap* mBitmap;
+ const SkBitmap* mBitmap;
int mMeshWidth;
int mMeshHeight;
- float* mVertices;
- int* mColors;
+ const float* mVertices;
+ const int* mColors;
};
class DrawPatchOp : public DrawBoundedOp {
public:
- DrawPatchOp(SkBitmap* bitmap, Res_png_9patch* patch,
- float left, float top, float right, float bottom, SkPaint* paint)
+ DrawPatchOp(const SkBitmap* bitmap, const Res_png_9patch* patch,
+ float left, float top, float right, float bottom, const SkPaint* paint)
: DrawBoundedOp(left, top, right, bottom, paint),
mBitmap(bitmap), mPatch(patch), mGenerationId(0), mMesh(NULL),
mAtlas(Caches::getInstance().assetAtlas) {
@@ -1028,8 +909,8 @@ public:
TextureVertex* opVertices = opMesh->vertices;
for (uint32_t j = 0; j < vertexCount; j++, opVertices++) {
TextureVertex::set(vertex++,
- opVertices->position[0] + tx, opVertices->position[1] + ty,
- opVertices->texture[0], opVertices->texture[1]);
+ opVertices->x + tx, opVertices->y + ty,
+ opVertices->u, opVertices->v);
}
// Dirty the current layer if possible. When the 9-patch does not
@@ -1068,7 +949,7 @@ public:
}
virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("Draw patch "RECT_STRING, RECT_ARGS(mLocalBounds));
+ OP_LOG("Draw patch " RECT_STRING, RECT_ARGS(mLocalBounds));
}
virtual const char* name() { return "DrawPatch"; }
@@ -1083,8 +964,8 @@ public:
}
private:
- SkBitmap* mBitmap;
- Res_png_9patch* mPatch;
+ const SkBitmap* mBitmap;
+ const Res_png_9patch* mPatch;
uint32_t mGenerationId;
const Patch* mMesh;
@@ -1097,7 +978,7 @@ private:
class DrawColorOp : public DrawOp {
public:
DrawColorOp(int color, SkXfermode::Mode mode)
- : DrawOp(0), mColor(color), mMode(mode) {};
+ : DrawOp(NULL), mColor(color), mMode(mode) {};
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
return renderer.drawColor(mColor, mMode);
@@ -1116,10 +997,10 @@ private:
class DrawStrokableOp : public DrawBoundedOp {
public:
- DrawStrokableOp(float left, float top, float right, float bottom, SkPaint* paint)
+ DrawStrokableOp(float left, float top, float right, float bottom, const SkPaint* paint)
: DrawBoundedOp(left, top, right, bottom, paint) {};
- bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) {
+ virtual bool getLocalBounds(Rect& localBounds) {
localBounds.set(mLocalBounds);
if (mPaint && mPaint->getStyle() != SkPaint::kFill_Style) {
localBounds.outset(strokeWidthOutset());
@@ -1141,7 +1022,7 @@ public:
class DrawRectOp : public DrawStrokableOp {
public:
- DrawRectOp(float left, float top, float right, float bottom, SkPaint* paint)
+ DrawRectOp(float left, float top, float right, float bottom, const SkPaint* paint)
: DrawStrokableOp(left, top, right, bottom, paint) {}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
@@ -1150,7 +1031,7 @@ public:
}
virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("Draw Rect "RECT_STRING, RECT_ARGS(mLocalBounds));
+ OP_LOG("Draw Rect " RECT_STRING, RECT_ARGS(mLocalBounds));
}
virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
@@ -1165,7 +1046,7 @@ public:
class DrawRectsOp : public DrawBoundedOp {
public:
- DrawRectsOp(const float* rects, int count, SkPaint* paint)
+ DrawRectsOp(const float* rects, int count, const SkPaint* paint)
: DrawBoundedOp(rects, count, paint),
mRects(rects), mCount(count) {}
@@ -1192,7 +1073,7 @@ private:
class DrawRoundRectOp : public DrawStrokableOp {
public:
DrawRoundRectOp(float left, float top, float right, float bottom,
- float rx, float ry, SkPaint* paint)
+ float rx, float ry, const SkPaint* paint)
: DrawStrokableOp(left, top, right, bottom, paint), mRx(rx), mRy(ry) {}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
@@ -1201,7 +1082,16 @@ public:
}
virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("Draw RoundRect "RECT_STRING", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy);
+ OP_LOG("Draw RoundRect " RECT_STRING ", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy);
+ }
+
+ virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
+ const DeferredDisplayState& state) {
+ DrawStrokableOp::onDefer(renderer, deferInfo, state);
+ if (!mPaint->getPathEffect()) {
+ renderer.getCaches().tessellationCache.precacheRoundRect(state.mMatrix, *mPaint,
+ mLocalBounds.getWidth(), mLocalBounds.getHeight(), mRx, mRy);
+ }
}
virtual const char* name() { return "DrawRoundRect"; }
@@ -1213,7 +1103,7 @@ private:
class DrawCircleOp : public DrawStrokableOp {
public:
- DrawCircleOp(float x, float y, float radius, SkPaint* paint)
+ DrawCircleOp(float x, float y, float radius, const SkPaint* paint)
: DrawStrokableOp(x - radius, y - radius, x + radius, y + radius, paint),
mX(x), mY(y), mRadius(radius) {}
@@ -1233,9 +1123,30 @@ private:
float mRadius;
};
+class DrawCirclePropsOp : public DrawOp {
+public:
+ DrawCirclePropsOp(float* x, float* y, float* radius, const SkPaint* paint)
+ : DrawOp(paint), mX(x), mY(y), mRadius(radius) {}
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
+ return renderer.drawCircle(*mX, *mY, *mRadius, getPaint(renderer));
+ }
+
+ virtual void output(int level, uint32_t logFlags) const {
+ OP_LOG("Draw Circle Props x %p, y %p, r %p", mX, mY, mRadius);
+ }
+
+ virtual const char* name() { return "DrawCircleProps"; }
+
+private:
+ float* mX;
+ float* mY;
+ float* mRadius;
+};
+
class DrawOvalOp : public DrawStrokableOp {
public:
- DrawOvalOp(float left, float top, float right, float bottom, SkPaint* paint)
+ DrawOvalOp(float left, float top, float right, float bottom, const SkPaint* paint)
: DrawStrokableOp(left, top, right, bottom, paint) {}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
@@ -1244,7 +1155,7 @@ public:
}
virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("Draw Oval "RECT_STRING, RECT_ARGS(mLocalBounds));
+ OP_LOG("Draw Oval " RECT_STRING, RECT_ARGS(mLocalBounds));
}
virtual const char* name() { return "DrawOval"; }
@@ -1253,7 +1164,7 @@ public:
class DrawArcOp : public DrawStrokableOp {
public:
DrawArcOp(float left, float top, float right, float bottom,
- float startAngle, float sweepAngle, bool useCenter, SkPaint* paint)
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint)
: DrawStrokableOp(left, top, right, bottom, paint),
mStartAngle(startAngle), mSweepAngle(sweepAngle), mUseCenter(useCenter) {}
@@ -1264,7 +1175,7 @@ public:
}
virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("Draw Arc "RECT_STRING", start %f, sweep %f, useCenter %d",
+ OP_LOG("Draw Arc " RECT_STRING ", start %f, sweep %f, useCenter %d",
RECT_ARGS(mLocalBounds), mStartAngle, mSweepAngle, mUseCenter);
}
@@ -1278,7 +1189,7 @@ private:
class DrawPathOp : public DrawBoundedOp {
public:
- DrawPathOp(SkPath* path, SkPaint* paint)
+ DrawPathOp(const SkPath* path, const SkPaint* paint)
: DrawBoundedOp(paint), mPath(path) {
float left, top, offset;
uint32_t width, height;
@@ -1294,25 +1205,25 @@ public:
virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
const DeferredDisplayState& state) {
- SkPaint* paint = getPaint(renderer);
+ const SkPaint* paint = getPaint(renderer);
renderer.getCaches().pathCache.precache(mPath, paint);
deferInfo.batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture;
}
virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("Draw Path %p in "RECT_STRING, mPath, RECT_ARGS(mLocalBounds));
+ OP_LOG("Draw Path %p in " RECT_STRING, mPath, RECT_ARGS(mLocalBounds));
}
virtual const char* name() { return "DrawPath"; }
private:
- SkPath* mPath;
+ const SkPath* mPath;
};
class DrawLinesOp : public DrawBoundedOp {
public:
- DrawLinesOp(float* points, int count, SkPaint* paint)
+ DrawLinesOp(const float* points, int count, const SkPaint* paint)
: DrawBoundedOp(points, count, paint),
mPoints(points), mCount(count) {
mLocalBounds.outset(strokeWidthOutset());
@@ -1336,13 +1247,13 @@ public:
}
protected:
- float* mPoints;
+ const float* mPoints;
int mCount;
};
class DrawPointsOp : public DrawLinesOp {
public:
- DrawPointsOp(float* points, int count, SkPaint* paint)
+ DrawPointsOp(const float* points, int count, const SkPaint* paint)
: DrawLinesOp(points, count, paint) {}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
@@ -1358,20 +1269,24 @@ public:
class DrawSomeTextOp : public DrawOp {
public:
- DrawSomeTextOp(const char* text, int bytesCount, int count, SkPaint* paint)
+ DrawSomeTextOp(const char* text, int bytesCount, int count, const SkPaint* paint)
: DrawOp(paint), mText(text), mBytesCount(bytesCount), mCount(count) {};
virtual void output(int level, uint32_t logFlags) const {
OP_LOG("Draw some text, %d bytes", mBytesCount);
}
+ virtual bool hasTextShadow() const {
+ return OpenGLRenderer::hasTextShadow(mPaint);
+ }
+
virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
const DeferredDisplayState& state) {
- SkPaint* paint = getPaint(renderer);
+ const SkPaint* paint = getPaint(renderer);
FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
- fontRenderer.precache(paint, mText, mCount, mat4::identity());
+ fontRenderer.precache(paint, mText, mCount, SkMatrix::I());
- deferInfo.batchId = mPaint->getColor() == 0xff000000 ?
+ deferInfo.batchId = mPaint->getColor() == SK_ColorBLACK ?
DeferredDisplayList::kOpBatch_Text :
DeferredDisplayList::kOpBatch_ColorText;
}
@@ -1385,7 +1300,7 @@ protected:
class DrawTextOnPathOp : public DrawSomeTextOp {
public:
DrawTextOnPathOp(const char* text, int bytesCount, int count,
- SkPath* path, float hOffset, float vOffset, SkPaint* paint)
+ const SkPath* path, float hOffset, float vOffset, const SkPaint* paint)
: DrawSomeTextOp(text, bytesCount, count, paint),
mPath(path), mHOffset(hOffset), mVOffset(vOffset) {
/* TODO: inherit from DrawBounded and init mLocalBounds */
@@ -1399,7 +1314,7 @@ public:
virtual const char* name() { return "DrawTextOnPath"; }
private:
- SkPath* mPath;
+ const SkPath* mPath;
float mHOffset;
float mVOffset;
};
@@ -1407,7 +1322,7 @@ private:
class DrawPosTextOp : public DrawSomeTextOp {
public:
DrawPosTextOp(const char* text, int bytesCount, int count,
- const float* positions, SkPaint* paint)
+ const float* positions, const SkPaint* paint)
: DrawSomeTextOp(text, bytesCount, count, paint), mPositions(positions) {
/* TODO: inherit from DrawBounded and init mLocalBounds */
}
@@ -1425,37 +1340,40 @@ private:
class DrawTextOp : public DrawBoundedOp {
public:
DrawTextOp(const char* text, int bytesCount, int count, float x, float y,
- const float* positions, SkPaint* paint, float totalAdvance, const Rect& bounds)
+ const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds)
: DrawBoundedOp(bounds, paint), mText(text), mBytesCount(bytesCount), mCount(count),
mX(x), mY(y), mPositions(positions), mTotalAdvance(totalAdvance) {
- memset(&mPrecacheTransform.data[0], 0xff, 16 * sizeof(float));
+ mPrecacheTransform = SkMatrix::InvalidMatrix();
}
virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
const DeferredDisplayState& state) {
- SkPaint* paint = getPaint(renderer);
+ const SkPaint* paint = getPaint(renderer);
FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint);
- const mat4& transform = renderer.findBestFontTransform(state.mMatrix);
+ SkMatrix transform;
+ renderer.findBestFontTransform(state.mMatrix, &transform);
if (mPrecacheTransform != transform) {
fontRenderer.precache(paint, mText, mCount, transform);
mPrecacheTransform = transform;
}
- deferInfo.batchId = mPaint->getColor() == 0xff000000 ?
+ deferInfo.batchId = mPaint->getColor() == SK_ColorBLACK ?
DeferredDisplayList::kOpBatch_Text :
DeferredDisplayList::kOpBatch_ColorText;
deferInfo.mergeId = reinterpret_cast<mergeid_t>(mPaint->getColor());
// don't merge decorated text - the decorations won't draw in order
- bool noDecorations = !(mPaint->getFlags() & (SkPaint::kUnderlineText_Flag |
- SkPaint::kStrikeThruText_Flag));
- deferInfo.mergeable = state.mMatrix.isPureTranslate() && noDecorations &&
- OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode;
+ bool hasDecorations = mPaint->getFlags()
+ & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag);
+
+ deferInfo.mergeable = state.mMatrix.isPureTranslate()
+ && !hasDecorations
+ && OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode;
}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
Rect bounds;
- getLocalBounds(renderer.getDrawModifiers(), bounds);
+ getLocalBounds(bounds);
return renderer.drawText(mText, mBytesCount, mCount, mX, mY,
mPositions, getPaint(renderer), mTotalAdvance, bounds);
}
@@ -1492,7 +1410,7 @@ private:
float mY;
const float* mPositions;
float mTotalAdvance;
- mat4 mPrecacheTransform;
+ SkMatrix mPrecacheTransform;
};
///////////////////////////////////////////////////////////////////////////////
@@ -1502,7 +1420,7 @@ private:
class DrawFunctorOp : public DrawOp {
public:
DrawFunctorOp(Functor* functor)
- : DrawOp(0), mFunctor(functor) {}
+ : DrawOp(NULL), mFunctor(functor) {}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
renderer.startMark("GL functor");
@@ -1521,48 +1439,118 @@ private:
Functor* mFunctor;
};
-class DrawDisplayListOp : public DrawBoundedOp {
+class DrawRenderNodeOp : public DrawBoundedOp {
+ friend class RenderNode; // grant RenderNode access to info of child
public:
- DrawDisplayListOp(DisplayList* displayList, int flags)
- : DrawBoundedOp(0, 0, displayList->getWidth(), displayList->getHeight(), 0),
- mDisplayList(displayList), mFlags(flags) {}
+ DrawRenderNodeOp(RenderNode* renderNode, int flags, const mat4& transformFromParent)
+ : DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), 0),
+ mRenderNode(renderNode), mFlags(flags), mTransformFromParent(transformFromParent) {}
virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level,
bool useQuickReject) {
- if (mDisplayList && mDisplayList->isRenderable()) {
- mDisplayList->defer(deferStruct, level + 1);
+ if (mRenderNode && mRenderNode->isRenderable() && !mSkipInOrderDraw) {
+ mRenderNode->defer(deferStruct, level + 1);
}
}
virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level,
bool useQuickReject) {
- if (mDisplayList && mDisplayList->isRenderable()) {
- mDisplayList->replay(replayStruct, level + 1);
+ if (mRenderNode && mRenderNode->isRenderable() && !mSkipInOrderDraw) {
+ mRenderNode->replay(replayStruct, level + 1);
}
}
- // NOT USED since replay() is overridden
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
- return DrawGlInfo::kStatusDone;
+ LOG_ALWAYS_FATAL("should not be called, because replay() is overridden");
+ return 0;
}
virtual void output(int level, uint32_t logFlags) const {
- OP_LOG("Draw Display List %p, flags %#x", mDisplayList, mFlags);
- if (mDisplayList && (logFlags & kOpLogFlag_Recurse)) {
- mDisplayList->output(level + 1);
+ OP_LOG("Draw Display List %p, flags %#x", mRenderNode, mFlags);
+ if (mRenderNode && (logFlags & kOpLogFlag_Recurse)) {
+ mRenderNode->output(level + 1);
}
}
- virtual const char* name() { return "DrawDisplayList"; }
+ virtual const char* name() { return "DrawRenderNode"; }
+
+ RenderNode* renderNode() { return mRenderNode; }
private:
- DisplayList* mDisplayList;
- int mFlags;
+ RenderNode* mRenderNode;
+ const int mFlags;
+
+ ///////////////////////////
+ // Properties below are used by RenderNode::computeOrderingImpl() and issueOperations()
+ ///////////////////////////
+ /**
+ * Records transform vs parent, used for computing total transform without rerunning DL contents
+ */
+ const mat4 mTransformFromParent;
+
+ /**
+ * Holds the transformation between the projection surface ViewGroup and this RenderNode
+ * drawing instance. Represents any translations / transformations done within the drawing of
+ * the compositing ancestor ViewGroup's draw, before the draw of the View represented by this
+ * DisplayList draw instance.
+ *
+ * Note: doesn't include transformation within the RenderNode, or its properties.
+ */
+ mat4 mTransformFromCompositingAncestor;
+ bool mSkipInOrderDraw;
+};
+
+/**
+ * Not a canvas operation, used only by 3d / z ordering logic in RenderNode::iterate()
+ */
+class DrawShadowOp : public DrawOp {
+public:
+ DrawShadowOp(const mat4& transformXY, const mat4& transformZ,
+ float casterAlpha, const SkPath* casterOutline)
+ : DrawOp(NULL)
+ , mTransformXY(transformXY)
+ , mTransformZ(transformZ)
+ , mCasterAlpha(casterAlpha)
+ , mCasterOutline(casterOutline) {
+ }
+
+ virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo,
+ const DeferredDisplayState& state) {
+ renderer.getCaches().tessellationCache.precacheShadows(&state.mMatrix,
+ renderer.getLocalClipBounds(), isCasterOpaque(), mCasterOutline,
+ &mTransformXY, &mTransformZ, renderer.getLightCenter(), renderer.getLightRadius());
+ }
+
+ virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
+ TessellationCache::vertexBuffer_pair_t buffers;
+ Matrix4 drawTransform;
+ renderer.getMatrix(&drawTransform);
+ renderer.getCaches().tessellationCache.getShadowBuffers(&drawTransform,
+ renderer.getLocalClipBounds(), isCasterOpaque(), mCasterOutline,
+ &mTransformXY, &mTransformZ, renderer.getLightCenter(), renderer.getLightRadius(),
+ buffers);
+
+ return renderer.drawShadow(mCasterAlpha, buffers.first, buffers.second);
+ }
+
+ virtual void output(int level, uint32_t logFlags) const {
+ OP_LOGS("DrawShadow");
+ }
+
+ virtual const char* name() { return "DrawShadow"; }
+
+private:
+ bool isCasterOpaque() { return mCasterAlpha >= 1.0f; }
+
+ const mat4 mTransformXY;
+ const mat4 mTransformZ;
+ const float mCasterAlpha;
+ const SkPath* mCasterOutline;
};
class DrawLayerOp : public DrawOp {
public:
DrawLayerOp(Layer* layer, float x, float y)
- : DrawOp(0), mLayer(layer), mX(x), mY(y) {}
+ : DrawOp(NULL), mLayer(layer), mX(x), mY(y) {}
virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
return renderer.drawLayer(mLayer, mX, mY);
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 8866029..94162fc 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -21,120 +21,54 @@
#include <private/hwui/DrawGlInfo.h>
-#include "DisplayList.h"
+#include "Caches.h"
#include "DeferredDisplayList.h"
#include "DisplayListLogBuffer.h"
#include "DisplayListOp.h"
#include "DisplayListRenderer.h"
-#include "Caches.h"
+#include "RenderNode.h"
namespace android {
namespace uirenderer {
-DisplayListRenderer::DisplayListRenderer():
- mCaches(Caches::getInstance()), mDisplayListData(new DisplayListData),
- mTranslateX(0.0f), mTranslateY(0.0f), mHasTranslate(false),
- mHasDrawOps(false), mFunctorCount(0) {
+DisplayListRenderer::DisplayListRenderer()
+ : mCaches(Caches::getInstance())
+ , mDisplayListData(0)
+ , mTranslateX(0.0f)
+ , mTranslateY(0.0f)
+ , mHasTranslate(false)
+ , mHighContrastText(false)
+ , mRestoreSaveCount(-1) {
}
DisplayListRenderer::~DisplayListRenderer() {
- reset();
-}
-
-void DisplayListRenderer::reset() {
- mDisplayListData = new DisplayListData();
- mCaches.resourceCache.lock();
-
- for (size_t i = 0; i < mBitmapResources.size(); i++) {
- mCaches.resourceCache.decrementRefcountLocked(mBitmapResources.itemAt(i));
- }
-
- for (size_t i = 0; i < mOwnedBitmapResources.size(); i++) {
- mCaches.resourceCache.decrementRefcountLocked(mOwnedBitmapResources.itemAt(i));
- }
-
- for (size_t i = 0; i < mFilterResources.size(); i++) {
- mCaches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i));
- }
-
- for (size_t i = 0; i < mPatchResources.size(); i++) {
- mCaches.resourceCache.decrementRefcountLocked(mPatchResources.itemAt(i));
- }
-
- for (size_t i = 0; i < mShaders.size(); i++) {
- mCaches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i));
- }
-
- for (size_t i = 0; i < mSourcePaths.size(); i++) {
- mCaches.resourceCache.decrementRefcountLocked(mSourcePaths.itemAt(i));
- }
-
- for (size_t i = 0; i < mLayers.size(); i++) {
- mCaches.resourceCache.decrementRefcountLocked(mLayers.itemAt(i));
- }
-
- mCaches.resourceCache.unlock();
-
- mBitmapResources.clear();
- mOwnedBitmapResources.clear();
- mFilterResources.clear();
- mPatchResources.clear();
- mSourcePaths.clear();
-
- mShaders.clear();
- mShaderMap.clear();
-
- mPaints.clear();
- mPaintMap.clear();
-
- mRegions.clear();
- mRegionMap.clear();
-
- mPaths.clear();
- mPathMap.clear();
-
- mMatrices.clear();
-
- mLayers.clear();
-
- mHasDrawOps = false;
- mFunctorCount = 0;
+ LOG_ALWAYS_FATAL_IF(mDisplayListData,
+ "Destroyed a DisplayListRenderer during a record!");
}
///////////////////////////////////////////////////////////////////////////////
// Operations
///////////////////////////////////////////////////////////////////////////////
-DisplayList* DisplayListRenderer::getDisplayList(DisplayList* displayList) {
- if (!displayList) {
- displayList = new DisplayList(*this);
- } else {
- displayList->initFromDisplayListRenderer(*this, true);
- }
- displayList->setRenderable(mHasDrawOps);
- return displayList;
-}
-
-bool DisplayListRenderer::isDeferred() {
- return true;
-}
-
-void DisplayListRenderer::setViewport(int width, int height) {
- mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1);
-
- mWidth = width;
- mHeight = height;
+DisplayListData* DisplayListRenderer::finishRecording() {
+ mPaintMap.clear();
+ mRegionMap.clear();
+ mPathMap.clear();
+ DisplayListData* data = mDisplayListData;
+ mDisplayListData = 0;
+ return data;
}
status_t DisplayListRenderer::prepareDirty(float left, float top,
float right, float bottom, bool opaque) {
- mSnapshot = new Snapshot(mFirstSnapshot,
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- mSaveCount = 1;
- mSnapshot->setClip(0.0f, 0.0f, mWidth, mHeight);
- mDirtyClip = opaque;
+ LOG_ALWAYS_FATAL_IF(mDisplayListData,
+ "prepareDirty called a second time during a recording!");
+ mDisplayListData = new DisplayListData();
+
+ initializeSaveStack(0, 0, getWidth(), getHeight(), Vector3());
+ mDirtyClip = opaque;
mRestoreSaveCount = -1;
return DrawGlInfo::kStatusDone; // No invalidate needed at record-time
@@ -154,13 +88,13 @@ void DisplayListRenderer::resume() {
status_t DisplayListRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) {
// Ignore dirty during recording, it matters only when we replay
addDrawOp(new (alloc()) DrawFunctorOp(functor));
- mFunctorCount++;
+ mDisplayListData->functors.add(functor);
return DrawGlInfo::kStatusDone; // No invalidate needed at record-time
}
int DisplayListRenderer::save(int flags) {
addStateOp(new (alloc()) SaveOp(flags));
- return OpenGLRenderer::save(flags);
+ return StatefulBaseRenderer::save(flags);
}
void DisplayListRenderer::restore() {
@@ -171,84 +105,86 @@ void DisplayListRenderer::restore() {
mRestoreSaveCount--;
insertTranslate();
- OpenGLRenderer::restore();
+ StatefulBaseRenderer::restore();
}
void DisplayListRenderer::restoreToCount(int saveCount) {
mRestoreSaveCount = saveCount;
insertTranslate();
- OpenGLRenderer::restoreToCount(saveCount);
+ StatefulBaseRenderer::restoreToCount(saveCount);
}
int DisplayListRenderer::saveLayer(float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags) {
- addStateOp(new (alloc()) SaveLayerOp(left, top, right, bottom, alpha, mode, flags));
- return OpenGLRenderer::save(flags);
+ const SkPaint* paint, int flags) {
+ paint = refPaint(paint);
+ addStateOp(new (alloc()) SaveLayerOp(left, top, right, bottom, paint, flags));
+ return StatefulBaseRenderer::save(flags);
}
-void DisplayListRenderer::translate(float dx, float dy) {
+void DisplayListRenderer::translate(float dx, float dy, float dz) {
+ // ignore dz, not used at defer time
mHasTranslate = true;
mTranslateX += dx;
mTranslateY += dy;
insertRestoreToCount();
- OpenGLRenderer::translate(dx, dy);
+ StatefulBaseRenderer::translate(dx, dy, dz);
}
void DisplayListRenderer::rotate(float degrees) {
addStateOp(new (alloc()) RotateOp(degrees));
- OpenGLRenderer::rotate(degrees);
+ StatefulBaseRenderer::rotate(degrees);
}
void DisplayListRenderer::scale(float sx, float sy) {
addStateOp(new (alloc()) ScaleOp(sx, sy));
- OpenGLRenderer::scale(sx, sy);
+ StatefulBaseRenderer::scale(sx, sy);
}
void DisplayListRenderer::skew(float sx, float sy) {
addStateOp(new (alloc()) SkewOp(sx, sy));
- OpenGLRenderer::skew(sx, sy);
+ StatefulBaseRenderer::skew(sx, sy);
}
-void DisplayListRenderer::setMatrix(SkMatrix* matrix) {
- matrix = refMatrix(matrix);
+void DisplayListRenderer::setMatrix(const SkMatrix& matrix) {
addStateOp(new (alloc()) SetMatrixOp(matrix));
- OpenGLRenderer::setMatrix(matrix);
+ StatefulBaseRenderer::setMatrix(matrix);
}
-void DisplayListRenderer::concatMatrix(SkMatrix* matrix) {
- matrix = refMatrix(matrix);
+void DisplayListRenderer::concatMatrix(const SkMatrix& matrix) {
addStateOp(new (alloc()) ConcatMatrixOp(matrix));
- OpenGLRenderer::concatMatrix(matrix);
+ StatefulBaseRenderer::concatMatrix(matrix);
}
bool DisplayListRenderer::clipRect(float left, float top, float right, float bottom,
SkRegion::Op op) {
addStateOp(new (alloc()) ClipRectOp(left, top, right, bottom, op));
- return OpenGLRenderer::clipRect(left, top, right, bottom, op);
+ return StatefulBaseRenderer::clipRect(left, top, right, bottom, op);
}
-bool DisplayListRenderer::clipPath(SkPath* path, SkRegion::Op op) {
+bool DisplayListRenderer::clipPath(const SkPath* path, SkRegion::Op op) {
path = refPath(path);
addStateOp(new (alloc()) ClipPathOp(path, op));
- return OpenGLRenderer::clipPath(path, op);
+ return StatefulBaseRenderer::clipPath(path, op);
}
-bool DisplayListRenderer::clipRegion(SkRegion* region, SkRegion::Op op) {
+bool DisplayListRenderer::clipRegion(const SkRegion* region, SkRegion::Op op) {
region = refRegion(region);
addStateOp(new (alloc()) ClipRegionOp(region, op));
- return OpenGLRenderer::clipRegion(region, op);
+ return StatefulBaseRenderer::clipRegion(region, op);
}
-status_t DisplayListRenderer::drawDisplayList(DisplayList* displayList,
- Rect& dirty, int32_t flags) {
+status_t DisplayListRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t flags) {
// dirty is an out parameter and should not be recorded,
// it matters only when replaying the display list
+ DrawRenderNodeOp* op = new (alloc()) DrawRenderNodeOp(renderNode, flags, *currentTransform());
+ int opIndex = addDrawOp(op);
+ mDisplayListData->addChild(op);
- // TODO: To be safe, the display list should be ref-counted in the
- // resources cache, but we rely on the caller (UI toolkit) to
- // do the right thing for now
+ if (renderNode->stagingProperties().isProjectionReceiver()) {
+ // use staging property, since recording on UI thread
+ mDisplayListData->projectionReceiveIndex = opIndex;
+ }
- addDrawOp(new (alloc()) DrawDisplayListOp(displayList, flags));
return DrawGlInfo::kStatusDone;
}
@@ -258,68 +194,60 @@ status_t DisplayListRenderer::drawLayer(Layer* layer, float x, float y) {
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) {
- bitmap = refBitmap(bitmap);
- paint = refPaint(paint);
-
- addDrawOp(new (alloc()) DrawBitmapOp(bitmap, left, top, paint));
- return DrawGlInfo::kStatusDone;
-}
-
-status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint) {
+status_t DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
bitmap = refBitmap(bitmap);
- matrix = refMatrix(matrix);
paint = refPaint(paint);
- addDrawOp(new (alloc()) DrawBitmapMatrixOp(bitmap, matrix, paint));
+ addDrawOp(new (alloc()) DrawBitmapOp(bitmap, paint));
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
+status_t DisplayListRenderer::drawBitmap(const SkBitmap* bitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop,
- float dstRight, float dstBottom, SkPaint* paint) {
- bitmap = refBitmap(bitmap);
- paint = refPaint(paint);
-
- if (srcLeft == 0 && srcTop == 0 &&
- srcRight == bitmap->width() && srcBottom == bitmap->height() &&
- (srcBottom - srcTop == dstBottom - dstTop) &&
- (srcRight - srcLeft == dstRight - dstLeft)) {
+ float dstRight, float dstBottom, const SkPaint* paint) {
+ if (srcLeft == 0 && srcTop == 0
+ && srcRight == bitmap->width() && srcBottom == bitmap->height()
+ && (srcBottom - srcTop == dstBottom - dstTop)
+ && (srcRight - srcLeft == dstRight - dstLeft)) {
// transform simple rect to rect drawing case into position bitmap ops, since they merge
- addDrawOp(new (alloc()) DrawBitmapOp(bitmap, dstLeft, dstTop, paint));
- return DrawGlInfo::kStatusDone;
- }
+ save(SkCanvas::kMatrix_SaveFlag);
+ translate(dstLeft, dstTop);
+ drawBitmap(bitmap, paint);
+ restore();
+ } else {
+ bitmap = refBitmap(bitmap);
+ paint = refPaint(paint);
- addDrawOp(new (alloc()) DrawBitmapRectOp(bitmap,
- srcLeft, srcTop, srcRight, srcBottom,
- dstLeft, dstTop, dstRight, dstBottom, paint));
+ addDrawOp(new (alloc()) DrawBitmapRectOp(bitmap,
+ srcLeft, srcTop, srcRight, srcBottom,
+ dstLeft, dstTop, dstRight, dstBottom, paint));
+ }
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawBitmapData(SkBitmap* bitmap, float left, float top,
- SkPaint* paint) {
+status_t DisplayListRenderer::drawBitmapData(const SkBitmap* bitmap, const SkPaint* paint) {
bitmap = refBitmapData(bitmap);
paint = refPaint(paint);
- addDrawOp(new (alloc()) DrawBitmapDataOp(bitmap, left, top, paint));
+ addDrawOp(new (alloc()) DrawBitmapDataOp(bitmap, paint));
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight,
- float* vertices, int* colors, SkPaint* paint) {
- int count = (meshWidth + 1) * (meshHeight + 1) * 2;
+status_t DisplayListRenderer::drawBitmapMesh(const SkBitmap* bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint) {
+ int vertexCount = (meshWidth + 1) * (meshHeight + 1);
bitmap = refBitmap(bitmap);
- vertices = refBuffer<float>(vertices, count);
+ vertices = refBuffer<float>(vertices, vertexCount * 2); // 2 floats per vertex
paint = refPaint(paint);
- colors = refBuffer<int>(colors, count);
+ colors = refBuffer<int>(colors, vertexCount); // 1 color per vertex
addDrawOp(new (alloc()) DrawBitmapMeshOp(bitmap, meshWidth, meshHeight,
vertices, colors, paint));
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
- float left, float top, float right, float bottom, SkPaint* paint) {
+status_t DisplayListRenderer::drawPatch(const SkBitmap* bitmap, const Res_png_9patch* patch,
+ float left, float top, float right, float bottom, const SkPaint* paint) {
bitmap = refBitmap(bitmap);
patch = refPatch(patch);
paint = refPaint(paint);
@@ -334,41 +262,56 @@ status_t DisplayListRenderer::drawColor(int color, SkXfermode::Mode mode) {
}
status_t DisplayListRenderer::drawRect(float left, float top, float right, float bottom,
- SkPaint* paint) {
+ const SkPaint* paint) {
paint = refPaint(paint);
addDrawOp(new (alloc()) DrawRectOp(left, top, right, bottom, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawRoundRect(float left, float top, float right, float bottom,
- float rx, float ry, SkPaint* paint) {
+ float rx, float ry, const SkPaint* paint) {
paint = refPaint(paint);
addDrawOp(new (alloc()) DrawRoundRectOp(left, top, right, bottom, rx, ry, paint));
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawCircle(float x, float y, float radius, SkPaint* paint) {
+status_t DisplayListRenderer::drawCircle(float x, float y, float radius, const SkPaint* paint) {
paint = refPaint(paint);
addDrawOp(new (alloc()) DrawCircleOp(x, y, radius, paint));
return DrawGlInfo::kStatusDone;
}
+status_t DisplayListRenderer::drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
+ CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) {
+ mDisplayListData->refProperty(x);
+ mDisplayListData->refProperty(y);
+ mDisplayListData->refProperty(radius);
+ mDisplayListData->refProperty(paint);
+ addDrawOp(new (alloc()) DrawCirclePropsOp(&x->value, &y->value,
+ &radius->value, &paint->value));
+ return DrawGlInfo::kStatusDone;
+}
+
status_t DisplayListRenderer::drawOval(float left, float top, float right, float bottom,
- SkPaint* paint) {
+ const SkPaint* paint) {
paint = refPaint(paint);
addDrawOp(new (alloc()) DrawOvalOp(left, top, right, bottom, paint));
return DrawGlInfo::kStatusDone;
}
status_t DisplayListRenderer::drawArc(float left, float top, float right, float bottom,
- float startAngle, float sweepAngle, bool useCenter, SkPaint* paint) {
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) {
+ if (fabs(sweepAngle) >= 360.0f) {
+ return drawOval(left, top, right, bottom, paint);
+ }
+
paint = refPaint(paint);
addDrawOp(new (alloc()) DrawArcOp(left, top, right, bottom,
startAngle, sweepAngle, useCenter, paint));
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawPath(SkPath* path, SkPaint* paint) {
+status_t DisplayListRenderer::drawPath(const SkPath* path, const SkPaint* paint) {
path = refPath(path);
paint = refPaint(paint);
@@ -376,7 +319,7 @@ status_t DisplayListRenderer::drawPath(SkPath* path, SkPaint* paint) {
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawLines(float* points, int count, SkPaint* paint) {
+status_t DisplayListRenderer::drawLines(const float* points, int count, const SkPaint* paint) {
points = refBuffer<float>(points, count);
paint = refPaint(paint);
@@ -384,7 +327,7 @@ status_t DisplayListRenderer::drawLines(float* points, int count, SkPaint* paint
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawPoints(float* points, int count, SkPaint* paint) {
+status_t DisplayListRenderer::drawPoints(const float* points, int count, const SkPaint* paint) {
points = refBuffer<float>(points, count);
paint = refPaint(paint);
@@ -393,7 +336,7 @@ status_t DisplayListRenderer::drawPoints(float* points, int count, SkPaint* pain
}
status_t DisplayListRenderer::drawTextOnPath(const char* text, int bytesCount, int count,
- SkPath* path, float hOffset, float vOffset, SkPaint* paint) {
+ const SkPath* path, float hOffset, float vOffset, const SkPaint* paint) {
if (!text || count <= 0) return DrawGlInfo::kStatusDone;
text = refText(text, bytesCount);
@@ -407,7 +350,7 @@ status_t DisplayListRenderer::drawTextOnPath(const char* text, int bytesCount, i
}
status_t DisplayListRenderer::drawPosText(const char* text, int bytesCount, int count,
- const float* positions, SkPaint* paint) {
+ const float* positions, const SkPaint* paint) {
if (!text || count <= 0) return DrawGlInfo::kStatusDone;
text = refText(text, bytesCount);
@@ -419,23 +362,56 @@ status_t DisplayListRenderer::drawPosText(const char* text, int bytesCount, int
return DrawGlInfo::kStatusDone;
}
+static void simplifyPaint(int color, SkPaint* paint) {
+ paint->setColor(color);
+ paint->setShader(NULL);
+ paint->setColorFilter(NULL);
+ paint->setLooper(NULL);
+ paint->setStrokeWidth(4 + 0.04 * paint->getTextSize());
+ paint->setStrokeJoin(SkPaint::kRound_Join);
+ paint->setLooper(NULL);
+}
+
status_t DisplayListRenderer::drawText(const char* text, int bytesCount, int count,
- float x, float y, const float* positions, SkPaint* paint,
+ float x, float y, const float* positions, const SkPaint* paint,
float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode) {
if (!text || count <= 0) return DrawGlInfo::kStatusDone;
text = refText(text, bytesCount);
positions = refBuffer<float>(positions, count * 2);
- paint = refPaint(paint);
- DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count,
- x, y, positions, paint, totalAdvance, bounds);
- addDrawOp(op);
+ if (CC_UNLIKELY(mHighContrastText)) {
+ // high contrast draw path
+ int color = paint->getColor();
+ int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+ bool darken = channelSum < (128 * 3);
+
+ // outline
+ SkPaint* outlinePaint = copyPaint(paint);
+ simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, outlinePaint);
+ outlinePaint->setStyle(SkPaint::kStrokeAndFill_Style);
+ addDrawOp(new (alloc()) DrawTextOp(text, bytesCount, count,
+ x, y, positions, outlinePaint, totalAdvance, bounds)); // bounds?
+
+ // inner
+ SkPaint* innerPaint = copyPaint(paint);
+ simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, innerPaint);
+ innerPaint->setStyle(SkPaint::kFill_Style);
+ addDrawOp(new (alloc()) DrawTextOp(text, bytesCount, count,
+ x, y, positions, innerPaint, totalAdvance, bounds));
+ } else {
+ // standard draw path
+ paint = refPaint(paint);
+
+ DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count,
+ x, y, positions, paint, totalAdvance, bounds);
+ addDrawOp(op);
+ }
return DrawGlInfo::kStatusDone;
}
-status_t DisplayListRenderer::drawRects(const float* rects, int count, SkPaint* paint) {
+status_t DisplayListRenderer::drawRects(const float* rects, int count, const SkPaint* paint) {
if (count <= 0) return DrawGlInfo::kStatusDone;
rects = refBuffer<float>(rects, count);
@@ -444,34 +420,6 @@ status_t DisplayListRenderer::drawRects(const float* rects, int count, SkPaint*
return DrawGlInfo::kStatusDone;
}
-void DisplayListRenderer::resetShader() {
- addStateOp(new (alloc()) ResetShaderOp());
-}
-
-void DisplayListRenderer::setupShader(SkiaShader* shader) {
- shader = refShader(shader);
- addStateOp(new (alloc()) SetupShaderOp(shader));
-}
-
-void DisplayListRenderer::resetColorFilter() {
- addStateOp(new (alloc()) ResetColorFilterOp());
-}
-
-void DisplayListRenderer::setupColorFilter(SkiaColorFilter* filter) {
- filter = refColorFilter(filter);
- addStateOp(new (alloc()) SetupColorFilterOp(filter));
-}
-
-void DisplayListRenderer::resetShadow() {
- addStateOp(new (alloc()) ResetShadowOp());
- OpenGLRenderer::resetShadow();
-}
-
-void DisplayListRenderer::setupShadow(float radius, float dx, float dy, int color) {
- addStateOp(new (alloc()) SetupShadowOp(radius, dx, dy, color));
- OpenGLRenderer::setupShadow(radius, dx, dy, color);
-}
-
void DisplayListRenderer::resetPaintFilter() {
addStateOp(new (alloc()) ResetPaintFilterOp());
}
@@ -499,20 +447,20 @@ void DisplayListRenderer::insertTranslate() {
}
}
-void DisplayListRenderer::addStateOp(StateOp* op) {
- addOpInternal(op);
+int DisplayListRenderer::addStateOp(StateOp* op) {
+ return addOpInternal(op);
}
-void DisplayListRenderer::addDrawOp(DrawOp* op) {
+int DisplayListRenderer::addDrawOp(DrawOp* op) {
Rect localBounds;
- if (op->getLocalBounds(mDrawModifiers, localBounds)) {
- bool rejected = quickRejectNoScissor(localBounds.left, localBounds.top,
+ if (op->getLocalBounds(localBounds)) {
+ bool rejected = quickRejectConservative(localBounds.left, localBounds.top,
localBounds.right, localBounds.bottom);
op->setQuickRejected(rejected);
}
- mHasDrawOps = true;
- addOpInternal(op);
+ mDisplayListData->hasDrawOps = true;
+ return addOpInternal(op);
}
}; // namespace uirenderer
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index d233150..b5c0159 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -22,9 +22,8 @@
#include <SkPath.h>
#include <cutils/compiler.h>
-#include "DisplayList.h"
#include "DisplayListLogBuffer.h"
-#include "OpenGLRenderer.h"
+#include "RenderNode.h"
namespace android {
namespace uirenderer {
@@ -33,9 +32,6 @@ namespace uirenderer {
// Defines
///////////////////////////////////////////////////////////////////////////////
-#define MIN_WRITER_SIZE 4096
-#define OP_MAY_BE_SKIPPED_MASK 0xff000000
-
// Debug
#if DEBUG_DISPLAY_LIST
#define DISPLAY_LIST_LOGD(...) ALOGD(__VA_ARGS__)
@@ -54,160 +50,128 @@ class DrawOp;
class StateOp;
/**
- * Records drawing commands in a display list for latter playback.
+ * Records drawing commands in a display list for later playback into an OpenGLRenderer.
*/
-class DisplayListRenderer: public OpenGLRenderer {
+class ANDROID_API DisplayListRenderer: public StatefulBaseRenderer {
public:
- ANDROID_API DisplayListRenderer();
+ DisplayListRenderer();
virtual ~DisplayListRenderer();
- ANDROID_API DisplayList* getDisplayList(DisplayList* displayList);
-
- virtual bool isDeferred();
+ DisplayListData* finishRecording();
- virtual void setViewport(int width, int height);
+// ----------------------------------------------------------------------------
+// Frame state operations
+// ----------------------------------------------------------------------------
virtual status_t prepareDirty(float left, float top, float right, float bottom, bool opaque);
virtual void finish();
-
- virtual status_t callDrawGLFunction(Functor *functor, Rect& dirty);
-
virtual void interrupt();
virtual void resume();
+// ----------------------------------------------------------------------------
+// Canvas state operations
+// ----------------------------------------------------------------------------
+ // Save (layer)
virtual int save(int flags);
virtual void restore();
virtual void restoreToCount(int saveCount);
-
virtual int saveLayer(float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags);
+ const SkPaint* paint, int flags);
- virtual void translate(float dx, float dy);
+ // Matrix
+ virtual void translate(float dx, float dy, float dz = 0.0f);
virtual void rotate(float degrees);
virtual void scale(float sx, float sy);
virtual void skew(float sx, float sy);
- virtual void setMatrix(SkMatrix* matrix);
- virtual void concatMatrix(SkMatrix* matrix);
+ virtual void setMatrix(const SkMatrix& matrix);
+ virtual void concatMatrix(const SkMatrix& matrix);
+ // Clip
virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
- virtual bool clipPath(SkPath* path, SkRegion::Op op);
- virtual bool clipRegion(SkRegion* region, SkRegion::Op op);
-
- virtual status_t drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t flags);
- virtual status_t drawLayer(Layer* layer, float x, float y);
- virtual status_t drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
- virtual status_t drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
- virtual status_t drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
- float srcRight, float srcBottom, float dstLeft, float dstTop,
- float dstRight, float dstBottom, SkPaint* paint);
- virtual status_t drawBitmapData(SkBitmap* bitmap, float left, float top, SkPaint* paint);
- virtual status_t drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight,
- float* vertices, int* colors, SkPaint* paint);
- virtual status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
- float left, float top, float right, float bottom, SkPaint* paint);
- virtual status_t drawColor(int color, SkXfermode::Mode mode);
- virtual status_t drawRect(float left, float top, float right, float bottom, SkPaint* paint);
- virtual status_t drawRoundRect(float left, float top, float right, float bottom,
- float rx, float ry, SkPaint* paint);
- virtual status_t drawCircle(float x, float y, float radius, SkPaint* paint);
- virtual status_t drawOval(float left, float top, float right, float bottom, SkPaint* paint);
- virtual status_t drawArc(float left, float top, float right, float bottom,
- float startAngle, float sweepAngle, bool useCenter, SkPaint* paint);
- virtual status_t drawPath(SkPath* path, SkPaint* paint);
- virtual status_t drawLines(float* points, int count, SkPaint* paint);
- virtual status_t drawPoints(float* points, int count, SkPaint* paint);
- virtual status_t drawTextOnPath(const char* text, int bytesCount, int count, SkPath* path,
- float hOffset, float vOffset, SkPaint* paint);
- virtual status_t drawPosText(const char* text, int bytesCount, int count,
- const float* positions, SkPaint* paint);
- virtual status_t drawText(const char* text, int bytesCount, int count, float x, float y,
- const float* positions, SkPaint* paint, float totalAdvance, const Rect& bounds,
- DrawOpMode drawOpMode);
-
- virtual status_t drawRects(const float* rects, int count, SkPaint* paint);
-
- virtual void resetShader();
- virtual void setupShader(SkiaShader* shader);
-
- virtual void resetColorFilter();
- virtual void setupColorFilter(SkiaColorFilter* filter);
-
- virtual void resetShadow();
- virtual void setupShadow(float radius, float dx, float dy, int color);
+ virtual bool clipPath(const SkPath* path, SkRegion::Op op);
+ virtual bool clipRegion(const SkRegion* region, SkRegion::Op op);
+ // Misc - should be implemented with SkPaint inspection
virtual void resetPaintFilter();
virtual void setupPaintFilter(int clearBits, int setBits);
- ANDROID_API void reset();
-
- sp<DisplayListData> getDisplayListData() const {
- return mDisplayListData;
+ bool isCurrentTransformSimple() {
+ return currentTransform()->isSimple();
}
- const Vector<SkBitmap*>& getBitmapResources() const {
- return mBitmapResources;
- }
-
- const Vector<SkBitmap*>& getOwnedBitmapResources() const {
- return mOwnedBitmapResources;
- }
-
- const Vector<SkiaColorFilter*>& getFilterResources() const {
- return mFilterResources;
- }
-
- const Vector<Res_png_9patch*>& getPatchResources() const {
- return mPatchResources;
- }
-
- const Vector<SkiaShader*>& getShaders() const {
- return mShaders;
- }
-
- const Vector<SkPaint*>& getPaints() const {
- return mPaints;
- }
-
- const Vector<SkPath*>& getPaths() const {
- return mPaths;
- }
+// ----------------------------------------------------------------------------
+// Canvas draw operations
+// ----------------------------------------------------------------------------
+ virtual status_t drawColor(int color, SkXfermode::Mode mode);
- const SortedVector<SkPath*>& getSourcePaths() const {
- return mSourcePaths;
- }
+ // Bitmap-based
+ virtual status_t drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
+ virtual status_t drawBitmap(const SkBitmap* bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint);
+ virtual status_t drawBitmapData(const SkBitmap* bitmap, const SkPaint* paint);
+ virtual status_t drawBitmapMesh(const SkBitmap* bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint);
+ virtual status_t drawPatch(const SkBitmap* bitmap, const Res_png_9patch* patch,
+ float left, float top, float right, float bottom, const SkPaint* paint);
+
+ // Shapes
+ virtual status_t drawRect(float left, float top, float right, float bottom,
+ const SkPaint* paint);
+ virtual status_t drawRects(const float* rects, int count, const SkPaint* paint);
+ virtual status_t drawRoundRect(float left, float top, float right, float bottom,
+ float rx, float ry, const SkPaint* paint);
+ virtual status_t drawCircle(float x, float y, float radius, const SkPaint* paint);
+ virtual status_t drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y,
+ CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint);
+ virtual status_t drawOval(float left, float top, float right, float bottom,
+ const SkPaint* paint);
+ virtual status_t drawArc(float left, float top, float right, float bottom,
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint);
+ virtual status_t drawPath(const SkPath* path, const SkPaint* paint);
+ virtual status_t drawLines(const float* points, int count, const SkPaint* paint);
+ virtual status_t drawPoints(const float* points, int count, const SkPaint* paint);
- const Vector<SkRegion*>& getRegions() const {
- return mRegions;
- }
+ // Text
+ virtual status_t drawText(const char* text, int bytesCount, int count, float x, float y,
+ const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
+ DrawOpMode drawOpMode = kDrawOpMode_Immediate);
+ virtual status_t drawTextOnPath(const char* text, int bytesCount, int count, const SkPath* path,
+ float hOffset, float vOffset, const SkPaint* paint);
+ virtual status_t drawPosText(const char* text, int bytesCount, int count,
+ const float* positions, const SkPaint* paint);
- const Vector<Layer*>& getLayers() const {
- return mLayers;
- }
+// ----------------------------------------------------------------------------
+// Canvas draw operations - special
+// ----------------------------------------------------------------------------
+ virtual status_t drawLayer(Layer* layer, float x, float y);
+ virtual status_t drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags);
- const Vector<SkMatrix*>& getMatrices() const {
- return mMatrices;
- }
+ // TODO: rename for consistency
+ virtual status_t callDrawGLFunction(Functor* functor, Rect& dirty);
- uint32_t getFunctorCount() const {
- return mFunctorCount;
+ void setHighContrastText(bool highContrastText) {
+ mHighContrastText = highContrastText;
}
-
private:
void insertRestoreToCount();
void insertTranslate();
LinearAllocator& alloc() { return mDisplayListData->allocator; }
- void addStateOp(StateOp* op);
- void addDrawOp(DrawOp* op);
- void addOpInternal(DisplayListOp* op) {
+
+ // Each method returns final index of op
+ int addStateOp(StateOp* op);
+ int addDrawOp(DrawOp* op);
+ int addOpInternal(DisplayListOp* op) {
insertRestoreToCount();
insertTranslate();
- mDisplayListData->displayListOps.add(op);
+ return mDisplayListData->displayListOps.add(op);
}
template<class T>
- inline T* refBuffer(const T* srcBuffer, int32_t count) {
- if (srcBuffer == NULL) return NULL;
+ inline const T* refBuffer(const T* srcBuffer, int32_t count) {
+ if (!srcBuffer) return NULL;
+
T* dstBuffer = (T*) mDisplayListData->allocator.alloc(count * sizeof(T));
memcpy(dstBuffer, srcBuffer, count * sizeof(T));
return dstBuffer;
@@ -217,153 +181,108 @@ private:
return (char*) refBuffer<uint8_t>((uint8_t*)text, byteLength);
}
- inline SkPath* refPath(SkPath* path) {
+ inline const SkPath* refPath(const SkPath* path) {
if (!path) return NULL;
- SkPath* pathCopy = mPathMap.valueFor(path);
+ const SkPath* pathCopy = mPathMap.valueFor(path);
if (pathCopy == NULL || pathCopy->getGenerationID() != path->getGenerationID()) {
- pathCopy = new SkPath(*path);
- pathCopy->setSourcePath(path);
+ SkPath* newPathCopy = new SkPath(*path);
+ newPathCopy->setSourcePath(path);
+
+ pathCopy = newPathCopy;
// replaceValueFor() performs an add if the entry doesn't exist
mPathMap.replaceValueFor(path, pathCopy);
- mPaths.add(pathCopy);
+ mDisplayListData->paths.add(pathCopy);
}
- if (mSourcePaths.indexOf(path) < 0) {
+ if (mDisplayListData->sourcePaths.indexOf(path) < 0) {
mCaches.resourceCache.incrementRefcount(path);
- mSourcePaths.add(path);
+ mDisplayListData->sourcePaths.add(path);
}
return pathCopy;
}
- inline SkPaint* refPaint(SkPaint* paint) {
- if (!paint) {
- return paint;
- }
+ inline const SkPaint* refPaint(const SkPaint* paint) {
+ if (!paint) return NULL;
- SkPaint* paintCopy = mPaintMap.valueFor(paint);
+ const SkPaint* paintCopy = mPaintMap.valueFor(paint);
if (paintCopy == NULL || paintCopy->getGenerationID() != paint->getGenerationID()) {
paintCopy = new SkPaint(*paint);
// replaceValueFor() performs an add if the entry doesn't exist
mPaintMap.replaceValueFor(paint, paintCopy);
- mPaints.add(paintCopy);
+ mDisplayListData->paints.add(paintCopy);
}
return paintCopy;
}
- inline SkRegion* refRegion(SkRegion* region) {
+ inline SkPaint* copyPaint(const SkPaint* paint) {
+ if (!paint) return NULL;
+ SkPaint* paintCopy = new SkPaint(*paint);
+ mDisplayListData->paints.add(paintCopy);
+
+ return paintCopy;
+ }
+
+ inline const SkRegion* refRegion(const SkRegion* region) {
if (!region) {
return region;
}
- SkRegion* regionCopy = mRegionMap.valueFor(region);
+ const SkRegion* regionCopy = mRegionMap.valueFor(region);
// TODO: Add generation ID to SkRegion
if (regionCopy == NULL) {
regionCopy = new SkRegion(*region);
// replaceValueFor() performs an add if the entry doesn't exist
mRegionMap.replaceValueFor(region, regionCopy);
- mRegions.add(regionCopy);
+ mDisplayListData->regions.add(regionCopy);
}
return regionCopy;
}
- inline SkMatrix* refMatrix(SkMatrix* matrix) {
- if (matrix) {
- // Copying the matrix is cheap and prevents against the user changing
- // the original matrix before the operation that uses it
- SkMatrix* copy = new SkMatrix(*matrix);
- mMatrices.add(copy);
- return copy;
- }
- return matrix;
- }
-
inline Layer* refLayer(Layer* layer) {
- mLayers.add(layer);
+ mDisplayListData->layers.add(layer);
mCaches.resourceCache.incrementRefcount(layer);
return layer;
}
- inline SkBitmap* refBitmap(SkBitmap* bitmap) {
+ inline const SkBitmap* refBitmap(const SkBitmap* bitmap) {
// Note that this assumes the bitmap is immutable. There are cases this won't handle
// correctly, such as creating the bitmap from scratch, drawing with it, changing its
// contents, and drawing again. The only fix would be to always copy it the first time,
// which doesn't seem worth the extra cycles for this unlikely case.
- mBitmapResources.add(bitmap);
+ mDisplayListData->bitmapResources.add(bitmap);
mCaches.resourceCache.incrementRefcount(bitmap);
return bitmap;
}
- inline SkBitmap* refBitmapData(SkBitmap* bitmap) {
- mOwnedBitmapResources.add(bitmap);
+ inline const SkBitmap* refBitmapData(const SkBitmap* bitmap) {
+ mDisplayListData->ownedBitmapResources.add(bitmap);
mCaches.resourceCache.incrementRefcount(bitmap);
return bitmap;
}
- inline SkiaShader* refShader(SkiaShader* shader) {
- if (!shader) return NULL;
-
- SkiaShader* shaderCopy = mShaderMap.valueFor(shader);
- // TODO: We also need to handle generation ID changes in compose shaders
- if (shaderCopy == NULL || shaderCopy->getGenerationId() != shader->getGenerationId()) {
- shaderCopy = shader->copy();
- // replaceValueFor() performs an add if the entry doesn't exist
- mShaderMap.replaceValueFor(shader, shaderCopy);
- mShaders.add(shaderCopy);
- mCaches.resourceCache.incrementRefcount(shaderCopy);
- }
- return shaderCopy;
- }
-
- inline SkiaColorFilter* refColorFilter(SkiaColorFilter* colorFilter) {
- mFilterResources.add(colorFilter);
- mCaches.resourceCache.incrementRefcount(colorFilter);
- return colorFilter;
- }
-
- inline Res_png_9patch* refPatch(Res_png_9patch* patch) {
- mPatchResources.add(patch);
+ inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) {
+ mDisplayListData->patchResources.add(patch);
mCaches.resourceCache.incrementRefcount(patch);
return patch;
}
- Vector<SkBitmap*> mBitmapResources;
- Vector<SkBitmap*> mOwnedBitmapResources;
- Vector<SkiaColorFilter*> mFilterResources;
- Vector<Res_png_9patch*> mPatchResources;
-
- Vector<SkPaint*> mPaints;
- DefaultKeyedVector<SkPaint*, SkPaint*> mPaintMap;
-
- Vector<SkPath*> mPaths;
- DefaultKeyedVector<SkPath*, SkPath*> mPathMap;
-
- SortedVector<SkPath*> mSourcePaths;
-
- Vector<SkRegion*> mRegions;
- DefaultKeyedVector<SkRegion*, SkRegion*> mRegionMap;
-
- Vector<SkiaShader*> mShaders;
- DefaultKeyedVector<SkiaShader*, SkiaShader*> mShaderMap;
-
- Vector<SkMatrix*> mMatrices;
-
- Vector<Layer*> mLayers;
-
- int mRestoreSaveCount;
+ DefaultKeyedVector<const SkPaint*, const SkPaint*> mPaintMap;
+ DefaultKeyedVector<const SkPath*, const SkPath*> mPathMap;
+ DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap;
Caches& mCaches;
- sp<DisplayListData> mDisplayListData;
+ DisplayListData* mDisplayListData;
float mTranslateX;
float mTranslateY;
bool mHasTranslate;
- bool mHasDrawOps;
+ bool mHighContrastText;
- uint32_t mFunctorCount;
+ int mRestoreSaveCount;
- friend class DisplayList;
+ friend class RenderNode;
}; // class DisplayListRenderer
diff --git a/libs/hwui/DrawProfiler.cpp b/libs/hwui/DrawProfiler.cpp
new file mode 100644
index 0000000..2409554
--- /dev/null
+++ b/libs/hwui/DrawProfiler.cpp
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2014 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 "DrawProfiler.h"
+
+#include <cutils/compiler.h>
+
+#include "OpenGLRenderer.h"
+#include "Properties.h"
+
+#define DEFAULT_MAX_FRAMES 128
+
+#define RETURN_IF_DISABLED() if (CC_LIKELY(mType == kNone)) return
+
+#define NANOS_TO_MILLIS_FLOAT(nanos) ((nanos) * 0.000001f)
+
+#define PROFILE_DRAW_WIDTH 3
+#define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2
+#define PROFILE_DRAW_DP_PER_MS 7
+
+// Number of floats we want to display from FrameTimingData
+// If this is changed make sure to update the indexes below
+#define NUM_ELEMENTS 4
+
+#define RECORD_INDEX 0
+#define PREPARE_INDEX 1
+#define PLAYBACK_INDEX 2
+#define SWAPBUFFERS_INDEX 3
+
+// Must be NUM_ELEMENTS in size
+static const SkColor ELEMENT_COLORS[] = { 0xcf3e66cc, 0xcf8f00ff, 0xcfdc3912, 0xcfe69800 };
+static const SkColor CURRENT_FRAME_COLOR = 0xcf5faa4d;
+static const SkColor THRESHOLD_COLOR = 0xff5faa4d;
+
+// We could get this from TimeLord and use the actual frame interval, but
+// this is good enough
+#define FRAME_THRESHOLD 16
+
+namespace android {
+namespace uirenderer {
+
+static int dpToPx(int dp, float density) {
+ return (int) (dp * density + 0.5f);
+}
+
+DrawProfiler::DrawProfiler()
+ : mType(kNone)
+ , mDensity(0)
+ , mData(NULL)
+ , mDataSize(0)
+ , mCurrentFrame(-1)
+ , mPreviousTime(0)
+ , mVerticalUnit(0)
+ , mHorizontalUnit(0)
+ , mThresholdStroke(0) {
+ setDensity(1);
+}
+
+DrawProfiler::~DrawProfiler() {
+ destroyData();
+}
+
+void DrawProfiler::setDensity(float density) {
+ if (CC_UNLIKELY(mDensity != density)) {
+ mDensity = density;
+ mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
+ mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density);
+ mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
+ }
+}
+
+void DrawProfiler::startFrame(nsecs_t recordDurationNanos) {
+ RETURN_IF_DISABLED();
+ mData[mCurrentFrame].record = NANOS_TO_MILLIS_FLOAT(recordDurationNanos);
+ mPreviousTime = systemTime(CLOCK_MONOTONIC);
+}
+
+void DrawProfiler::markPlaybackStart() {
+ RETURN_IF_DISABLED();
+ nsecs_t now = systemTime(CLOCK_MONOTONIC);
+ mData[mCurrentFrame].prepare = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime);
+ mPreviousTime = now;
+}
+
+void DrawProfiler::markPlaybackEnd() {
+ RETURN_IF_DISABLED();
+ nsecs_t now = systemTime(CLOCK_MONOTONIC);
+ mData[mCurrentFrame].playback = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime);
+ mPreviousTime = now;
+}
+
+void DrawProfiler::finishFrame() {
+ RETURN_IF_DISABLED();
+ nsecs_t now = systemTime(CLOCK_MONOTONIC);
+ mData[mCurrentFrame].swapBuffers = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime);
+ mPreviousTime = now;
+ mCurrentFrame = (mCurrentFrame + 1) % mDataSize;
+}
+
+void DrawProfiler::unionDirty(SkRect* dirty) {
+ RETURN_IF_DISABLED();
+ // Not worth worrying about minimizing the dirty region for debugging, so just
+ // dirty the entire viewport.
+ if (dirty) {
+ dirty->setEmpty();
+ }
+}
+
+void DrawProfiler::draw(OpenGLRenderer* canvas) {
+ if (CC_LIKELY(mType != kBars)) {
+ return;
+ }
+
+ prepareShapes(canvas->getViewportHeight());
+ drawGraph(canvas);
+ drawCurrentFrame(canvas);
+ drawThreshold(canvas);
+}
+
+void DrawProfiler::createData() {
+ if (mData) return;
+
+ mDataSize = property_get_int32(PROPERTY_PROFILE_MAXFRAMES, DEFAULT_MAX_FRAMES);
+ if (mDataSize <= 0) mDataSize = 1;
+ if (mDataSize > 4096) mDataSize = 4096; // Reasonable maximum
+ mData = (FrameTimingData*) calloc(mDataSize, sizeof(FrameTimingData));
+ mRects = new float*[NUM_ELEMENTS];
+ for (int i = 0; i < NUM_ELEMENTS; i++) {
+ // 4 floats per rect
+ mRects[i] = (float*) calloc(mDataSize, 4 * sizeof(float));
+ }
+ mCurrentFrame = 0;
+}
+
+void DrawProfiler::destroyData() {
+ delete mData;
+ mData = NULL;
+}
+
+void DrawProfiler::addRect(Rect& r, float data, float* shapeOutput) {
+ r.top = r.bottom - (data * mVerticalUnit);
+ shapeOutput[0] = r.left;
+ shapeOutput[1] = r.top;
+ shapeOutput[2] = r.right;
+ shapeOutput[3] = r.bottom;
+ r.bottom = r.top;
+}
+
+void DrawProfiler::prepareShapes(const int baseline) {
+ Rect r;
+ r.right = mHorizontalUnit;
+ for (int i = 0; i < mDataSize; i++) {
+ const int shapeIndex = i * 4;
+ r.bottom = baseline;
+ addRect(r, mData[i].record, mRects[RECORD_INDEX] + shapeIndex);
+ addRect(r, mData[i].prepare, mRects[PREPARE_INDEX] + shapeIndex);
+ addRect(r, mData[i].playback, mRects[PLAYBACK_INDEX] + shapeIndex);
+ addRect(r, mData[i].swapBuffers, mRects[SWAPBUFFERS_INDEX] + shapeIndex);
+ r.translate(mHorizontalUnit, 0);
+ }
+}
+
+void DrawProfiler::drawGraph(OpenGLRenderer* canvas) {
+ SkPaint paint;
+ for (int i = 0; i < NUM_ELEMENTS; i++) {
+ paint.setColor(ELEMENT_COLORS[i]);
+ canvas->drawRects(mRects[i], mDataSize * 4, &paint);
+ }
+}
+
+void DrawProfiler::drawCurrentFrame(OpenGLRenderer* canvas) {
+ // This draws a solid rect over the entirety of the current frame's shape
+ // To do so we use the bottom of mRects[0] and the top of mRects[NUM_ELEMENTS-1]
+ // which will therefore fully overlap the previously drawn rects
+ SkPaint paint;
+ paint.setColor(CURRENT_FRAME_COLOR);
+ const int i = mCurrentFrame * 4;
+ canvas->drawRect(mRects[0][i], mRects[NUM_ELEMENTS-1][i+1], mRects[0][i+2],
+ mRects[0][i+3], &paint);
+}
+
+void DrawProfiler::drawThreshold(OpenGLRenderer* canvas) {
+ SkPaint paint;
+ paint.setColor(THRESHOLD_COLOR);
+ paint.setStrokeWidth(mThresholdStroke);
+
+ float pts[4];
+ pts[0] = 0.0f;
+ pts[1] = pts[3] = canvas->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit);
+ pts[2] = canvas->getViewportWidth();
+ canvas->drawLines(pts, 4, &paint);
+}
+
+DrawProfiler::ProfileType DrawProfiler::loadRequestedProfileType() {
+ ProfileType type = kNone;
+ char buf[PROPERTY_VALUE_MAX] = {'\0',};
+ if (property_get(PROPERTY_PROFILE, buf, "") > 0) {
+ if (!strcmp(buf, PROPERTY_PROFILE_VISUALIZE_BARS)) {
+ type = kBars;
+ } else if (!strcmp(buf, "true")) {
+ type = kConsole;
+ }
+ }
+ return type;
+}
+
+bool DrawProfiler::loadSystemProperties() {
+ ProfileType newType = loadRequestedProfileType();
+ if (newType != mType) {
+ mType = newType;
+ if (mType == kNone) {
+ destroyData();
+ } else {
+ createData();
+ }
+ return true;
+ }
+ return false;
+}
+
+void DrawProfiler::dumpData(int fd) {
+ RETURN_IF_DISABLED();
+
+ // This method logs the last N frames (where N is <= mDataSize) since the
+ // last call to dumpData(). In other words if there's a dumpData(), draw frame,
+ // dumpData(), the last dumpData() should only log 1 frame.
+
+ const FrameTimingData emptyData = {0, 0, 0, 0};
+
+ FILE *file = fdopen(fd, "a");
+ fprintf(file, "\n\tDraw\tPrepare\tProcess\tExecute\n");
+
+ for (int frameOffset = 1; frameOffset <= mDataSize; frameOffset++) {
+ int i = (mCurrentFrame + frameOffset) % mDataSize;
+ if (!memcmp(mData + i, &emptyData, sizeof(FrameTimingData))) {
+ continue;
+ }
+ fprintf(file, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n",
+ mData[i].record, mData[i].prepare, mData[i].playback, mData[i].swapBuffers);
+ }
+ // reset the buffer
+ memset(mData, 0, sizeof(FrameTimingData) * mDataSize);
+ mCurrentFrame = 0;
+
+ fflush(file);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/DrawProfiler.h b/libs/hwui/DrawProfiler.h
new file mode 100644
index 0000000..7c06e5d
--- /dev/null
+++ b/libs/hwui/DrawProfiler.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef DRAWPROFILER_H
+#define DRAWPROFILER_H
+
+#include <utils/Timers.h>
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+class OpenGLRenderer;
+
+class DrawProfiler {
+public:
+ DrawProfiler();
+ ~DrawProfiler();
+
+ bool loadSystemProperties();
+ void setDensity(float density);
+
+ void startFrame(nsecs_t recordDurationNanos = 0);
+ void markPlaybackStart();
+ void markPlaybackEnd();
+ void finishFrame();
+
+ void unionDirty(SkRect* dirty);
+ void draw(OpenGLRenderer* canvas);
+
+ void dumpData(int fd);
+
+private:
+ enum ProfileType {
+ kNone,
+ kConsole,
+ kBars,
+ };
+
+ typedef struct {
+ float record;
+ float prepare;
+ float playback;
+ float swapBuffers;
+ } FrameTimingData;
+
+ void createData();
+ void destroyData();
+
+ void addRect(Rect& r, float data, float* shapeOutput);
+ void prepareShapes(const int baseline);
+ void drawGraph(OpenGLRenderer* canvas);
+ void drawCurrentFrame(OpenGLRenderer* canvas);
+ void drawThreshold(OpenGLRenderer* canvas);
+
+ ProfileType loadRequestedProfileType();
+
+ ProfileType mType;
+ float mDensity;
+
+ FrameTimingData* mData;
+ int mDataSize;
+
+ int mCurrentFrame;
+ nsecs_t mPreviousTime;
+
+ int mVerticalUnit;
+ int mHorizontalUnit;
+ int mThresholdStroke;
+
+ /*
+ * mRects represents an array of rect shapes, divided into NUM_ELEMENTS
+ * groups such that each group is drawn with the same paint.
+ * For example mRects[0] is the array of rect floats suitable for
+ * OpenGLRenderer:drawRects() that makes up all the FrameTimingData:record
+ * information.
+ */
+ float** mRects;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* DRAWPROFILER_H */
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 8d19ca2..3910381 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -72,19 +72,20 @@ status_t TextSetupFunctor::operator ()(int what, void* data) {
break;
}
}
- renderer->setupDrawColorFilter();
- renderer->setupDrawShader();
- renderer->setupDrawBlending(true, mode);
+ renderer->setupDrawColorFilter(paint->getColorFilter());
+ renderer->setupDrawShader(paint->getShader());
+ renderer->setupDrawBlending(paint);
renderer->setupDrawProgram();
- renderer->setupDrawModelView(x, y, x, y, pureTranslate, true);
+ renderer->setupDrawModelView(kModelViewMode_Translate, false,
+ 0.0f, 0.0f, 0.0f, 0.0f, pureTranslate);
// Calling setupDrawTexture with the name 0 will enable the
// uv attributes and increase the texture unit count
// texture binding will be performed by the font renderer as
// needed
renderer->setupDrawTexture(0);
renderer->setupDrawPureColorUniforms();
- renderer->setupDrawColorFilterUniforms();
- renderer->setupDrawShaderUniforms(pureTranslate);
+ renderer->setupDrawColorFilterUniforms(paint->getColorFilter());
+ renderer->setupDrawShaderUniforms(paint->getShader(), pureTranslate);
renderer->setupDrawTextGammaUniforms();
return NO_ERROR;
@@ -497,7 +498,7 @@ void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) {
}
checkTextureUpdate();
- caches.bindIndicesBuffer();
+ caches.bindQuadIndicesBuffer();
if (!mDrawn) {
// If returns true, a VBO was bound and we must
@@ -514,8 +515,8 @@ void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) {
texture->setLinearFiltering(mLinearFiltering, false);
TextureVertex* mesh = texture->mesh();
- caches.bindPositionVertexPointer(force, &mesh[0].position[0]);
- caches.bindTexCoordsVertexPointer(force, &mesh[0].texture[0]);
+ caches.bindPositionVertexPointer(force, &mesh[0].x);
+ caches.bindTexCoordsVertexPointer(force, &mesh[0].u);
force = false;
glDrawElements(GL_TRIANGLES, texture->meshElementCount(),
@@ -586,12 +587,12 @@ void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
}
}
-void FontRenderer::setFont(SkPaint* paint, const mat4& matrix) {
+void FontRenderer::setFont(const SkPaint* paint, const SkMatrix& matrix) {
mCurrentFont = Font::create(this, paint, matrix);
}
-FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius, const float* positions) {
+FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const char *text,
+ uint32_t startIndex, uint32_t len, int numGlyphs, float radius, const float* positions) {
checkInit();
DropShadow image;
@@ -612,8 +613,9 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch
Rect bounds;
mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions);
- uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
- uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
+ uint32_t intRadius = Blur::convertRadiusToInt(radius);
+ uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * intRadius;
+ uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * intRadius;
uint32_t maxSize = Caches::getInstance().maxTextureSize;
if (paddedWidth > maxSize || paddedHeight > maxSize) {
@@ -634,8 +636,8 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch
memset(dataBuffer, 0, size);
- int penX = radius - bounds.left;
- int penY = radius - bounds.bottom;
+ int penX = intRadius - bounds.left;
+ int penY = intRadius - bounds.bottom;
if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) {
// text has non-whitespace, so draw and blur to create the shadow
@@ -675,7 +677,8 @@ void FontRenderer::finishRender() {
issueDrawCommand();
}
-void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix) {
+void FontRenderer::precache(const SkPaint* paint, const char* text, int numGlyphs,
+ const SkMatrix& matrix) {
Font* font = Font::create(this, paint, matrix);
font->precache(paint, text, numGlyphs);
}
@@ -684,7 +687,7 @@ void FontRenderer::endPrecaching() {
checkTextureUpdate();
}
-bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
+bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
const float* positions, Rect* bounds, Functor* functor, bool forceFinish) {
if (!mCurrentFont) {
@@ -702,8 +705,8 @@ bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *t
return mDrawn;
}
-bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
- uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
+bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
+ uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
float hOffset, float vOffset, Rect* bounds, Functor* functor) {
if (!mCurrentFont) {
ALOGE("No font set");
@@ -725,9 +728,10 @@ void FontRenderer::removeFont(const Font* font) {
}
}
-void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) {
+void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, float radius) {
+ uint32_t intRadius = Blur::convertRadiusToInt(radius);
#ifdef ANDROID_ENABLE_RENDERSCRIPT
- if (width * height * radius >= RS_MIN_INPUT_CUTOFF) {
+ if (width * height * intRadius >= RS_MIN_INPUT_CUTOFF) {
uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height);
if (mRs == 0) {
@@ -766,12 +770,12 @@ void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int
}
#endif
- float *gaussian = new float[2 * radius + 1];
- Blur::generateGaussianWeights(gaussian, radius);
+ float *gaussian = new float[2 * intRadius + 1];
+ Blur::generateGaussianWeights(gaussian, intRadius);
uint8_t* scratch = new uint8_t[width * height];
- Blur::horizontal(gaussian, radius, *image, scratch, width, height);
- Blur::vertical(gaussian, radius, scratch, *image, width, height);
+ Blur::horizontal(gaussian, intRadius, *image, scratch, width, height);
+ Blur::vertical(gaussian, intRadius, scratch, *image, width, height);
delete[] gaussian;
delete[] scratch;
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index aa7e776..5c96c6b 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -44,8 +44,6 @@ namespace RSC {
}
#endif
-class Functor;
-
namespace android {
namespace uirenderer {
@@ -64,7 +62,7 @@ public:
};
TextSetupFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate,
- int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(),
+ int alpha, SkXfermode::Mode mode, const SkPaint* paint): Functor(),
renderer(renderer), x(x), y(y), pureTranslate(pureTranslate),
alpha(alpha), mode(mode), paint(paint) {
}
@@ -78,7 +76,7 @@ public:
bool pureTranslate;
int alpha;
SkXfermode::Mode mode;
- SkPaint* paint;
+ const SkPaint* paint;
};
///////////////////////////////////////////////////////////////////////////////
@@ -97,20 +95,20 @@ public:
mGammaTable = gammaTable;
}
- void setFont(SkPaint* paint, const mat4& matrix);
+ void setFont(const SkPaint* paint, const SkMatrix& matrix);
- void precache(SkPaint* paint, const char* text, int numGlyphs, const mat4& matrix);
+ void precache(const SkPaint* paint, const char* text, int numGlyphs, const SkMatrix& matrix);
void endPrecaching();
// bounds is an out parameter
- bool renderPosText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
- uint32_t len, int numGlyphs, int x, int y, const float* positions, Rect* bounds,
- Functor* functor, bool forceFinish = true);
+ bool renderPosText(const SkPaint* paint, const Rect* clip, const char *text,
+ uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, const float* positions,
+ Rect* bounds, Functor* functor, bool forceFinish = true);
// bounds is an out parameter
- bool renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
- uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds,
- Functor* functor);
+ bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text,
+ uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path,
+ float hOffset, float vOffset, Rect* bounds, Functor* functor);
struct DropShadow {
DropShadow() { };
@@ -130,8 +128,8 @@ public:
// After renderDropShadow returns, the called owns the memory in DropShadow.image
// and is responsible for releasing it when it's done with it
- DropShadow renderDropShadow(SkPaint* paint, const char *text, uint32_t startIndex,
- uint32_t len, int numGlyphs, uint32_t radius, const float* positions);
+ DropShadow renderDropShadow(const SkPaint* paint, const char *text, uint32_t startIndex,
+ uint32_t len, int numGlyphs, float radius, const float* positions);
void setTextureFiltering(bool linearFiltering) {
mLinearFiltering = linearFiltering;
@@ -220,7 +218,7 @@ private:
int32_t width, int32_t height);
// the input image handle may have its pointer replaced (to avoid copies)
- void blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius);
+ void blurImage(uint8_t** image, int32_t width, int32_t height, float radius);
};
}; // namespace uirenderer
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 0916942..ffd1e8c 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -115,7 +115,7 @@ void GradientCache::setMaxSize(uint32_t maxSize) {
// Callbacks
///////////////////////////////////////////////////////////////////////////////
-void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) {
+void GradientCache::operator()(GradientCacheEntry&, Texture*& texture) {
if (texture) {
const uint32_t size = texture->width * texture->height * bytesPerPixel();
mSize -= size;
@@ -185,7 +185,7 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient,
mCache.removeOldest();
}
- generateTexture(colors, positions, count, texture);
+ generateTexture(colors, positions, texture);
mSize += size;
mCache.put(gradient, texture);
@@ -238,8 +238,7 @@ void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float am
dst += 4 * sizeof(float);
}
-void GradientCache::generateTexture(uint32_t* colors, float* positions,
- int count, Texture* texture) {
+void GradientCache::generateTexture(uint32_t* colors, float* positions, Texture* texture) {
const uint32_t width = texture->width;
const GLsizei rowBytes = width * bytesPerPixel();
uint8_t pixels[rowBytes * texture->height];
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 43934d9..6a783b1 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -151,7 +151,7 @@ private:
Texture* addLinearGradient(GradientCacheEntry& gradient,
uint32_t* colors, float* positions, int count);
- void generateTexture(uint32_t* colors, float* positions, int count, Texture* texture);
+ void generateTexture(uint32_t* colors, float* positions, Texture* texture);
struct GradientInfo {
uint32_t width;
diff --git a/libs/hwui/Interpolator.cpp b/libs/hwui/Interpolator.cpp
new file mode 100644
index 0000000..fc0e8a0
--- /dev/null
+++ b/libs/hwui/Interpolator.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#define LOG_TAG "Interpolator"
+
+#include "Interpolator.h"
+
+#include <cmath>
+#include <cutils/log.h>
+
+#include "utils/MathUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+Interpolator* Interpolator::createDefaultInterpolator() {
+ return new AccelerateDecelerateInterpolator();
+}
+
+float AccelerateDecelerateInterpolator::interpolate(float input) {
+ return (float)(cosf((input + 1) * M_PI) / 2.0f) + 0.5f;
+}
+
+float AccelerateInterpolator::interpolate(float input) {
+ if (mFactor == 1.0f) {
+ return input * input;
+ } else {
+ return pow(input, mDoubleFactor);
+ }
+}
+
+float AnticipateInterpolator::interpolate(float t) {
+ return t * t * ((mTension + 1) * t - mTension);
+}
+
+static float a(float t, float s) {
+ return t * t * ((s + 1) * t - s);
+}
+
+static float o(float t, float s) {
+ return t * t * ((s + 1) * t + s);
+}
+
+float AnticipateOvershootInterpolator::interpolate(float t) {
+ if (t < 0.5f) return 0.5f * a(t * 2.0f, mTension);
+ else return 0.5f * (o(t * 2.0f - 2.0f, mTension) + 2.0f);
+}
+
+static float bounce(float t) {
+ return t * t * 8.0f;
+}
+
+float BounceInterpolator::interpolate(float t) {
+ t *= 1.1226f;
+ if (t < 0.3535f) return bounce(t);
+ else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
+ else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
+ else return bounce(t - 1.0435f) + 0.95f;
+}
+
+float CycleInterpolator::interpolate(float input) {
+ return sinf(2 * mCycles * M_PI * input);
+}
+
+float DecelerateInterpolator::interpolate(float input) {
+ float result;
+ if (mFactor == 1.0f) {
+ result = 1.0f - (1.0f - input) * (1.0f - input);
+ } else {
+ result = 1.0f - pow((1.0f - input), 2 * mFactor);
+ }
+ return result;
+}
+
+float OvershootInterpolator::interpolate(float t) {
+ t -= 1.0f;
+ return t * t * ((mTension + 1) * t + mTension) + 1.0f;
+}
+
+LUTInterpolator::LUTInterpolator(float* values, size_t size) {
+ mValues = values;
+ mSize = size;
+}
+
+LUTInterpolator::~LUTInterpolator() {
+ delete mValues;
+ mValues = 0;
+}
+
+float LUTInterpolator::interpolate(float input) {
+ float lutpos = input * mSize;
+ if (lutpos >= (mSize - 1)) {
+ return mValues[mSize - 1];
+ }
+
+ float ipart, weight;
+ weight = modff(lutpos, &ipart);
+
+ int i1 = (int) ipart;
+ int i2 = MathUtils::min(i1 + 1, mSize - 1);
+
+ LOG_ALWAYS_FATAL_IF(i1 < 0 || i2 < 0, "negatives in interpolation!"
+ " i1=%d, i2=%d, input=%f, lutpos=%f, size=%zu, values=%p, ipart=%f, weight=%f",
+ i1, i2, input, lutpos, mSize, mValues, ipart, weight);
+
+ float v1 = mValues[i1];
+ float v2 = mValues[i2];
+
+ return MathUtils::lerp(v1, v2, weight);
+}
+
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/Interpolator.h b/libs/hwui/Interpolator.h
new file mode 100644
index 0000000..dfa0a85
--- /dev/null
+++ b/libs/hwui/Interpolator.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INTERPOLATOR_H
+#define INTERPOLATOR_H
+
+#include <stddef.h>
+
+#include <cutils/compiler.h>
+
+namespace android {
+namespace uirenderer {
+
+class Interpolator {
+public:
+ virtual ~Interpolator() {}
+
+ virtual float interpolate(float input) = 0;
+
+ static Interpolator* createDefaultInterpolator();
+
+protected:
+ Interpolator() {}
+};
+
+class ANDROID_API AccelerateDecelerateInterpolator : public Interpolator {
+public:
+ virtual float interpolate(float input);
+};
+
+class ANDROID_API AccelerateInterpolator : public Interpolator {
+public:
+ AccelerateInterpolator(float factor) : mFactor(factor), mDoubleFactor(factor*2) {}
+ virtual float interpolate(float input);
+private:
+ const float mFactor;
+ const float mDoubleFactor;
+};
+
+class ANDROID_API AnticipateInterpolator : public Interpolator {
+public:
+ AnticipateInterpolator(float tension) : mTension(tension) {}
+ virtual float interpolate(float input);
+private:
+ const float mTension;
+};
+
+class ANDROID_API AnticipateOvershootInterpolator : public Interpolator {
+public:
+ AnticipateOvershootInterpolator(float tension) : mTension(tension) {}
+ virtual float interpolate(float input);
+private:
+ const float mTension;
+};
+
+class ANDROID_API BounceInterpolator : public Interpolator {
+public:
+ virtual float interpolate(float input);
+};
+
+class ANDROID_API CycleInterpolator : public Interpolator {
+public:
+ CycleInterpolator(float cycles) : mCycles(cycles) {}
+ virtual float interpolate(float input);
+private:
+ const float mCycles;
+};
+
+class ANDROID_API DecelerateInterpolator : public Interpolator {
+public:
+ DecelerateInterpolator(float factor) : mFactor(factor) {}
+ virtual float interpolate(float input);
+private:
+ const float mFactor;
+};
+
+class ANDROID_API LinearInterpolator : public Interpolator {
+public:
+ virtual float interpolate(float input) { return input; }
+};
+
+class ANDROID_API OvershootInterpolator : public Interpolator {
+public:
+ OvershootInterpolator(float tension) : mTension(tension) {}
+ virtual float interpolate(float input);
+private:
+ const float mTension;
+};
+
+class ANDROID_API LUTInterpolator : public Interpolator {
+public:
+ LUTInterpolator(float* values, size_t size);
+ ~LUTInterpolator();
+
+ virtual float interpolate(float input);
+
+private:
+ float* mValues;
+ size_t mSize;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* INTERPOLATOR_H */
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index 987bf03..8639ae1 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -18,18 +18,21 @@
#include <utils/Log.h>
-#include "DisplayList.h"
+#include "Caches.h"
#include "DeferredDisplayList.h"
+#include "RenderState.h"
#include "Layer.h"
#include "LayerRenderer.h"
#include "OpenGLRenderer.h"
-#include "Caches.h"
+#include "RenderNode.h"
namespace android {
namespace uirenderer {
-Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight):
- caches(Caches::getInstance()), texture(caches) {
+Layer::Layer(RenderState& renderState, const uint32_t layerWidth, const uint32_t layerHeight)
+ : caches(Caches::getInstance())
+ , renderState(renderState)
+ , texture(caches) {
mesh = NULL;
meshElementCount = 0;
cacheable = true;
@@ -41,22 +44,26 @@ Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight):
colorFilter = NULL;
deferredUpdateScheduled = false;
renderer = NULL;
- displayList = NULL;
+ renderNode = NULL;
fbo = 0;
stencil = NULL;
debugDrawUpdate = false;
hasDrawnSinceUpdate = false;
+ forceFilter = false;
deferredList = NULL;
+ convexMask = NULL;
caches.resourceCache.incrementRefcount(this);
+ rendererLightPosDirty = true;
}
Layer::~Layer() {
- if (colorFilter) caches.resourceCache.decrementRefcount(colorFilter);
+ SkSafeUnref(colorFilter);
removeFbo();
deleteTexture();
delete[] mesh;
delete deferredList;
+ delete renderer;
}
uint32_t Layer::computeIdealWidth(uint32_t layerWidth) {
@@ -67,6 +74,24 @@ uint32_t Layer::computeIdealHeight(uint32_t layerHeight) {
return uint32_t(ceilf(layerHeight / float(LAYER_SIZE)) * LAYER_SIZE);
}
+void Layer::requireRenderer() {
+ if (!renderer) {
+ renderer = new LayerRenderer(renderState, this);
+ renderer->initProperties();
+ }
+}
+
+void Layer::updateLightPosFromRenderer(const OpenGLRenderer& rootRenderer) {
+ if (renderer && rendererLightPosDirty) {
+ // re-init renderer's light position, based upon last cached location in window
+ Vector3 lightPos = rootRenderer.getLightCenter();
+ cachedInvTransformInWindow.mapPoint3d(lightPos);
+ renderer->initLight(lightPos, rootRenderer.getLightRadius(),
+ rootRenderer.getAmbientShadowAlpha(), rootRenderer.getSpotShadowAlpha());
+ rendererLightPosDirty = false;
+ }
+}
+
bool Layer::resize(const uint32_t width, const uint32_t height) {
uint32_t desiredWidth = computeIdealWidth(width);
uint32_t desiredHeight = computeIdealWidth(height);
@@ -75,6 +100,8 @@ bool Layer::resize(const uint32_t width, const uint32_t height) {
return true;
}
+ ATRACE_NAME("resizeLayer");
+
const uint32_t maxTextureSize = caches.maxTextureSize;
if (desiredWidth > maxTextureSize || desiredHeight > maxTextureSize) {
ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)",
@@ -113,36 +140,38 @@ bool Layer::resize(const uint32_t width, const uint32_t height) {
void Layer::removeFbo(bool flush) {
if (stencil) {
- GLuint previousFbo;
- glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
- if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLuint previousFbo = renderState.getFramebuffer();
+ renderState.bindFramebuffer(fbo);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
- if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+ renderState.bindFramebuffer(previousFbo);
caches.renderBufferCache.put(stencil);
stencil = NULL;
}
if (fbo) {
- if (flush) LayerRenderer::flushLayer(this);
+ if (flush) LayerRenderer::flushLayer(renderState, this);
// If put fails the cache will delete the FBO
caches.fboCache.put(fbo);
fbo = 0;
}
}
-void Layer::setPaint(SkPaint* paint) {
+void Layer::updateDeferred(RenderNode* renderNode, int left, int top, int right, int bottom) {
+ requireRenderer();
+ this->renderNode = renderNode;
+ const Rect r(left, top, right, bottom);
+ dirtyRect.unionWith(r);
+ deferredUpdateScheduled = true;
+}
+
+void Layer::setPaint(const SkPaint* paint) {
OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode);
+ setColorFilter((paint) ? paint->getColorFilter() : NULL);
}
-void Layer::setColorFilter(SkiaColorFilter* filter) {
- if (colorFilter) {
- caches.resourceCache.decrementRefcount(colorFilter);
- }
- colorFilter = filter;
- if (colorFilter) {
- caches.resourceCache.incrementRefcount(colorFilter);
- }
+void Layer::setColorFilter(SkColorFilter* filter) {
+ SkRefCnt_SafeAssign(colorFilter, filter);
}
void Layer::bindTexture() const {
@@ -186,7 +215,8 @@ void Layer::allocateTexture() {
}
}
-void Layer::defer() {
+void Layer::defer(const OpenGLRenderer& rootRenderer) {
+ updateLightPosFromRenderer(rootRenderer);
const float width = layer.getWidth();
const float height = layer.getHeight();
@@ -195,26 +225,24 @@ void Layer::defer() {
dirtyRect.set(0, 0, width, height);
}
- if (deferredList) {
- deferredList->reset(dirtyRect);
- } else {
- deferredList = new DeferredDisplayList(dirtyRect);
- }
+ delete deferredList;
+ deferredList = new DeferredDisplayList(dirtyRect);
+
DeferStateStruct deferredState(*deferredList, *renderer,
- DisplayList::kReplayFlag_ClipChildren);
+ RenderNode::kReplayFlag_ClipChildren);
- renderer->initViewport(width, height);
+ renderer->setViewport(width, height);
renderer->setupFrameState(dirtyRect.left, dirtyRect.top,
dirtyRect.right, dirtyRect.bottom, !isBlend());
- displayList->defer(deferredState, 0);
+ renderNode->computeOrdering();
+ renderNode->defer(deferredState, 0);
deferredUpdateScheduled = false;
}
void Layer::cancelDefer() {
- renderer = NULL;
- displayList = NULL;
+ renderNode = NULL;
deferredUpdateScheduled = false;
if (deferredList) {
delete deferredList;
@@ -232,27 +260,26 @@ void Layer::flush() {
deferredList->flush(*renderer, dirtyRect);
renderer->finish();
- renderer = NULL;
dirtyRect.setEmpty();
- displayList = NULL;
+ renderNode = NULL;
}
}
-void Layer::render() {
+void Layer::render(const OpenGLRenderer& rootRenderer) {
+ updateLightPosFromRenderer(rootRenderer);
renderer->setViewport(layer.getWidth(), layer.getHeight());
renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom,
!isBlend());
- renderer->drawDisplayList(displayList, dirtyRect, DisplayList::kReplayFlag_ClipChildren);
+ renderer->drawRenderNode(renderNode.get(), dirtyRect, RenderNode::kReplayFlag_ClipChildren);
renderer->finish();
- renderer = NULL;
dirtyRect.setEmpty();
deferredUpdateScheduled = false;
- displayList = NULL;
+ renderNode = NULL;
}
}; // namespace uirenderer
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index b70042f..38c29c7 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -17,7 +17,9 @@
#ifndef ANDROID_HWUI_LAYER_H
#define ANDROID_HWUI_LAYER_H
+#include <cutils/compiler.h>
#include <sys/types.h>
+#include <utils/StrongPointer.h>
#include <GLES2/gl2.h>
@@ -26,9 +28,9 @@
#include <SkPaint.h>
#include <SkXfermode.h>
+#include "Matrix.h"
#include "Rect.h"
#include "RenderBuffer.h"
-#include "SkiaColorFilter.h"
#include "Texture.h"
#include "Vertex.h"
@@ -41,16 +43,18 @@ namespace uirenderer {
// Forward declarations
class Caches;
+class RenderState;
class OpenGLRenderer;
-class DisplayList;
+class RenderNode;
class DeferredDisplayList;
class DeferStateStruct;
/**
* A layer has dimensions and is backed by an OpenGL texture or FBO.
*/
-struct Layer {
- Layer(const uint32_t layerWidth, const uint32_t layerHeight);
+class Layer {
+public:
+ Layer(RenderState& renderState, const uint32_t layerWidth, const uint32_t layerHeight);
~Layer();
static uint32_t computeIdealWidth(uint32_t layerWidth);
@@ -82,15 +86,13 @@ struct Layer {
regionRect.translate(layer.left, layer.top);
}
- void updateDeferred(OpenGLRenderer* renderer, DisplayList* displayList,
- int left, int top, int right, int bottom) {
- this->renderer = renderer;
- this->displayList = displayList;
- const Rect r(left, top, right, bottom);
- dirtyRect.unionWith(r);
- deferredUpdateScheduled = true;
+ void setWindowTransform(Matrix4& windowTransform) {
+ cachedInvTransformInWindow.loadInverse(windowTransform);
+ rendererLightPosDirty = true;
}
+ void updateDeferred(RenderNode* renderNode, int left, int top, int right, int bottom);
+
inline uint32_t getWidth() const {
return texture.width;
}
@@ -115,7 +117,7 @@ struct Layer {
texture.height = height;
}
- ANDROID_API void setPaint(SkPaint* paint);
+ ANDROID_API void setPaint(const SkPaint* paint);
inline void setBlend(bool blend) {
texture.blend = blend;
@@ -125,6 +127,14 @@ struct Layer {
return texture.blend;
}
+ inline void setForceFilter(bool forceFilter) {
+ this->forceFilter = forceFilter;
+ }
+
+ inline bool getForceFilter() const {
+ return forceFilter;
+ }
+
inline void setAlpha(int alpha) {
this->alpha = alpha;
}
@@ -216,11 +226,19 @@ struct Layer {
this->textureLayer = textureLayer;
}
- inline SkiaColorFilter* getColorFilter() const {
+ inline SkColorFilter* getColorFilter() const {
return colorFilter;
}
- ANDROID_API void setColorFilter(SkiaColorFilter* filter);
+ ANDROID_API void setColorFilter(SkColorFilter* filter);
+
+ inline void setConvexMask(const SkPath* convexMask) {
+ this->convexMask = convexMask;
+ }
+
+ inline const SkPath* getConvexMask() {
+ return convexMask;
+ }
void bindStencilRenderBuffer() const;
@@ -244,10 +262,10 @@ struct Layer {
return transform;
}
- void defer();
+ void defer(const OpenGLRenderer& rootRenderer);
void cancelDefer();
void flush();
- void render();
+ void render(const OpenGLRenderer& rootRenderer);
/**
* Bounds of the layer.
@@ -284,14 +302,19 @@ struct Layer {
*/
bool deferredUpdateScheduled;
OpenGLRenderer* renderer;
- DisplayList* displayList;
+ sp<RenderNode> renderNode;
Rect dirtyRect;
bool debugDrawUpdate;
bool hasDrawnSinceUpdate;
private:
+ void requireRenderer();
+ void updateLightPosFromRenderer(const OpenGLRenderer& rootRenderer);
+
Caches& caches;
+ RenderState& renderState;
+
/**
* Name of the FBO used to render the layer. If the name is 0
* this layer is not backed by an FBO, but a simple texture.
@@ -338,12 +361,18 @@ private:
/**
* Color filter used to draw this layer. Optional.
*/
- SkiaColorFilter* colorFilter;
+ SkColorFilter* colorFilter;
+
+ /**
+ * Indicates raster data backing the layer is scaled, requiring filtration.
+ */
+ bool forceFilter;
/**
* Opacity of the layer.
*/
int alpha;
+
/**
* Blending mode of the layer.
*/
@@ -360,11 +389,24 @@ private:
mat4 transform;
/**
+ * Cached transform of layer in window, updated only on creation / resize
+ */
+ mat4 cachedInvTransformInWindow;
+ bool rendererLightPosDirty;
+
+ /**
* Used to defer display lists when the layer is updated with a
* display list.
*/
DeferredDisplayList* deferredList;
+ /**
+ * This convex path should be used to mask the layer's draw to the screen.
+ *
+ * Data not owned/managed by layer object.
+ */
+ const SkPath* convexMask;
+
}; // struct Layer
}; // namespace uirenderer
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index 6be0146..13869aa 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -91,7 +91,7 @@ void LayerCache::clear() {
mCache.clear();
}
-Layer* LayerCache::get(const uint32_t width, const uint32_t height) {
+Layer* LayerCache::get(RenderState& renderState, const uint32_t width, const uint32_t height) {
Layer* layer = NULL;
LayerEntry entry(width, height);
@@ -108,7 +108,7 @@ Layer* LayerCache::get(const uint32_t width, const uint32_t height) {
} else {
LAYER_LOGD("Creating new layer %dx%d", entry.mWidth, entry.mHeight);
- layer = new Layer(entry.mWidth, entry.mHeight);
+ layer = new Layer(renderState, entry.mWidth, entry.mHeight);
layer->setBlend(true);
layer->setEmpty(true);
layer->setFbo(0);
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index 221bfe0..1b0fc2c 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -24,6 +24,8 @@
namespace android {
namespace uirenderer {
+class RenderState;
+
///////////////////////////////////////////////////////////////////////////////
// Defines
///////////////////////////////////////////////////////////////////////////////
@@ -55,7 +57,7 @@ public:
* @param width The desired width of the layer
* @param height The desired height of the layer
*/
- Layer* get(const uint32_t width, const uint32_t height);
+ Layer* get(RenderState& renderState, const uint32_t width, const uint32_t height);
/**
* Adds the layer to the cache. The layer will not be added if there is
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 006d145..e3b0daf 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -15,11 +15,13 @@
*/
#define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
#include <ui/Rect.h>
#include <private/hwui/DrawGlInfo.h>
+#include "RenderState.h"
#include "LayerCache.h"
#include "LayerRenderer.h"
#include "Matrix.h"
@@ -33,21 +35,19 @@ namespace uirenderer {
// Rendering
///////////////////////////////////////////////////////////////////////////////
-LayerRenderer::LayerRenderer(Layer* layer): mLayer(layer) {
+LayerRenderer::LayerRenderer(RenderState& renderState, Layer* layer)
+ : OpenGLRenderer(renderState)
+ , mLayer(layer) {
}
LayerRenderer::~LayerRenderer() {
}
-void LayerRenderer::setViewport(int width, int height) {
- initViewport(width, height);
-}
-
status_t LayerRenderer::prepareDirty(float left, float top, float right, float bottom,
bool opaque) {
LAYER_RENDERER_LOGD("Rendering into layer, fbo = %d", mLayer->getFbo());
- glBindFramebuffer(GL_FRAMEBUFFER, mLayer->getFbo());
+ renderState().bindFramebuffer(mLayer->getFbo());
const float width = mLayer->layer.getWidth();
const float height = mLayer->layer.getHeight();
@@ -92,7 +92,7 @@ void LayerRenderer::finish() {
// who will invoke OpenGLRenderer::resume()
}
-GLint LayerRenderer::getTargetFbo() const {
+GLuint LayerRenderer::getTargetFbo() const {
return mLayer->getFbo();
}
@@ -117,7 +117,7 @@ void LayerRenderer::ensureStencilBuffer() {
///////////////////////////////////////////////////////////////////////////////
Region* LayerRenderer::getRegion() const {
- if (getSnapshot()->flags & Snapshot::kFlagFboTarget) {
+ if (currentSnapshot()->flags & Snapshot::kFlagFboTarget) {
return OpenGLRenderer::getRegion();
}
return &mLayer->region;
@@ -184,7 +184,8 @@ void LayerRenderer::generateMesh() {
// Layers management
///////////////////////////////////////////////////////////////////////////////
-Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque) {
+Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width, uint32_t height) {
+ ATRACE_CALL();
LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height);
Caches& caches = Caches::getInstance();
@@ -195,7 +196,7 @@ Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque
}
caches.activeTexture(0);
- Layer* layer = caches.layerCache.get(width, height);
+ Layer* layer = caches.layerCache.get(renderState, width, height);
if (!layer) {
ALOGW("Could not obtain a layer");
return NULL;
@@ -221,15 +222,13 @@ Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque
layer->texCoords.set(0.0f, height / float(layer->getHeight()),
width / float(layer->getWidth()), 0.0f);
layer->setAlpha(255, SkXfermode::kSrcOver_Mode);
- layer->setBlend(!isOpaque);
layer->setColorFilter(NULL);
layer->setDirty(true);
layer->region.clear();
- GLuint previousFbo;
- glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
+ GLuint previousFbo = renderState.getFramebuffer();
- glBindFramebuffer(GL_FRAMEBUFFER, layer->getFbo());
+ renderState.bindFramebuffer(layer->getFbo());
layer->bindTexture();
// Initialize the texture if needed
@@ -240,7 +239,7 @@ Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque
// This should only happen if we run out of memory
if (glGetError() != GL_NO_ERROR) {
ALOGE("Could not allocate texture for layer (fbo=%d %dx%d)", fbo, width, height);
- glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+ renderState.bindFramebuffer(previousFbo);
caches.resourceCache.decrementRefcount(layer);
return NULL;
}
@@ -249,7 +248,7 @@ Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
layer->getTexture(), 0);
- glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+ renderState.bindFramebuffer(previousFbo);
return layer;
}
@@ -270,13 +269,12 @@ bool LayerRenderer::resizeLayer(Layer* layer, uint32_t width, uint32_t height) {
return true;
}
-Layer* LayerRenderer::createTextureLayer(bool isOpaque) {
+Layer* LayerRenderer::createTextureLayer(RenderState& renderState) {
LAYER_RENDERER_LOGD("Creating new texture layer");
- Layer* layer = new Layer(0, 0);
+ Layer* layer = new Layer(renderState, 0, 0);
layer->setCacheable(false);
layer->setTextureLayer(true);
- layer->setBlend(!isOpaque);
layer->setEmpty(true);
layer->setFbo(0);
layer->setAlpha(255, SkXfermode::kSrcOver_Mode);
@@ -292,14 +290,15 @@ Layer* LayerRenderer::createTextureLayer(bool isOpaque) {
}
void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
- bool isOpaque, GLenum renderTarget, float* transform) {
+ bool isOpaque, bool forceFilter, GLenum renderTarget, float* textureTransform) {
if (layer) {
layer->setBlend(!isOpaque);
+ layer->setForceFilter(forceFilter);
layer->setSize(width, height);
layer->layer.set(0.0f, 0.0f, width, height);
layer->region.set(width, height);
layer->regionRect.set(0.0f, 0.0f, width, height);
- layer->getTexTransform().load(transform);
+ layer->getTexTransform().load(textureTransform);
if (renderTarget != layer->getRenderTarget()) {
layer->setRenderTarget(renderTarget);
@@ -312,6 +311,7 @@ void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t he
void LayerRenderer::destroyLayer(Layer* layer) {
if (layer) {
+ ATRACE_CALL();
LAYER_RENDERER_LOGD("Recycling layer, %dx%d fbo = %d",
layer->getWidth(), layer->getHeight(), layer->getFbo());
@@ -337,7 +337,7 @@ void LayerRenderer::destroyLayerDeferred(Layer* layer) {
}
}
-void LayerRenderer::flushLayer(Layer* layer) {
+void LayerRenderer::flushLayer(RenderState& renderState, Layer* layer) {
#ifdef GL_EXT_discard_framebuffer
if (!layer) return;
@@ -346,20 +346,23 @@ void LayerRenderer::flushLayer(Layer* layer) {
// If possible, discard any enqueud operations on deferred
// rendering architectures
if (Extensions::getInstance().hasDiscardFramebuffer()) {
- GLuint previousFbo;
- glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
- if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ GLuint previousFbo = renderState.getFramebuffer();
+ if (fbo != previousFbo) {
+ renderState.bindFramebuffer(fbo);
+ }
const GLenum attachments[] = { GL_COLOR_ATTACHMENT0 };
glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, attachments);
- if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+ if (fbo != previousFbo) {
+ renderState.bindFramebuffer(previousFbo);
+ }
}
}
#endif
}
-bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) {
+bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* bitmap) {
Caches& caches = Caches::getInstance();
if (layer && bitmap->width() <= caches.maxTextureSize &&
bitmap->height() <= caches.maxTextureSize) {
@@ -374,7 +377,8 @@ bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) {
GLuint texture;
GLuint previousFbo;
- GLuint previousViewport[4];
+ GLsizei previousViewportWidth;
+ GLsizei previousViewportHeight;
GLenum format;
GLenum type;
@@ -382,20 +386,20 @@ bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) {
GLenum error = GL_NO_ERROR;
bool status = false;
- switch (bitmap->config()) {
- case SkBitmap::kA8_Config:
+ switch (bitmap->colorType()) {
+ case kAlpha_8_SkColorType:
format = GL_ALPHA;
type = GL_UNSIGNED_BYTE;
break;
- case SkBitmap::kRGB_565_Config:
+ case kRGB_565_SkColorType:
format = GL_RGB;
type = GL_UNSIGNED_SHORT_5_6_5;
break;
- case SkBitmap::kARGB_4444_Config:
+ case kARGB_4444_SkColorType:
format = GL_RGBA;
type = GL_UNSIGNED_SHORT_4_4_4_4;
break;
- case SkBitmap::kARGB_8888_Config:
+ case kN32_SkColorType:
default:
format = GL_RGBA;
type = GL_UNSIGNED_BYTE;
@@ -409,9 +413,9 @@ bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) {
layer->setAlpha(255, SkXfermode::kSrc_Mode);
layer->setFbo(fbo);
- glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
- glGetIntegerv(GL_VIEWPORT, (GLint*) &previousViewport);
- glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ previousFbo = renderState.getFramebuffer();
+ renderState.getViewport(&previousViewportWidth, &previousViewportHeight);
+ renderState.bindFramebuffer(fbo);
glGenTextures(1, &texture);
if ((error = glGetError()) != GL_NO_ERROR) goto error;
@@ -436,7 +440,7 @@ bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) {
if ((error = glGetError()) != GL_NO_ERROR) goto error;
{
- LayerRenderer renderer(layer);
+ LayerRenderer renderer(renderState, layer);
renderer.setViewport(bitmap->width(), bitmap->height());
renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f,
bitmap->width(), bitmap->height(), !layer->isBlend());
@@ -476,13 +480,12 @@ error:
}
#endif
- glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+ renderState.bindFramebuffer(previousFbo);
layer->setAlpha(alpha, mode);
layer->setFbo(previousLayerFbo);
caches.deleteTexture(texture);
caches.fboCache.put(fbo);
- glViewport(previousViewport[0], previousViewport[1],
- previousViewport[2], previousViewport[3]);
+ renderState.setViewport(previousViewportWidth, previousViewportHeight);
return status;
}
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index 5f86731..bf7828c 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -27,6 +27,8 @@
namespace android {
namespace uirenderer {
+class RenderState;
+
///////////////////////////////////////////////////////////////////////////////
// Defines
///////////////////////////////////////////////////////////////////////////////
@@ -44,30 +46,30 @@ namespace uirenderer {
class LayerRenderer: public OpenGLRenderer {
public:
- ANDROID_API LayerRenderer(Layer* layer);
+ LayerRenderer(RenderState& renderState, Layer* layer);
virtual ~LayerRenderer();
- virtual void setViewport(int width, int height);
+ virtual void onViewportInitialized() { /* do nothing */ }
virtual status_t prepareDirty(float left, float top, float right, float bottom, bool opaque);
virtual status_t clear(float left, float top, float right, float bottom, bool opaque);
virtual void finish();
- ANDROID_API static Layer* createTextureLayer(bool isOpaque);
- ANDROID_API static Layer* createLayer(uint32_t width, uint32_t height, bool isOpaque = false);
- ANDROID_API static bool resizeLayer(Layer* layer, uint32_t width, uint32_t height);
- ANDROID_API static void updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
- bool isOpaque, GLenum renderTarget, float* transform);
- ANDROID_API static void destroyLayer(Layer* layer);
+ static Layer* createTextureLayer(RenderState& renderState);
+ static Layer* createRenderLayer(RenderState& renderState, uint32_t width, uint32_t height);
+ static bool resizeLayer(Layer* layer, uint32_t width, uint32_t height);
+ static void updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
+ bool isOpaque, bool forceFilter, GLenum renderTarget, float* textureTransform);
+ static void destroyLayer(Layer* layer);
ANDROID_API static void destroyLayerDeferred(Layer* layer);
- ANDROID_API static bool copyLayer(Layer* layer, SkBitmap* bitmap);
+ static bool copyLayer(RenderState& renderState, Layer* layer, SkBitmap* bitmap);
- static void flushLayer(Layer* layer);
+ static void flushLayer(RenderState& renderState, Layer* layer);
protected:
virtual void ensureStencilBuffer();
virtual bool hasLayer() const;
virtual Region* getRegion() const;
- virtual GLint getTargetFbo() const;
+ virtual GLuint getTargetFbo() const;
virtual bool suppressErrorChecks() const;
private:
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index ba22071..9f2014f 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -89,8 +89,9 @@ uint8_t Matrix4::getType() const {
float m01 = data[kSkewX];
float m10 = data[kSkewY];
float m11 = data[kScaleY];
+ float m32 = data[kTranslateZ];
- if (m01 != 0.0f || m10 != 0.0f) {
+ if (m01 != 0.0f || m10 != 0.0f || m32 != 0.0f) {
mType |= kTypeAffine;
}
@@ -131,11 +132,13 @@ bool Matrix4::changesBounds() const {
}
bool Matrix4::isPureTranslate() const {
- return getGeometryType() <= kTypeTranslate;
+ // NOTE: temporary hack to workaround ignoreTransform behavior with Z values
+ // TODO: separate this into isPure2dTranslate vs isPure3dTranslate
+ return getGeometryType() <= kTypeTranslate && (data[kTranslateZ] == 0.0f);
}
bool Matrix4::isSimple() const {
- return getGeometryType() <= (kTypeScale | kTypeTranslate);
+ return getGeometryType() <= (kTypeScale | kTypeTranslate) && (data[kTranslateZ] == 0.0f);
}
bool Matrix4::isIdentity() const {
@@ -382,6 +385,19 @@ void Matrix4::loadOrtho(float left, float right, float bottom, float top, float
mType = kTypeTranslate | kTypeScale | kTypeRectToRect;
}
+float Matrix4::mapZ(const Vector3& orig) const {
+ // duplicates logic for mapPoint3d's z coordinate
+ return orig.x * data[2] + orig.y * data[6] + orig.z * data[kScaleZ] + data[kTranslateZ];
+}
+
+void Matrix4::mapPoint3d(Vector3& vec) const {
+ //TODO: optimize simple case
+ const Vector3 orig(vec);
+ vec.x = orig.x * data[kScaleX] + orig.y * data[kSkewX] + orig.z * data[8] + data[kTranslateX];
+ vec.y = orig.x * data[kSkewY] + orig.y * data[kScaleY] + orig.z * data[9] + data[kTranslateY];
+ vec.z = orig.x * data[2] + orig.y * data[6] + orig.z * data[kScaleZ] + data[kTranslateZ];
+}
+
#define MUL_ADD_STORE(a, b, c) a = (a) * (b) + (c)
void Matrix4::mapPoint(float& x, float& y) const {
@@ -401,6 +417,8 @@ void Matrix4::mapPoint(float& x, float& y) const {
}
void Matrix4::mapRect(Rect& r) const {
+ if (isIdentity()) return;
+
if (isSimple()) {
MUL_ADD_STORE(r.left, data[kScaleX], data[kTranslateX]);
MUL_ADD_STORE(r.right, data[kScaleX], data[kTranslateX]);
@@ -466,8 +484,8 @@ void Matrix4::decomposeScale(float& sx, float& sy) const {
sy = copysignf(sqrtf(len), data[mat4::kScaleY]);
}
-void Matrix4::dump() const {
- ALOGD("Matrix4[simple=%d, type=0x%x", isSimple(), getType());
+void Matrix4::dump(const char* label) const {
+ ALOGD("%s[simple=%d, type=0x%x", label ? label : "Matrix4", isSimple(), getType());
ALOGD(" %f %f %f %f", data[kScaleX], data[kSkewX], data[8], data[kTranslateX]);
ALOGD(" %f %f %f %f", data[kSkewY], data[kScaleY], data[9], data[kTranslateY]);
ALOGD(" %f %f %f %f", data[2], data[6], data[kScaleZ], data[kTranslateZ]);
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index b861ba4..1c5c578 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -26,12 +26,20 @@
namespace android {
namespace uirenderer {
-#define MATRIX_STRING "[%.2f %.2f %.2f] [%.2f %.2f %.2f] [%.2f %.2f %.2f]"
-#define MATRIX_ARGS(m) \
+#define SK_MATRIX_STRING "[%.2f %.2f %.2f] [%.2f %.2f %.2f] [%.2f %.2f %.2f]"
+#define SK_MATRIX_ARGS(m) \
(m)->get(0), (m)->get(1), (m)->get(2), \
(m)->get(3), (m)->get(4), (m)->get(5), \
(m)->get(6), (m)->get(7), (m)->get(8)
+#define MATRIX_4_STRING "[%.2f %.2f %.2f %.2f] [%.2f %.2f %.2f %.2f]" \
+ " [%.2f %.2f %.2f %.2f] [%.2f %.2f %.2f %.2f]"
+#define MATRIX_4_ARGS(m) \
+ (m)->data[0], (m)->data[4], (m)->data[8], (m)->data[12], \
+ (m)->data[1], (m)->data[5], (m)->data[9], (m)->data[13], \
+ (m)->data[2], (m)->data[6], (m)->data[10], (m)->data[14], \
+ (m)->data[3], (m)->data[7], (m)->data[11], (m)->data[15] \
+
///////////////////////////////////////////////////////////////////////////////
// Classes
///////////////////////////////////////////////////////////////////////////////
@@ -134,17 +142,19 @@ public:
void multiply(float v);
- void translate(float x, float y) {
+ void translate(float x, float y, float z = 0) {
if ((getType() & sGeometryMask) <= kTypeTranslate) {
data[kTranslateX] += x;
data[kTranslateY] += y;
+ data[kTranslateZ] += z;
+ mType |= kTypeUnknown;
} else {
// Doing a translation will only affect the translate bit of the type
// Save the type
uint8_t type = mType;
Matrix4 u;
- u.loadTranslate(x, y, 0.0f);
+ u.loadTranslate(x, y, z);
multiply(u);
// Restore the type and fix the translate bit
@@ -190,15 +200,17 @@ public:
void copyTo(float* v) const;
void copyTo(SkMatrix& v) const;
- void mapRect(Rect& r) const;
- void mapPoint(float& x, float& y) const;
+ float mapZ(const Vector3& orig) const;
+ void mapPoint3d(Vector3& vec) const;
+ void mapPoint(float& x, float& y) const; // 2d only
+ void mapRect(Rect& r) const; // 2d only
float getTranslateX() const;
float getTranslateY() const;
void decomposeScale(float& sx, float& sy) const;
- void dump() const;
+ void dump(const char* label = NULL) const;
static const Matrix4& identity();
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index c13e614..721ab3d 100755
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -21,6 +21,8 @@
#include <sys/types.h>
#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkShader.h>
#include <SkTypeface.h>
#include <utils/Log.h>
@@ -34,9 +36,20 @@
#include "DeferredDisplayList.h"
#include "DisplayListRenderer.h"
#include "Fence.h"
+#include "RenderState.h"
#include "PathTessellator.h"
#include "Properties.h"
+#include "ShadowTessellator.h"
+#include "SkiaShader.h"
+#include "utils/GLUtils.h"
#include "Vector.h"
+#include "VertexBuffer.h"
+
+#if DEBUG_DETAILED_EVENTS
+ #define EVENT_LOGD(...) eventMarkDEBUG(__VA_ARGS__)
+#else
+ #define EVENT_LOGD(...)
+#endif
namespace android {
namespace uirenderer {
@@ -45,12 +58,12 @@ namespace uirenderer {
// Defines
///////////////////////////////////////////////////////////////////////////////
-#define RAD_TO_DEG (180.0f / 3.14159265f)
-#define MIN_ANGLE 0.001f
-
-#define ALPHA_THRESHOLD 0
-
-#define FILTER(paint) (!paint || paint->isFilterBitmap() ? GL_LINEAR : GL_NEAREST)
+static GLenum getFilter(const SkPaint* paint) {
+ if (!paint || paint->getFilterLevel() != SkPaint::kNone_FilterLevel) {
+ return GL_LINEAR;
+ }
+ return GL_NEAREST;
+}
///////////////////////////////////////////////////////////////////////////////
// Globals
@@ -119,19 +132,22 @@ static inline T min(T a, T b) {
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////
-OpenGLRenderer::OpenGLRenderer():
- mCaches(Caches::getInstance()), mExtensions(Extensions::getInstance()) {
+OpenGLRenderer::OpenGLRenderer(RenderState& renderState)
+ : mFrameStarted(false)
+ , mCaches(Caches::getInstance())
+ , mExtensions(Extensions::getInstance())
+ , mRenderState(renderState)
+ , mScissorOptimizationDisabled(false)
+ , mCountOverdraw(false)
+ , mLightCenter((Vector3){FLT_MIN, FLT_MIN, FLT_MIN})
+ , mLightRadius(FLT_MIN)
+ , mAmbientShadowAlpha(0)
+ , mSpotShadowAlpha(0) {
// *set* draw modifiers to be 0
memset(&mDrawModifiers, 0, sizeof(mDrawModifiers));
mDrawModifiers.mOverrideLayerAlpha = 1.0f;
memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
-
- mFirstSnapshot = new Snapshot;
- mFrameStarted = false;
- mCountOverdraw = false;
-
- mScissorOptimizationDisabled = false;
}
OpenGLRenderer::~OpenGLRenderer() {
@@ -150,56 +166,30 @@ void OpenGLRenderer::initProperties() {
}
}
+void OpenGLRenderer::initLight(const Vector3& lightCenter, float lightRadius,
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+ mLightCenter = lightCenter;
+ mLightRadius = lightRadius;
+ mAmbientShadowAlpha = ambientShadowAlpha;
+ mSpotShadowAlpha = spotShadowAlpha;
+}
+
///////////////////////////////////////////////////////////////////////////////
// Setup
///////////////////////////////////////////////////////////////////////////////
-void OpenGLRenderer::setName(const char* name) {
- if (name) {
- mName.setTo(name);
- } else {
- mName.clear();
- }
-}
-
-const char* OpenGLRenderer::getName() const {
- return mName.string();
-}
-
-bool OpenGLRenderer::isDeferred() {
- return false;
-}
-
-void OpenGLRenderer::setViewport(int width, int height) {
- initViewport(width, height);
-
+void OpenGLRenderer::onViewportInitialized() {
glDisable(GL_DITHER);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glEnableVertexAttribArray(Program::kBindingPosition);
}
-void OpenGLRenderer::initViewport(int width, int height) {
- mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1);
-
- mWidth = width;
- mHeight = height;
-
- mFirstSnapshot->height = height;
- mFirstSnapshot->viewport.set(0, 0, width, height);
-}
-
void OpenGLRenderer::setupFrameState(float left, float top,
float right, float bottom, bool opaque) {
mCaches.clearGarbage();
-
+ initializeSaveStack(left, top, right, bottom, mLightCenter);
mOpaque = opaque;
- mSnapshot = new Snapshot(mFirstSnapshot,
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- mSnapshot->fbo = getTargetFbo();
- mSaveCount = 1;
-
- mSnapshot->setClip(left, top, right, bottom);
mTilingClip.set(left, top, right, bottom);
}
@@ -211,14 +201,14 @@ status_t OpenGLRenderer::startFrame() {
discardFramebuffer(mTilingClip.left, mTilingClip.top, mTilingClip.right, mTilingClip.bottom);
- glViewport(0, 0, mWidth, mHeight);
+ mRenderState.setViewport(getWidth(), getHeight());
// Functors break the tiling extension in pretty spectacular ways
// This ensures we don't use tiling when a functor is going to be
// invoked during the frame
mSuppressTiling = mCaches.hasRegisteredFunctors();
- startTiling(mSnapshot, true);
+ startTilingCurrentClip(true);
debugOverdraw(true, true);
@@ -226,10 +216,6 @@ status_t OpenGLRenderer::startFrame() {
mTilingClip.right, mTilingClip.bottom, mOpaque);
}
-status_t OpenGLRenderer::prepare(bool opaque) {
- return prepareDirty(0.0f, 0.0f, mWidth, mHeight, opaque);
-}
-
status_t OpenGLRenderer::prepareDirty(float left, float top,
float right, float bottom, bool opaque) {
@@ -239,7 +225,7 @@ status_t OpenGLRenderer::prepareDirty(float left, float top,
// The framebuffer renderer will first defer the display list
// for each layer and wait until the first drawing command
// to start the frame
- if (mSnapshot->fbo == 0) {
+ if (currentSnapshot()->fbo == 0) {
syncState();
updateLayers();
} else {
@@ -254,7 +240,7 @@ void OpenGLRenderer::discardFramebuffer(float left, float top, float right, floa
// perform a discard to let the driver know we don't need to preserve
// the back buffer for this frame.
if (mExtensions.hasDiscardFramebuffer() &&
- left <= 0.0f && top <= 0.0f && right >= mWidth && bottom >= mHeight) {
+ left <= 0.0f && top <= 0.0f && right >= getWidth() && bottom >= getHeight()) {
const bool isFbo = getTargetFbo() == 0;
const GLenum attachments[] = {
isFbo ? (const GLenum) GL_COLOR_EXT : (const GLenum) GL_COLOR_ATTACHMENT0,
@@ -266,7 +252,7 @@ void OpenGLRenderer::discardFramebuffer(float left, float top, float right, floa
status_t OpenGLRenderer::clear(float left, float top, float right, float bottom, bool opaque) {
if (!opaque || mCountOverdraw) {
mCaches.enableScissor();
- mCaches.setScissor(left, mSnapshot->height - bottom, right - left, bottom - top);
+ mCaches.setScissor(left, getViewportHeight() - bottom, right - left, bottom - top);
glClear(GL_COLOR_BUFFER_BIT);
return DrawGlInfo::kStatusDrew;
}
@@ -283,14 +269,16 @@ void OpenGLRenderer::syncState() {
}
}
-void OpenGLRenderer::startTiling(const sp<Snapshot>& s, bool opaque, bool expand) {
+void OpenGLRenderer::startTilingCurrentClip(bool opaque, bool expand) {
if (!mSuppressTiling) {
- Rect* clip = &mTilingClip;
- if (s->flags & Snapshot::kFlagFboTarget) {
- clip = &(s->layer->clipRect);
+ const Snapshot* snapshot = currentSnapshot();
+
+ const Rect* clip = &mTilingClip;
+ if (snapshot->flags & Snapshot::kFlagFboTarget) {
+ clip = &(snapshot->layer->clipRect);
}
- startTiling(*clip, s->height, opaque, expand);
+ startTiling(*clip, getViewportHeight(), opaque, expand);
}
}
@@ -326,28 +314,12 @@ void OpenGLRenderer::finish() {
// of the current frame
if (getTargetFbo() == 0) {
mCaches.pathCache.trim();
+ mCaches.tessellationCache.trim();
}
if (!suppressErrorChecks()) {
#if DEBUG_OPENGL
- GLenum status = GL_NO_ERROR;
- while ((status = glGetError()) != GL_NO_ERROR) {
- ALOGD("GL error from OpenGLRenderer: 0x%x", status);
- switch (status) {
- case GL_INVALID_ENUM:
- ALOGE(" GL_INVALID_ENUM");
- break;
- case GL_INVALID_VALUE:
- ALOGE(" GL_INVALID_VALUE");
- break;
- case GL_INVALID_OPERATION:
- ALOGE(" GL_INVALID_OPERATION");
- break;
- case GL_OUT_OF_MEMORY:
- ALOGE(" Out of memory!");
- break;
- }
- }
+ GLUtils::dumpGLErrors();
#endif
#if DEBUG_MEMORY_USAGE
@@ -366,112 +338,23 @@ void OpenGLRenderer::finish() {
mFrameStarted = false;
}
-void OpenGLRenderer::interrupt() {
- if (mCaches.currentProgram) {
- if (mCaches.currentProgram->isInUse()) {
- mCaches.currentProgram->remove();
- mCaches.currentProgram = NULL;
- }
- }
- mCaches.resetActiveTexture();
- mCaches.unbindMeshBuffer();
- mCaches.unbindIndicesBuffer();
- mCaches.resetVertexPointers();
- mCaches.disableTexCoordsVertexArray();
- debugOverdraw(false, false);
-}
-
-void OpenGLRenderer::resume() {
- sp<Snapshot> snapshot = (mSnapshot != NULL) ? mSnapshot : mFirstSnapshot;
- glViewport(0, 0, snapshot->viewport.getWidth(), snapshot->viewport.getHeight());
- glBindFramebuffer(GL_FRAMEBUFFER, snapshot->fbo);
- debugOverdraw(true, false);
-
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-
- mCaches.scissorEnabled = glIsEnabled(GL_SCISSOR_TEST);
- mCaches.enableScissor();
- mCaches.resetScissor();
- dirtyClip();
-
- mCaches.activeTexture(0);
- mCaches.resetBoundTextures();
-
- mCaches.blend = true;
- glEnable(GL_BLEND);
- glBlendFunc(mCaches.lastSrcMode, mCaches.lastDstMode);
- glBlendEquation(GL_FUNC_ADD);
-
- glStencilMask(0xff);
-}
-
void OpenGLRenderer::resumeAfterLayer() {
- sp<Snapshot> snapshot = (mSnapshot != NULL) ? mSnapshot : mFirstSnapshot;
- glViewport(0, 0, snapshot->viewport.getWidth(), snapshot->viewport.getHeight());
- glBindFramebuffer(GL_FRAMEBUFFER, snapshot->fbo);
+ mRenderState.setViewport(getViewportWidth(), getViewportHeight());
+ mRenderState.bindFramebuffer(currentSnapshot()->fbo);
debugOverdraw(true, false);
mCaches.resetScissor();
dirtyClip();
}
-void OpenGLRenderer::detachFunctor(Functor* functor) {
- mFunctors.remove(functor);
-}
-
-void OpenGLRenderer::attachFunctor(Functor* functor) {
- mFunctors.add(functor);
-}
-
-status_t OpenGLRenderer::invokeFunctors(Rect& dirty) {
- status_t result = DrawGlInfo::kStatusDone;
- size_t count = mFunctors.size();
-
- if (count > 0) {
- interrupt();
- SortedVector<Functor*> functors(mFunctors);
- mFunctors.clear();
-
- DrawGlInfo info;
- info.clipLeft = 0;
- info.clipTop = 0;
- info.clipRight = 0;
- info.clipBottom = 0;
- info.isLayer = false;
- info.width = 0;
- info.height = 0;
- memset(info.transform, 0, sizeof(float) * 16);
-
- for (size_t i = 0; i < count; i++) {
- Functor* f = functors.itemAt(i);
- result |= (*f)(DrawGlInfo::kModeProcess, &info);
-
- if (result & DrawGlInfo::kStatusDraw) {
- Rect localDirty(info.dirtyLeft, info.dirtyTop, info.dirtyRight, info.dirtyBottom);
- dirty.unionWith(localDirty);
- }
-
- if (result & DrawGlInfo::kStatusInvoke) {
- mFunctors.add(f);
- }
- }
- resume();
- }
-
- return result;
-}
-
status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
- if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;
-
- detachFunctor(functor);
-
+ if (currentSnapshot()->isIgnored()) return DrawGlInfo::kStatusDone;
- Rect clip(*mSnapshot->clipRect);
+ Rect clip(*currentClipRect());
clip.snapToPixelBoundaries();
// Since we don't know what the functor will draw, let's dirty
- // tne entire clip region
+ // the entire clip region
if (hasLayer()) {
dirtyLayerUnchecked(clip, getRegion());
}
@@ -482,40 +365,45 @@ status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
info.clipRight = clip.right;
info.clipBottom = clip.bottom;
info.isLayer = hasLayer();
- info.width = getSnapshot()->viewport.getWidth();
- info.height = getSnapshot()->height;
- getSnapshot()->transform->copyTo(&info.transform[0]);
+ info.width = getViewportWidth();
+ info.height = getViewportHeight();
+ currentTransform()->copyTo(&info.transform[0]);
- bool dirtyClip = mDirtyClip;
+ bool prevDirtyClip = mDirtyClip;
// setup GL state for functor
if (mDirtyClip) {
setStencilFromClip(); // can issue draws, so must precede enableScissor()/interrupt()
}
- if (mCaches.enableScissor() || dirtyClip) {
+ if (mCaches.enableScissor() || prevDirtyClip) {
setScissorFromClip();
}
- interrupt();
- // call functor immediately after GL state setup
- status_t result = (*functor)(DrawGlInfo::kModeDraw, &info);
-
- if (result != DrawGlInfo::kStatusDone) {
- Rect localDirty(info.dirtyLeft, info.dirtyTop, info.dirtyRight, info.dirtyBottom);
- dirty.unionWith(localDirty);
-
- if (result & DrawGlInfo::kStatusInvoke) {
- mFunctors.add(functor);
- }
- }
+ mRenderState.invokeFunctor(functor, DrawGlInfo::kModeDraw, &info);
+ // Scissor may have been modified, reset dirty clip
+ dirtyClip();
- resume();
- return result | DrawGlInfo::kStatusDrew;
+ return DrawGlInfo::kStatusDrew;
}
///////////////////////////////////////////////////////////////////////////////
// Debug
///////////////////////////////////////////////////////////////////////////////
+void OpenGLRenderer::eventMarkDEBUG(const char* fmt, ...) const {
+#if DEBUG_DETAILED_EVENTS
+ const int BUFFER_SIZE = 256;
+ va_list ap;
+ char buf[BUFFER_SIZE];
+
+ va_start(ap, fmt);
+ vsnprintf(buf, BUFFER_SIZE, fmt, ap);
+ va_end(ap);
+
+ eventMark(buf);
+#endif
+}
+
+
void OpenGLRenderer::eventMark(const char* name) const {
mCaches.eventMark(0, name);
}
@@ -529,17 +417,7 @@ void OpenGLRenderer::endMark() const {
}
void OpenGLRenderer::debugOverdraw(bool enable, bool clear) {
- if (mCaches.debugOverdraw && getTargetFbo() == 0) {
- if (clear) {
- mCaches.disableScissor();
- mCaches.stencil.clear();
- }
- if (enable) {
- mCaches.stencil.enableDebugWrite();
- } else {
- mCaches.stencil.disable();
- }
- }
+ mRenderState.debugOverdraw(enable, clear);
}
void OpenGLRenderer::renderOverdraw() {
@@ -547,7 +425,7 @@ void OpenGLRenderer::renderOverdraw() {
const Rect* clip = &mTilingClip;
mCaches.enableScissor();
- mCaches.setScissor(clip->left, mFirstSnapshot->height - clip->bottom,
+ mCaches.setScissor(clip->left, firstSnapshot()->getViewportHeight() - clip->bottom,
clip->right - clip->left, clip->bottom - clip->top);
// 1x overdraw
@@ -571,9 +449,9 @@ void OpenGLRenderer::renderOverdraw() {
}
void OpenGLRenderer::countOverdraw() {
- size_t count = mWidth * mHeight;
+ size_t count = getWidth() * getHeight();
uint32_t* buffer = new uint32_t[count];
- glReadPixels(0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0]);
+ glReadPixels(0, 0, getWidth(), getHeight(), GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0]);
size_t total = 0;
for (size_t i = 0; i < count; i++) {
@@ -590,8 +468,8 @@ void OpenGLRenderer::countOverdraw() {
///////////////////////////////////////////////////////////////////////////////
bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
- if (layer->deferredUpdateScheduled && layer->renderer &&
- layer->displayList && layer->displayList->isRenderable()) {
+ if (layer->deferredUpdateScheduled && layer->renderer
+ && layer->renderNode.get() && layer->renderNode->isRenderable()) {
ATRACE_CALL();
Rect& dirty = layer->dirtyRect;
@@ -602,14 +480,14 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) {
}
if (CC_UNLIKELY(inFrame || mCaches.drawDeferDisabled)) {
- layer->render();
+ layer->render(*this);
} else {
- layer->defer();
+ layer->defer(*this);
}
if (inFrame) {
resumeAfterLayer();
- startTiling(mSnapshot);
+ startTilingCurrentClip();
}
layer->debugDrawUpdate = mCaches.debugLayersUpdates;
@@ -644,7 +522,7 @@ void OpenGLRenderer::updateLayers() {
if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
mLayerUpdates.clear();
- glBindFramebuffer(GL_FRAMEBUFFER, getTargetFbo());
+ mRenderState.bindFramebuffer(getTargetFbo());
}
endMark();
}
@@ -672,7 +550,7 @@ void OpenGLRenderer::flushLayers() {
}
mLayerUpdates.clear();
- glBindFramebuffer(GL_FRAMEBUFFER, getTargetFbo());
+ mRenderState.bindFramebuffer(getTargetFbo());
endMark();
}
@@ -719,6 +597,7 @@ void OpenGLRenderer::clearLayerUpdates() {
}
void OpenGLRenderer::flushLayerUpdates() {
+ ATRACE_CALL();
syncState();
updateLayers();
flushLayers();
@@ -730,49 +609,14 @@ void OpenGLRenderer::flushLayerUpdates() {
// State management
///////////////////////////////////////////////////////////////////////////////
-int OpenGLRenderer::getSaveCount() const {
- return mSaveCount;
-}
+void OpenGLRenderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {
+ bool restoreViewport = removed.flags & Snapshot::kFlagIsFboLayer;
+ bool restoreClip = removed.flags & Snapshot::kFlagClipSet;
+ bool restoreLayer = removed.flags & Snapshot::kFlagIsLayer;
-int OpenGLRenderer::save(int flags) {
- return saveSnapshot(flags);
-}
-
-void OpenGLRenderer::restore() {
- if (mSaveCount > 1) {
- restoreSnapshot();
+ if (restoreViewport) {
+ mRenderState.setViewport(getViewportWidth(), getViewportHeight());
}
-}
-
-void OpenGLRenderer::restoreToCount(int saveCount) {
- if (saveCount < 1) saveCount = 1;
-
- while (mSaveCount > saveCount) {
- restoreSnapshot();
- }
-}
-
-int OpenGLRenderer::saveSnapshot(int flags) {
- mSnapshot = new Snapshot(mSnapshot, flags);
- return mSaveCount++;
-}
-
-bool OpenGLRenderer::restoreSnapshot() {
- bool restoreClip = mSnapshot->flags & Snapshot::kFlagClipSet;
- bool restoreLayer = mSnapshot->flags & Snapshot::kFlagIsLayer;
- bool restoreOrtho = mSnapshot->flags & Snapshot::kFlagDirtyOrtho;
-
- sp<Snapshot> current = mSnapshot;
- sp<Snapshot> previous = mSnapshot->previous;
-
- if (restoreOrtho) {
- Rect& r = previous->viewport;
- glViewport(r.left, r.top, r.right, r.bottom);
- mOrthoMatrix.load(current->orthoMatrix);
- }
-
- mSaveCount--;
- mSnapshot = previous;
if (restoreClip) {
dirtyClip();
@@ -781,11 +625,9 @@ bool OpenGLRenderer::restoreSnapshot() {
if (restoreLayer) {
endMark(); // Savelayer
startMark("ComposeLayer");
- composeLayer(current, previous);
+ composeLayer(removed, restored);
endMark();
}
-
- return restoreClip;
}
///////////////////////////////////////////////////////////////////////////////
@@ -793,12 +635,11 @@ bool OpenGLRenderer::restoreSnapshot() {
///////////////////////////////////////////////////////////////////////////////
int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags) {
- const GLuint previousFbo = mSnapshot->fbo;
+ const SkPaint* paint, int flags, const SkPath* convexMask) {
const int count = saveSnapshot(flags);
- if (!mSnapshot->isIgnored()) {
- createLayer(left, top, right, bottom, alpha, mode, flags, previousFbo);
+ if (!currentSnapshot()->isIgnored()) {
+ createLayer(left, top, right, bottom, paint, flags, convexMask);
}
return count;
@@ -807,22 +648,24 @@ int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool fboLayer) {
const Rect untransformedBounds(bounds);
- currentTransform().mapRect(bounds);
+ currentTransform()->mapRect(bounds);
// Layers only make sense if they are in the framebuffer's bounds
- if (bounds.intersect(*mSnapshot->clipRect)) {
+ if (bounds.intersect(*currentClipRect())) {
// We cannot work with sub-pixels in this case
bounds.snapToPixelBoundaries();
// When the layer is not an FBO, we may use glCopyTexImage so we
// need to make sure the layer does not extend outside the bounds
// of the framebuffer
- if (!bounds.intersect(mSnapshot->previous->viewport)) {
+ const Snapshot& previous = *(currentSnapshot()->previous);
+ Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight());
+ if (!bounds.intersect(previousViewport)) {
bounds.setEmpty();
} else if (fboLayer) {
clip.set(bounds);
mat4 inverse;
- inverse.loadInverse(currentTransform());
+ inverse.loadInverse(*currentTransform());
inverse.mapRect(clip);
clip.snapToPixelBoundaries();
if (clip.intersect(untransformedBounds)) {
@@ -844,16 +687,15 @@ void OpenGLRenderer::updateSnapshotIgnoreForLayer(const Rect& bounds, const Rect
(fboLayer && clip.isEmpty())) {
mSnapshot->empty = fboLayer;
} else {
- mSnapshot->invisible = mSnapshot->invisible || (alpha <= ALPHA_THRESHOLD && fboLayer);
+ mSnapshot->invisible = mSnapshot->invisible || (alpha <= 0 && fboLayer);
}
}
int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags) {
- const GLuint previousFbo = mSnapshot->fbo;
+ const SkPaint* paint, int flags) {
const int count = saveSnapshot(flags);
- if (!mSnapshot->isIgnored() && (flags & SkCanvas::kClipToLayer_SaveFlag)) {
+ if (!currentSnapshot()->isIgnored() && (flags & SkCanvas::kClipToLayer_SaveFlag)) {
// initialize the snapshot as though it almost represents an FBO layer so deferred draw
// operations will be able to store and restore the current clip and transform info, and
// quick rejection will be correct (for display lists)
@@ -861,19 +703,19 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float
Rect bounds(left, top, right, bottom);
Rect clip;
calculateLayerBoundsAndClip(bounds, clip, true);
- updateSnapshotIgnoreForLayer(bounds, clip, true, alpha);
+ updateSnapshotIgnoreForLayer(bounds, clip, true, getAlphaDirect(paint));
- if (!mSnapshot->isIgnored()) {
+ if (!currentSnapshot()->isIgnored()) {
mSnapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
mSnapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
- mSnapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
+ mSnapshot->initializeViewport(bounds.getWidth(), bounds.getHeight());
+ mSnapshot->roundRectClipState = NULL;
}
}
return count;
}
-
/**
* Layers are viewed by Skia are slightly different than layers in image editing
* programs (for instance.) When a layer is created, previously created layers
@@ -926,7 +768,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float
* something actually gets drawn are the layers regions cleared.
*/
bool OpenGLRenderer::createLayer(float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo) {
+ const SkPaint* paint, int flags, const SkPath* convexMask) {
LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top);
LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
@@ -936,26 +778,27 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto
Rect clip;
Rect bounds(left, top, right, bottom);
calculateLayerBoundsAndClip(bounds, clip, fboLayer);
- updateSnapshotIgnoreForLayer(bounds, clip, fboLayer, alpha);
+ updateSnapshotIgnoreForLayer(bounds, clip, fboLayer, getAlphaDirect(paint));
// Bail out if we won't draw in this snapshot
- if (mSnapshot->isIgnored()) {
+ if (currentSnapshot()->isIgnored()) {
return false;
}
mCaches.activeTexture(0);
- Layer* layer = mCaches.layerCache.get(bounds.getWidth(), bounds.getHeight());
+ Layer* layer = mCaches.layerCache.get(mRenderState, bounds.getWidth(), bounds.getHeight());
if (!layer) {
return false;
}
- layer->setAlpha(alpha, mode);
+ layer->setPaint(paint);
layer->layer.set(bounds);
layer->texCoords.set(0.0f, bounds.getHeight() / float(layer->getHeight()),
bounds.getWidth() / float(layer->getWidth()), 0.0f);
- layer->setColorFilter(mDrawModifiers.mColorFilter);
+
layer->setBlend(true);
layer->setDirty(false);
+ layer->setConvexMask(convexMask); // note: the mask must be cleared before returning to the cache
// Save the layer in the snapshot
mSnapshot->flags |= Snapshot::kFlagIsLayer;
@@ -963,7 +806,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto
startMark("SaveLayer");
if (fboLayer) {
- return createFboLayer(layer, bounds, clip, previousFbo);
+ return createFboLayer(layer, bounds, clip);
} else {
// Copy the framebuffer into the layer
layer->bindTexture();
@@ -978,8 +821,9 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto
layer->setEmpty(false);
}
- glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bounds.left,
- mSnapshot->height - bounds.bottom, bounds.getWidth(), bounds.getHeight());
+ glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
+ bounds.left, getViewportHeight() - bounds.bottom,
+ bounds.getWidth(), bounds.getHeight());
// Enqueue the buffer coordinates to clear the corresponding region later
mLayers.push(new Rect(bounds));
@@ -989,24 +833,22 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto
return true;
}
-bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLuint previousFbo) {
+bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) {
layer->clipRect.set(clip);
layer->setFbo(mCaches.fboCache.get());
mSnapshot->region = &mSnapshot->layer->region;
- mSnapshot->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer |
- Snapshot::kFlagDirtyOrtho;
+ mSnapshot->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer;
mSnapshot->fbo = layer->getFbo();
mSnapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
mSnapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
- mSnapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
- mSnapshot->height = bounds.getHeight();
- mSnapshot->orthoMatrix.load(mOrthoMatrix);
+ mSnapshot->initializeViewport(bounds.getWidth(), bounds.getHeight());
+ mSnapshot->roundRectClipState = NULL;
endTiling();
debugOverdraw(false, false);
// Bind texture to FBO
- glBindFramebuffer(GL_FRAMEBUFFER, layer->getFbo());
+ mRenderState.bindFramebuffer(layer->getFbo());
layer->bindTexture();
// Initialize the texture if needed
@@ -1019,7 +861,7 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLui
layer->getTexture(), 0);
// Expand the startTiling region by 1
- startTiling(mSnapshot, true, true);
+ startTilingCurrentClip(true, true);
// Clear the FBO, expand the clear region by 1 to get nice bilinear filtering
mCaches.enableScissor();
@@ -1030,27 +872,26 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLui
dirtyClip();
// Change the ortho projection
- glViewport(0, 0, bounds.getWidth(), bounds.getHeight());
- mOrthoMatrix.loadOrtho(0.0f, bounds.getWidth(), bounds.getHeight(), 0.0f, -1.0f, 1.0f);
-
+ mRenderState.setViewport(bounds.getWidth(), bounds.getHeight());
return true;
}
/**
* Read the documentation of createLayer() before doing anything in this method.
*/
-void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
- if (!current->layer) {
+void OpenGLRenderer::composeLayer(const Snapshot& removed, const Snapshot& restored) {
+ if (!removed.layer) {
ALOGE("Attempting to compose a layer that does not exist");
return;
}
- Layer* layer = current->layer;
+ Layer* layer = removed.layer;
const Rect& rect = layer->layer;
- const bool fboLayer = current->flags & Snapshot::kFlagIsFboLayer;
+ const bool fboLayer = removed.flags & Snapshot::kFlagIsFboLayer;
bool clipRequired = false;
- quickRejectNoScissor(rect, &clipRequired); // safely ignore return, should never be rejected
+ calculateQuickRejectForScissor(rect.left, rect.top, rect.right, rect.bottom,
+ &clipRequired, NULL, false); // safely ignore return, should never be rejected
mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired);
if (fboLayer) {
@@ -1062,15 +903,19 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
layer->removeFbo(false);
// Unbind current FBO and restore previous one
- glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
+ mRenderState.bindFramebuffer(restored.fbo);
debugOverdraw(true, false);
- startTiling(previous);
+ startTilingCurrentClip();
}
if (!fboLayer && layer->getAlpha() < 255) {
- drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
- layer->getAlpha() << 24, SkXfermode::kDstIn_Mode, true);
+ SkPaint layerPaint;
+ layerPaint.setAlpha(layer->getAlpha());
+ layerPaint.setXfermodeMode(SkXfermode::kDstIn_Mode);
+ layerPaint.setColorFilter(layer->getColorFilter());
+
+ drawColorRect(rect.left, rect.top, rect.right, rect.bottom, &layerPaint, true);
// Required below, composeLayerRect() will divide by 255
layer->setAlpha(255);
}
@@ -1082,14 +927,8 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
// When the layer is stored in an FBO, we can save a bit of fillrate by
// drawing only the dirty region
if (fboLayer) {
- dirtyLayer(rect.left, rect.top, rect.right, rect.bottom, *previous->transform);
- if (layer->getColorFilter()) {
- setupColorFilter(layer->getColorFilter());
- }
+ dirtyLayer(rect.left, rect.top, rect.right, rect.bottom, *restored.transform);
composeLayerRegion(layer, rect);
- if (layer->getColorFilter()) {
- resetColorFilter();
- }
} else if (!rect.isEmpty()) {
dirtyLayer(rect.left, rect.top, rect.right, rect.bottom);
@@ -1104,6 +943,7 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
dirtyClip();
// Failing to add the layer to the cache should happen only if the layer is too large
+ layer->setConvexMask(NULL);
if (!mCaches.layerCache.put(layer)) {
LAYER_LOGD("Deleting layer");
Caches::getInstance().resourceCache.decrementRefcount(layer);
@@ -1121,51 +961,60 @@ void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) {
}
setupDrawTextureTransform();
setupDrawColor(alpha, alpha, alpha, alpha);
- setupDrawColorFilter();
- setupDrawBlending(layer->isBlend() || alpha < 1.0f, layer->getMode());
+ setupDrawColorFilter(layer->getColorFilter());
+ setupDrawBlending(layer);
setupDrawProgram();
setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
+ setupDrawColorFilterUniforms(layer->getColorFilter());
if (layer->getRenderTarget() == GL_TEXTURE_2D) {
setupDrawTexture(layer->getTexture());
} else {
setupDrawExternalTexture(layer->getTexture());
}
- if (currentTransform().isPureTranslate() &&
+ if (currentTransform()->isPureTranslate() &&
+ !layer->getForceFilter() &&
layer->getWidth() == (uint32_t) rect.getWidth() &&
layer->getHeight() == (uint32_t) rect.getHeight()) {
- const float x = (int) floorf(rect.left + currentTransform().getTranslateX() + 0.5f);
- const float y = (int) floorf(rect.top + currentTransform().getTranslateY() + 0.5f);
+ const float x = (int) floorf(rect.left + currentTransform()->getTranslateX() + 0.5f);
+ const float y = (int) floorf(rect.top + currentTransform()->getTranslateY() + 0.5f);
layer->setFilter(GL_NEAREST);
- setupDrawModelView(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
+ setupDrawModelView(kModelViewMode_TranslateAndScale, false,
+ x, y, x + rect.getWidth(), y + rect.getHeight(), true);
} else {
layer->setFilter(GL_LINEAR);
- setupDrawModelView(rect.left, rect.top, rect.right, rect.bottom);
+ setupDrawModelView(kModelViewMode_TranslateAndScale, false,
+ rect.left, rect.top, rect.right, rect.bottom);
}
setupDrawTextureTransformUniforms(layer->getTexTransform());
- setupDrawMesh(&mMeshVertices[0].position[0], &mMeshVertices[0].texture[0]);
+ setupDrawMesh(&mMeshVertices[0].x, &mMeshVertices[0].u);
glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
}
void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) {
- if (!layer->isTextureLayer()) {
+ if (layer->isTextureLayer()) {
+ EVENT_LOGD("composeTextureLayerRect");
+ resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
+ drawTextureLayer(layer, rect);
+ resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+ } else {
+ EVENT_LOGD("composeHardwareLayerRect");
const Rect& texCoords = layer->texCoords;
resetDrawTextureTexCoords(texCoords.left, texCoords.top,
texCoords.right, texCoords.bottom);
float x = rect.left;
float y = rect.top;
- bool simpleTransform = currentTransform().isPureTranslate() &&
+ bool simpleTransform = currentTransform()->isPureTranslate() &&
layer->getWidth() == (uint32_t) rect.getWidth() &&
layer->getHeight() == (uint32_t) rect.getHeight();
if (simpleTransform) {
// When we're swapping, the layer is already in screen coordinates
if (!swap) {
- x = (int) floorf(rect.left + currentTransform().getTranslateX() + 0.5f);
- y = (int) floorf(rect.top + currentTransform().getTranslateY() + 0.5f);
+ x = (int) floorf(rect.left + currentTransform()->getTranslateX() + 0.5f);
+ y = (int) floorf(rect.top + currentTransform()->getTranslateY() + 0.5f);
}
layer->setFilter(GL_NEAREST, true);
@@ -1173,18 +1022,18 @@ void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap)
layer->setFilter(GL_LINEAR, true);
}
- float alpha = getLayerAlpha(layer);
- bool blend = layer->isBlend() || alpha < 1.0f;
+ SkPaint layerPaint;
+ layerPaint.setAlpha(getLayerAlpha(layer) * 255);
+ layerPaint.setXfermodeMode(layer->getMode());
+ layerPaint.setColorFilter(layer->getColorFilter());
+
+ bool blend = layer->isBlend() || getLayerAlpha(layer) < 1.0f;
drawTextureMesh(x, y, x + rect.getWidth(), y + rect.getHeight(),
- layer->getTexture(), alpha, layer->getMode(), blend,
- &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+ layer->getTexture(), &layerPaint, blend,
+ &mMeshVertices[0].x, &mMeshVertices[0].u,
GL_TRIANGLE_STRIP, gMeshCount, swap, swap || simpleTransform);
resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
- } else {
- resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
- drawTextureLayer(layer, rect);
- resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
}
}
@@ -1205,7 +1054,76 @@ void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap)
#define DRAW_DOUBLE_STENCIL(DRAW_COMMAND) DRAW_DOUBLE_STENCIL_IF(true, DRAW_COMMAND)
+// This class is purely for inspection. It inherits from SkShader, but Skia does not know how to
+// use it. The OpenGLRenderer will look at it to find its Layer and whether it is opaque.
+class LayerShader : public SkShader {
+public:
+ LayerShader(Layer* layer, const SkMatrix* localMatrix)
+ : INHERITED(localMatrix)
+ , mLayer(layer) {
+ }
+
+ virtual bool asACustomShader(void** data) const {
+ if (data) {
+ *data = static_cast<void*>(mLayer);
+ }
+ return true;
+ }
+
+ virtual bool isOpaque() const {
+ return !mLayer->isBlend();
+ }
+
+protected:
+ virtual void shadeSpan(int x, int y, SkPMColor[], int count) {
+ LOG_ALWAYS_FATAL("LayerShader should never be drawn with raster backend.");
+ }
+
+ virtual void flatten(SkWriteBuffer&) const {
+ LOG_ALWAYS_FATAL("LayerShader should never be flattened.");
+ }
+
+ virtual Factory getFactory() const {
+ LOG_ALWAYS_FATAL("LayerShader should never be created from a stream.");
+ return NULL;
+ }
+private:
+ // Unowned.
+ Layer* mLayer;
+ typedef SkShader INHERITED;
+};
+
void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) {
+ if (CC_UNLIKELY(layer->region.isEmpty())) return; // nothing to draw
+
+ if (layer->getConvexMask()) {
+ save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag);
+
+ // clip to the area of the layer the mask can be larger
+ clipRect(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kIntersect_Op);
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setColor(SkColorSetARGB(int(getLayerAlpha(layer) * 255), 0, 0, 0));
+
+ // create LayerShader to map SaveLayer content into subsequent draw
+ SkMatrix shaderMatrix;
+ shaderMatrix.setTranslate(rect.left, rect.bottom);
+ shaderMatrix.preScale(1, -1);
+ LayerShader layerShader(layer, &shaderMatrix);
+ paint.setShader(&layerShader);
+
+ // Since the drawing primitive is defined in local drawing space,
+ // we don't need to modify the draw matrix
+ const SkPath* maskPath = layer->getConvexMask();
+ DRAW_DOUBLE_STENCIL(drawConvexPath(*maskPath, &paint));
+
+ paint.setShader(NULL);
+ restore();
+
+ return;
+ }
+
if (layer->region.isRect()) {
layer->setRegionAsRect();
@@ -1215,90 +1133,92 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) {
return;
}
- if (CC_LIKELY(!layer->region.isEmpty())) {
- size_t count;
- const android::Rect* rects;
- Region safeRegion;
- if (CC_LIKELY(hasRectToRectTransform())) {
- rects = layer->region.getArray(&count);
- } else {
- safeRegion = Region::createTJunctionFreeRegion(layer->region);
- rects = safeRegion.getArray(&count);
- }
+ EVENT_LOGD("composeLayerRegion");
+ // standard Region based draw
+ size_t count;
+ const android::Rect* rects;
+ Region safeRegion;
+ if (CC_LIKELY(hasRectToRectTransform())) {
+ rects = layer->region.getArray(&count);
+ } else {
+ safeRegion = Region::createTJunctionFreeRegion(layer->region);
+ rects = safeRegion.getArray(&count);
+ }
- const float alpha = getLayerAlpha(layer);
- const float texX = 1.0f / float(layer->getWidth());
- const float texY = 1.0f / float(layer->getHeight());
- const float height = rect.getHeight();
+ const float alpha = getLayerAlpha(layer);
+ const float texX = 1.0f / float(layer->getWidth());
+ const float texY = 1.0f / float(layer->getHeight());
+ const float height = rect.getHeight();
- setupDraw();
+ setupDraw();
- // We must get (and therefore bind) the region mesh buffer
- // after we setup drawing in case we need to mess with the
- // stencil buffer in setupDraw()
- TextureVertex* mesh = mCaches.getRegionMesh();
- uint32_t numQuads = 0;
+ // We must get (and therefore bind) the region mesh buffer
+ // after we setup drawing in case we need to mess with the
+ // stencil buffer in setupDraw()
+ TextureVertex* mesh = mCaches.getRegionMesh();
+ uint32_t numQuads = 0;
- setupDrawWithTexture();
- setupDrawColor(alpha, alpha, alpha, alpha);
- setupDrawColorFilter();
- setupDrawBlending(layer->isBlend() || alpha < 1.0f, layer->getMode(), false);
- setupDrawProgram();
- setupDrawDirtyRegionsDisabled();
- setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawTexture(layer->getTexture());
- if (currentTransform().isPureTranslate()) {
- const float x = (int) floorf(rect.left + currentTransform().getTranslateX() + 0.5f);
- const float y = (int) floorf(rect.top + currentTransform().getTranslateY() + 0.5f);
+ setupDrawWithTexture();
+ setupDrawColor(alpha, alpha, alpha, alpha);
+ setupDrawColorFilter(layer->getColorFilter());
+ setupDrawBlending(layer);
+ setupDrawProgram();
+ setupDrawDirtyRegionsDisabled();
+ setupDrawPureColorUniforms();
+ setupDrawColorFilterUniforms(layer->getColorFilter());
+ setupDrawTexture(layer->getTexture());
+ if (currentTransform()->isPureTranslate()) {
+ const float x = (int) floorf(rect.left + currentTransform()->getTranslateX() + 0.5f);
+ const float y = (int) floorf(rect.top + currentTransform()->getTranslateY() + 0.5f);
- layer->setFilter(GL_NEAREST);
- setupDrawModelViewTranslate(x, y, x + rect.getWidth(), y + rect.getHeight(), true);
- } else {
- layer->setFilter(GL_LINEAR);
- setupDrawModelViewTranslate(rect.left, rect.top, rect.right, rect.bottom);
- }
- setupDrawMeshIndices(&mesh[0].position[0], &mesh[0].texture[0]);
+ layer->setFilter(GL_NEAREST);
+ setupDrawModelView(kModelViewMode_Translate, false,
+ x, y, x + rect.getWidth(), y + rect.getHeight(), true);
+ } else {
+ layer->setFilter(GL_LINEAR);
+ setupDrawModelView(kModelViewMode_Translate, false,
+ rect.left, rect.top, rect.right, rect.bottom);
+ }
+ setupDrawMeshIndices(&mesh[0].x, &mesh[0].u);
- for (size_t i = 0; i < count; i++) {
- const android::Rect* r = &rects[i];
-
- const float u1 = r->left * texX;
- const float v1 = (height - r->top) * texY;
- const float u2 = r->right * texX;
- const float v2 = (height - r->bottom) * texY;
-
- // TODO: Reject quads outside of the clip
- TextureVertex::set(mesh++, r->left, r->top, u1, v1);
- TextureVertex::set(mesh++, r->right, r->top, u2, v1);
- TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
- TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
-
- numQuads++;
-
- if (numQuads >= gMaxNumberOfQuads) {
- DRAW_DOUBLE_STENCIL(glDrawElements(GL_TRIANGLES, numQuads * 6,
- GL_UNSIGNED_SHORT, NULL));
- numQuads = 0;
- mesh = mCaches.getRegionMesh();
- }
- }
+ for (size_t i = 0; i < count; i++) {
+ const android::Rect* r = &rects[i];
+
+ const float u1 = r->left * texX;
+ const float v1 = (height - r->top) * texY;
+ const float u2 = r->right * texX;
+ const float v2 = (height - r->bottom) * texY;
+
+ // TODO: Reject quads outside of the clip
+ TextureVertex::set(mesh++, r->left, r->top, u1, v1);
+ TextureVertex::set(mesh++, r->right, r->top, u2, v1);
+ TextureVertex::set(mesh++, r->left, r->bottom, u1, v2);
+ TextureVertex::set(mesh++, r->right, r->bottom, u2, v2);
+
+ numQuads++;
- if (numQuads > 0) {
+ if (numQuads >= gMaxNumberOfQuads) {
DRAW_DOUBLE_STENCIL(glDrawElements(GL_TRIANGLES, numQuads * 6,
GL_UNSIGNED_SHORT, NULL));
+ numQuads = 0;
+ mesh = mCaches.getRegionMesh();
}
+ }
+
+ if (numQuads > 0) {
+ DRAW_DOUBLE_STENCIL(glDrawElements(GL_TRIANGLES, numQuads * 6,
+ GL_UNSIGNED_SHORT, NULL));
+ }
#if DEBUG_LAYERS_AS_REGIONS
- drawRegionRects(layer->region);
+ drawRegionRectsDebug(layer->region);
#endif
- layer->region.clear();
- }
+ layer->region.clear();
}
-void OpenGLRenderer::drawRegionRects(const Region& region) {
#if DEBUG_LAYERS_AS_REGIONS
+void OpenGLRenderer::drawRegionRectsDebug(const Region& region) {
size_t count;
const android::Rect* rects = region.getArray(&count);
@@ -1316,15 +1236,15 @@ void OpenGLRenderer::drawRegionRects(const Region& region) {
top = rects[i].top;
}
+ SkPaint paint;
+ paint.setColor(colors[offset + (i & 0x1)]);
Rect r(rects[i].left, rects[i].top, rects[i].right, rects[i].bottom);
- drawColorRect(r.left, r.top, r.right, r.bottom, colors[offset + (i & 0x1)],
- SkXfermode::kSrcOver_Mode);
+ drawColorRect(r.left, r.top, r.right, r.bottom, paint);
}
-#endif
}
+#endif
-void OpenGLRenderer::drawRegionRects(const SkRegion& region, int color,
- SkXfermode::Mode mode, bool dirty) {
+void OpenGLRenderer::drawRegionRects(const SkRegion& region, const SkPaint& paint, bool dirty) {
Vector<float> rects;
SkRegion::Iterator it(region);
@@ -1337,7 +1257,7 @@ void OpenGLRenderer::drawRegionRects(const SkRegion& region, int color,
it.next();
}
- drawColorRects(rects.array(), rects.size(), color, mode, true, dirty, false);
+ drawColorRects(rects.array(), rects.size(), &paint, true, dirty, false);
}
void OpenGLRenderer::dirtyLayer(const float left, const float top,
@@ -1358,7 +1278,7 @@ void OpenGLRenderer::dirtyLayer(const float left, const float top,
}
void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) {
- if (bounds.intersect(*mSnapshot->clipRect)) {
+ if (bounds.intersect(*currentClipRect())) {
bounds.snapToPixelBoundaries();
android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
if (!dirty.isEmpty()) {
@@ -1367,12 +1287,12 @@ void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) {
}
}
-void OpenGLRenderer::drawIndexedQuads(Vertex* mesh, GLsizei quadsCount) {
+void OpenGLRenderer::issueIndexedQuadDraw(Vertex* mesh, GLsizei quadsCount) {
GLsizei elementsCount = quadsCount * 6;
while (elementsCount > 0) {
GLsizei drawCount = min(elementsCount, (GLsizei) gMaxNumberOfQuads * 6);
- setupDrawIndexedVertices(&mesh[0].position[0]);
+ setupDrawIndexedVertices(&mesh[0].x);
glDrawElements(GL_TRIANGLES, drawCount, GL_UNSIGNED_SHORT, NULL);
elementsCount -= drawCount;
@@ -1386,7 +1306,8 @@ void OpenGLRenderer::clearLayerRegions() {
const size_t count = mLayers.size();
if (count == 0) return;
- if (!mSnapshot->isIgnored()) {
+ if (!currentSnapshot()->isIgnored()) {
+ EVENT_LOGD("clearLayerRegions");
// Doing several glScissor/glClear here can negatively impact
// GPUs with a tiler architecture, instead we draw quads with
// the Clear blending mode
@@ -1414,14 +1335,18 @@ void OpenGLRenderer::clearLayerRegions() {
// the same thing again
mLayers.clear();
+ SkPaint clearPaint;
+ clearPaint.setXfermodeMode(SkXfermode::kClear_Mode);
+
setupDraw(false);
setupDrawColor(0.0f, 0.0f, 0.0f, 1.0f);
- setupDrawBlending(true, SkXfermode::kClear_Mode);
+ setupDrawBlending(&clearPaint, true);
setupDrawProgram();
setupDrawPureColorUniforms();
- setupDrawModelViewTranslate(0.0f, 0.0f, 0.0f, 0.0f, true);
+ setupDrawModelView(kModelViewMode_Translate, false,
+ 0.0f, 0.0f, 0.0f, 0.0f, true);
- drawIndexedQuads(&mesh[0], count);
+ issueIndexedQuadDraw(&mesh[0], count);
if (scissorChanged) mCaches.enableScissor();
} else {
@@ -1437,58 +1362,62 @@ void OpenGLRenderer::clearLayerRegions() {
///////////////////////////////////////////////////////////////////////////////
bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
- const Rect& currentClip = *(mSnapshot->clipRect);
- const mat4& currentMatrix = *(mSnapshot->transform);
+ const Rect* currentClip = currentClipRect();
+ const mat4* currentMatrix = currentTransform();
if (stateDeferFlags & kStateDeferFlag_Draw) {
// state has bounds initialized in local coordinates
if (!state.mBounds.isEmpty()) {
- currentMatrix.mapRect(state.mBounds);
+ currentMatrix->mapRect(state.mBounds);
Rect clippedBounds(state.mBounds);
// NOTE: if we ever want to use this clipping info to drive whether the scissor
// is used, it should more closely duplicate the quickReject logic (in how it uses
// snapToPixelBoundaries)
- if(!clippedBounds.intersect(currentClip)) {
+ if(!clippedBounds.intersect(*currentClip)) {
// quick rejected
return true;
}
state.mClipSideFlags = kClipSide_None;
- if (!currentClip.contains(state.mBounds)) {
+ if (!currentClip->contains(state.mBounds)) {
int& flags = state.mClipSideFlags;
// op partially clipped, so record which sides are clipped for clip-aware merging
- if (currentClip.left > state.mBounds.left) flags |= kClipSide_Left;
- if (currentClip.top > state.mBounds.top) flags |= kClipSide_Top;
- if (currentClip.right < state.mBounds.right) flags |= kClipSide_Right;
- if (currentClip.bottom < state.mBounds.bottom) flags |= kClipSide_Bottom;
+ if (currentClip->left > state.mBounds.left) flags |= kClipSide_Left;
+ if (currentClip->top > state.mBounds.top) flags |= kClipSide_Top;
+ if (currentClip->right < state.mBounds.right) flags |= kClipSide_Right;
+ if (currentClip->bottom < state.mBounds.bottom) flags |= kClipSide_Bottom;
}
state.mBounds.set(clippedBounds);
} else {
// Empty bounds implies size unknown. Label op as conservatively clipped to disable
// overdraw avoidance (since we don't know what it overlaps)
state.mClipSideFlags = kClipSide_ConservativeFull;
- state.mBounds.set(currentClip);
+ state.mBounds.set(*currentClip);
}
}
state.mClipValid = (stateDeferFlags & kStateDeferFlag_Clip);
if (state.mClipValid) {
- state.mClip.set(currentClip);
+ state.mClip.set(*currentClip);
}
// Transform, drawModifiers, and alpha always deferred, since they are used by state operations
// (Note: saveLayer/restore use colorFilter and alpha, so we just save restore everything)
- state.mMatrix.load(currentMatrix);
+ state.mMatrix.load(*currentMatrix);
state.mDrawModifiers = mDrawModifiers;
- state.mAlpha = mSnapshot->alpha;
+ state.mAlpha = currentSnapshot()->alpha;
+
+ // always store/restore, since it's just a pointer
+ state.mRoundRectClipState = currentSnapshot()->roundRectClipState;
return false;
}
void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore) {
- currentTransform().load(state.mMatrix);
- mDrawModifiers = state.mDrawModifiers;
+ setMatrix(state.mMatrix);
mSnapshot->alpha = state.mAlpha;
+ mDrawModifiers = state.mDrawModifiers;
+ mSnapshot->roundRectClipState = state.mRoundRectClipState;
if (state.mClipValid && !skipClipRestore) {
mSnapshot->setClip(state.mClip.left, state.mClip.top,
@@ -1508,64 +1437,21 @@ void OpenGLRenderer::setupMergedMultiDraw(const Rect* clipRect) {
if (clipRect != NULL) {
mSnapshot->setClip(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
} else {
- mSnapshot->setClip(0, 0, mWidth, mHeight);
+ mSnapshot->setClip(0, 0, getWidth(), getHeight());
}
dirtyClip();
mCaches.setScissorEnabled(clipRect != NULL || mScissorOptimizationDisabled);
}
///////////////////////////////////////////////////////////////////////////////
-// Transforms
-///////////////////////////////////////////////////////////////////////////////
-
-void OpenGLRenderer::translate(float dx, float dy) {
- currentTransform().translate(dx, dy);
-}
-
-void OpenGLRenderer::rotate(float degrees) {
- currentTransform().rotate(degrees, 0.0f, 0.0f, 1.0f);
-}
-
-void OpenGLRenderer::scale(float sx, float sy) {
- currentTransform().scale(sx, sy, 1.0f);
-}
-
-void OpenGLRenderer::skew(float sx, float sy) {
- currentTransform().skew(sx, sy);
-}
-
-void OpenGLRenderer::setMatrix(SkMatrix* matrix) {
- if (matrix) {
- currentTransform().load(*matrix);
- } else {
- currentTransform().loadIdentity();
- }
-}
-
-bool OpenGLRenderer::hasRectToRectTransform() {
- return CC_LIKELY(currentTransform().rectToRect());
-}
-
-void OpenGLRenderer::getMatrix(SkMatrix* matrix) {
- currentTransform().copyTo(*matrix);
-}
-
-void OpenGLRenderer::concatMatrix(SkMatrix* matrix) {
- SkMatrix transform;
- currentTransform().copyTo(transform);
- transform.preConcat(*matrix);
- currentTransform().load(transform);
-}
-
-///////////////////////////////////////////////////////////////////////////////
// Clipping
///////////////////////////////////////////////////////////////////////////////
void OpenGLRenderer::setScissorFromClip() {
- Rect clip(*mSnapshot->clipRect);
+ Rect clip(*currentClipRect());
clip.snapToPixelBoundaries();
- if (mCaches.setScissor(clip.left, mSnapshot->height - clip.bottom,
+ if (mCaches.setScissor(clip.left, getViewportHeight() - clip.bottom,
clip.getWidth(), clip.getHeight())) {
mDirtyClip = false;
}
@@ -1576,7 +1462,7 @@ void OpenGLRenderer::ensureStencilBuffer() {
// cannot attach a stencil buffer to fbo0 dynamically. Let's
// just hope we have one when hasLayer() returns false.
if (hasLayer()) {
- attachStencilBufferToLayer(mSnapshot->layer);
+ attachStencilBufferToLayer(currentSnapshot()->layer);
}
}
@@ -1598,7 +1484,9 @@ void OpenGLRenderer::attachStencilBufferToLayer(Layer* layer) {
void OpenGLRenderer::setStencilFromClip() {
if (!mCaches.debugOverdraw) {
- if (!mSnapshot->clipRegion->isEmpty()) {
+ if (!currentSnapshot()->clipRegion->isEmpty()) {
+ EVENT_LOGD("setStencilFromClip - enabling");
+
// NOTE: The order here is important, we must set dirtyClip to false
// before any draw call to avoid calling back into this method
mDirtyClip = false;
@@ -1607,7 +1495,7 @@ void OpenGLRenderer::setStencilFromClip() {
mCaches.stencil.enableWrite();
- // Clear the stencil but first make sure we restrict drawing
+ // Clear and update the stencil, but first make sure we restrict drawing
// to the region's bounds
bool resetScissor = mCaches.enableScissor();
if (resetScissor) {
@@ -1615,7 +1503,14 @@ void OpenGLRenderer::setStencilFromClip() {
setScissorFromClip();
}
mCaches.stencil.clear();
- if (resetScissor) mCaches.disableScissor();
+
+ // stash and disable the outline clip state, since stencil doesn't account for outline
+ bool storedSkipOutlineClip = mSkipOutlineClip;
+ mSkipOutlineClip = true;
+
+ SkPaint paint;
+ paint.setColor(SK_ColorBLACK);
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
// NOTE: We could use the region contour path to generate a smaller mesh
// Since we are using the stencil we could use the red book path
@@ -1623,149 +1518,90 @@ void OpenGLRenderer::setStencilFromClip() {
// The last parameter is important: we are not drawing in the color buffer
// so we don't want to dirty the current layer, if any
- drawRegionRects(*mSnapshot->clipRegion, 0xff000000, SkXfermode::kSrc_Mode, false);
+ drawRegionRects(*(currentSnapshot()->clipRegion), paint, false);
+ if (resetScissor) mCaches.disableScissor();
+ mSkipOutlineClip = storedSkipOutlineClip;
mCaches.stencil.enableTest();
// Draw the region used to generate the stencil if the appropriate debug
// mode is enabled
if (mCaches.debugStencilClip == Caches::kStencilShowRegion) {
- drawRegionRects(*mSnapshot->clipRegion, 0x7f0000ff, SkXfermode::kSrcOver_Mode);
+ paint.setColor(0x7f0000ff);
+ paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
+ drawRegionRects(*(currentSnapshot()->clipRegion), paint);
}
} else {
+ EVENT_LOGD("setStencilFromClip - disabling");
mCaches.stencil.disable();
}
}
}
-const Rect& OpenGLRenderer::getClipBounds() {
- return mSnapshot->getLocalClip();
-}
-
-bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, float bottom,
- bool snapOut, bool* clipRequired) {
- if (mSnapshot->isIgnored() || bottom <= top || right <= left) {
- return true;
- }
-
- Rect r(left, top, right, bottom);
- currentTransform().mapRect(r);
- r.snapGeometryToPixelBoundaries(snapOut);
-
- Rect clipRect(*mSnapshot->clipRect);
- clipRect.snapToPixelBoundaries();
-
- if (!clipRect.intersects(r)) return true;
-
- if (clipRequired) *clipRequired = !clipRect.contains(r);
- return false;
-}
-
-bool OpenGLRenderer::quickRejectPreStroke(float left, float top, float right, float bottom,
- SkPaint* paint) {
- // AA geometry will likely have a ramp around it (not accounted for in local bounds). Snap out
- // the final mapped rect to ensure correct clipping behavior for the ramp.
- bool snapOut = paint->isAntiAlias();
+/**
+ * Returns false and sets scissor enable based upon bounds if drawing won't be clipped out.
+ *
+ * @param paint if not null, the bounds will be expanded to account for stroke depending on paint
+ * style, and tessellated AA ramp
+ */
+bool OpenGLRenderer::quickRejectSetupScissor(float left, float top, float right, float bottom,
+ const SkPaint* paint) {
+ bool snapOut = paint && paint->isAntiAlias();
- if (paint->getStyle() != SkPaint::kFill_Style) {
+ if (paint && paint->getStyle() != SkPaint::kFill_Style) {
float outset = paint->getStrokeWidth() * 0.5f;
- return quickReject(left - outset, top - outset, right + outset, bottom + outset, snapOut);
- } else {
- return quickReject(left, top, right, bottom, snapOut);
+ left -= outset;
+ top -= outset;
+ right += outset;
+ bottom += outset;
}
-}
-bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom, bool snapOut) {
bool clipRequired = false;
- if (quickRejectNoScissor(left, top, right, bottom, snapOut, &clipRequired)) {
+ bool roundRectClipRequired = false;
+ if (calculateQuickRejectForScissor(left, top, right, bottom,
+ &clipRequired, &roundRectClipRequired, snapOut)) {
return true;
}
- if (!isDeferred()) {
- mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired);
- }
+ // not quick rejected, so enable the scissor if clipRequired
+ mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired);
+ mSkipOutlineClip = !roundRectClipRequired;
return false;
}
void OpenGLRenderer::debugClip() {
#if DEBUG_CLIP_REGIONS
- if (!isDeferred() && !mSnapshot->clipRegion->isEmpty()) {
- drawRegionRects(*mSnapshot->clipRegion, 0x7f00ff00, SkXfermode::kSrcOver_Mode);
- }
-#endif
-}
-
-bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
- if (CC_LIKELY(currentTransform().rectToRect())) {
- bool clipped = mSnapshot->clip(left, top, right, bottom, op);
- if (clipped) {
- dirtyClip();
- }
- return !mSnapshot->clipRect->isEmpty();
- }
-
- SkPath path;
- path.addRect(left, top, right, bottom);
-
- return OpenGLRenderer::clipPath(&path, op);
-}
-
-bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) {
- SkMatrix transform;
- currentTransform().copyTo(transform);
-
- SkPath transformed;
- path->transform(transform, &transformed);
-
- SkRegion clip;
- if (!mSnapshot->previous->clipRegion->isEmpty()) {
- clip.setRegion(*mSnapshot->previous->clipRegion);
- } else {
- if (mSnapshot->previous == mFirstSnapshot) {
- clip.setRect(0, 0, mWidth, mHeight);
- } else {
- Rect* bounds = mSnapshot->previous->clipRect;
- clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom);
- }
- }
-
- SkRegion region;
- region.setPath(transformed, clip);
-
- bool clipped = mSnapshot->clipRegionTransformed(region, op);
- if (clipped) {
- dirtyClip();
- }
- return !mSnapshot->clipRect->isEmpty();
-}
+ if (!currentSnapshot()->clipRegion->isEmpty()) {
+ SkPaint paint;
+ paint.setColor(0x7f00ff00);
+ drawRegionRects(*(currentSnapshot()->clipRegion, paint);
-bool OpenGLRenderer::clipRegion(SkRegion* region, SkRegion::Op op) {
- bool clipped = mSnapshot->clipRegionTransformed(*region, op);
- if (clipped) {
- dirtyClip();
}
- return !mSnapshot->clipRect->isEmpty();
-}
-
-Rect* OpenGLRenderer::getClipRect() {
- return mSnapshot->clipRect;
+#endif
}
///////////////////////////////////////////////////////////////////////////////
// Drawing commands
///////////////////////////////////////////////////////////////////////////////
-void OpenGLRenderer::setupDraw(bool clear) {
- // TODO: It would be best if we could do this before quickReject()
+void OpenGLRenderer::setupDraw(bool clearLayer) {
+ // TODO: It would be best if we could do this before quickRejectSetupScissor()
// changes the scissor test state
- if (clear) clearLayerRegions();
+ if (clearLayer) clearLayerRegions();
// Make sure setScissor & setStencil happen at the beginning of
// this method
if (mDirtyClip) {
if (mCaches.scissorEnabled) {
setScissorFromClip();
}
- setStencilFromClip();
+
+ if (clearLayer) {
+ setStencilFromClip();
+ } else {
+ // While clearing layer, force disable stencil buffer, since
+ // it's invalid to stencil-clip *during* the layer clear
+ mCaches.stencil.disable();
+ }
}
mDescription.reset();
@@ -1804,8 +1640,9 @@ void OpenGLRenderer::setupDrawNoTexture() {
mCaches.disableTexCoordsVertexArray();
}
-void OpenGLRenderer::setupDrawAA() {
- mDescription.isAA = true;
+void OpenGLRenderer::setupDrawVertexAlpha(bool useShadowAlphaInterp) {
+ mDescription.hasVertexAlpha = true;
+ mDescription.useShadowAlphaInterp = useShadowAlphaInterp;
}
void OpenGLRenderer::setupDrawColor(int color, int alpha) {
@@ -1814,7 +1651,7 @@ void OpenGLRenderer::setupDrawColor(int color, int alpha) {
mColorG = mColorA * ((color >> 8) & 0xFF) / 255.0f;
mColorB = mColorA * ((color ) & 0xFF) / 255.0f;
mColorSet = true;
- mSetShaderColor = mDescription.setColor(mColorR, mColorG, mColorB, mColorA);
+ mSetShaderColor = mDescription.setColorModulate(mColorA);
}
void OpenGLRenderer::setupDrawAlpha8Color(int color, int alpha) {
@@ -1823,7 +1660,7 @@ void OpenGLRenderer::setupDrawAlpha8Color(int color, int alpha) {
mColorG = mColorA * ((color >> 8) & 0xFF) / 255.0f;
mColorB = mColorA * ((color ) & 0xFF) / 255.0f;
mColorSet = true;
- mSetShaderColor = mDescription.setAlpha8Color(mColorR, mColorG, mColorB, mColorA);
+ mSetShaderColor = mDescription.setAlpha8ColorModulate(mColorR, mColorG, mColorB, mColorA);
}
void OpenGLRenderer::setupDrawTextGamma(const SkPaint* paint) {
@@ -1836,18 +1673,26 @@ void OpenGLRenderer::setupDrawColor(float r, float g, float b, float a) {
mColorG = g;
mColorB = b;
mColorSet = true;
- mSetShaderColor = mDescription.setColor(r, g, b, a);
+ mSetShaderColor = mDescription.setColorModulate(a);
}
-void OpenGLRenderer::setupDrawShader() {
- if (mDrawModifiers.mShader) {
- mDrawModifiers.mShader->describe(mDescription, mExtensions);
+void OpenGLRenderer::setupDrawShader(const SkShader* shader) {
+ if (shader != NULL) {
+ SkiaShader::describe(&mCaches, mDescription, mExtensions, *shader);
}
}
-void OpenGLRenderer::setupDrawColorFilter() {
- if (mDrawModifiers.mColorFilter) {
- mDrawModifiers.mColorFilter->describe(mDescription, mExtensions);
+void OpenGLRenderer::setupDrawColorFilter(const SkColorFilter* filter) {
+ if (filter == NULL) {
+ return;
+ }
+
+ SkXfermode::Mode mode;
+ if (filter->asColorMode(NULL, &mode)) {
+ mDescription.colorOp = ProgramDescription::kColorBlend;
+ mDescription.colorMode = mode;
+ } else if (filter->asColorMatrix(NULL)) {
+ mDescription.colorOp = ProgramDescription::kColorMatrix;
}
}
@@ -1859,71 +1704,76 @@ void OpenGLRenderer::accountForClear(SkXfermode::Mode mode) {
}
}
-void OpenGLRenderer::setupDrawBlending(SkXfermode::Mode mode, bool swapSrcDst) {
+static bool isBlendedColorFilter(const SkColorFilter* filter) {
+ if (filter == NULL) {
+ return false;
+ }
+ return (filter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag) == 0;
+}
+
+void OpenGLRenderer::setupDrawBlending(const Layer* layer, bool swapSrcDst) {
+ SkXfermode::Mode mode = layer->getMode();
// When the blending mode is kClear_Mode, we need to use a modulate color
// argb=1,0,0,0
accountForClear(mode);
- bool blend = (mColorSet && mColorA < 1.0f) ||
- (mDrawModifiers.mShader && mDrawModifiers.mShader->blend());
+ // TODO: check shader blending, once we have shader drawing support for layers.
+ bool blend = layer->isBlend() || getLayerAlpha(layer) < 1.0f ||
+ (mColorSet && mColorA < 1.0f) || isBlendedColorFilter(layer->getColorFilter());
chooseBlending(blend, mode, mDescription, swapSrcDst);
}
-void OpenGLRenderer::setupDrawBlending(bool blend, SkXfermode::Mode mode, bool swapSrcDst) {
+void OpenGLRenderer::setupDrawBlending(const SkPaint* paint, bool blend, bool swapSrcDst) {
+ SkXfermode::Mode mode = getXfermodeDirect(paint);
// When the blending mode is kClear_Mode, we need to use a modulate color
// argb=1,0,0,0
accountForClear(mode);
blend |= (mColorSet && mColorA < 1.0f) ||
- (mDrawModifiers.mShader && mDrawModifiers.mShader->blend()) ||
- (mDrawModifiers.mColorFilter && mDrawModifiers.mColorFilter->blend());
+ (getShader(paint) && !getShader(paint)->isOpaque()) ||
+ isBlendedColorFilter(getColorFilter(paint));
chooseBlending(blend, mode, mDescription, swapSrcDst);
}
void OpenGLRenderer::setupDrawProgram() {
useProgram(mCaches.programCache.get(mDescription));
+ if (mDescription.hasRoundRectClip) {
+ // TODO: avoid doing this repeatedly, stashing state pointer in program
+ const RoundRectClipState* state = mSnapshot->roundRectClipState;
+ const Rect& innerRect = state->innerRect;
+ glUniform4f(mCaches.currentProgram->getUniform("roundRectInnerRectLTRB"),
+ innerRect.left, innerRect.top,
+ innerRect.right, innerRect.bottom);
+ glUniform1f(mCaches.currentProgram->getUniform("roundRectRadius"),
+ state->radius);
+ glUniformMatrix4fv(mCaches.currentProgram->getUniform("roundRectInvTransform"),
+ 1, GL_FALSE, &state->matrix.data[0]);
+ }
}
void OpenGLRenderer::setupDrawDirtyRegionsDisabled() {
mTrackDirtyRegions = false;
}
-void OpenGLRenderer::setupDrawModelViewTranslate(float left, float top, float right, float bottom,
- bool ignoreTransform) {
- mModelView.loadTranslate(left, top, 0.0f);
- if (!ignoreTransform) {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, currentTransform());
- if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom, currentTransform());
- } else {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, mat4::identity());
- if (mTrackDirtyRegions) dirtyLayer(left, top, right, bottom);
+void OpenGLRenderer::setupDrawModelView(ModelViewMode mode, bool offset,
+ float left, float top, float right, float bottom, bool ignoreTransform) {
+ mModelViewMatrix.loadTranslate(left, top, 0.0f);
+ if (mode == kModelViewMode_TranslateAndScale) {
+ mModelViewMatrix.scale(right - left, bottom - top, 1.0f);
}
-}
-void OpenGLRenderer::setupDrawModelViewIdentity(bool offset) {
- mCaches.currentProgram->set(mOrthoMatrix, mat4::identity(), currentTransform(), offset);
-}
-
-void OpenGLRenderer::setupDrawModelView(float left, float top, float right, float bottom,
- bool ignoreTransform, bool ignoreModelView) {
- if (!ignoreModelView) {
- mModelView.loadTranslate(left, top, 0.0f);
- mModelView.scale(right - left, bottom - top, 1.0f);
- } else {
- mModelView.loadIdentity();
- }
bool dirty = right - left > 0.0f && bottom - top > 0.0f;
- if (!ignoreTransform) {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, currentTransform());
- if (mTrackDirtyRegions && dirty) {
- dirtyLayer(left, top, right, bottom, currentTransform());
+ const Matrix4& transformMatrix = ignoreTransform ? Matrix4::identity() : *currentTransform();
+ mCaches.currentProgram->set(mSnapshot->getOrthoMatrix(), mModelViewMatrix, transformMatrix, offset);
+ if (dirty && mTrackDirtyRegions) {
+ if (!ignoreTransform) {
+ dirtyLayer(left, top, right, bottom, *currentTransform());
+ } else {
+ dirtyLayer(left, top, right, bottom);
}
- } else {
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, mat4::identity());
- if (mTrackDirtyRegions && dirty) dirtyLayer(left, top, right, bottom);
}
}
-void OpenGLRenderer::setupDrawColorUniforms() {
- if ((mColorSet && !mDrawModifiers.mShader) || (mDrawModifiers.mShader && mSetShaderColor)) {
+void OpenGLRenderer::setupDrawColorUniforms(bool hasShader) {
+ if ((mColorSet && !hasShader) || (hasShader && mSetShaderColor)) {
mCaches.currentProgram->setColor(mColorR, mColorG, mColorB, mColorA);
}
}
@@ -1934,27 +1784,65 @@ void OpenGLRenderer::setupDrawPureColorUniforms() {
}
}
-void OpenGLRenderer::setupDrawShaderUniforms(bool ignoreTransform) {
- if (mDrawModifiers.mShader) {
- if (ignoreTransform) {
- mModelView.loadInverse(currentTransform());
- }
- mDrawModifiers.mShader->setupProgram(mCaches.currentProgram,
- mModelView, *mSnapshot, &mTextureUnit);
+void OpenGLRenderer::setupDrawShaderUniforms(const SkShader* shader, bool ignoreTransform) {
+ if (shader == NULL) {
+ return;
}
-}
-void OpenGLRenderer::setupDrawShaderIdentityUniforms() {
- if (mDrawModifiers.mShader) {
- mDrawModifiers.mShader->setupProgram(mCaches.currentProgram,
- mat4::identity(), *mSnapshot, &mTextureUnit);
+ if (ignoreTransform) {
+ // if ignoreTransform=true was passed to setupDrawModelView, undo currentTransform()
+ // because it was built into modelView / the geometry, and the description needs to
+ // compensate.
+ mat4 modelViewWithoutTransform;
+ modelViewWithoutTransform.loadInverse(*currentTransform());
+ modelViewWithoutTransform.multiply(mModelViewMatrix);
+ mModelViewMatrix.load(modelViewWithoutTransform);
}
+
+ SkiaShader::setupProgram(&mCaches, mModelViewMatrix, &mTextureUnit, mExtensions, *shader);
}
-void OpenGLRenderer::setupDrawColorFilterUniforms() {
- if (mDrawModifiers.mColorFilter) {
- mDrawModifiers.mColorFilter->setupProgram(mCaches.currentProgram);
+void OpenGLRenderer::setupDrawColorFilterUniforms(const SkColorFilter* filter) {
+ if (NULL == filter) {
+ return;
+ }
+
+ SkColor color;
+ SkXfermode::Mode mode;
+ if (filter->asColorMode(&color, &mode)) {
+ const int alpha = SkColorGetA(color);
+ const GLfloat a = alpha / 255.0f;
+ const GLfloat r = a * SkColorGetR(color) / 255.0f;
+ const GLfloat g = a * SkColorGetG(color) / 255.0f;
+ const GLfloat b = a * SkColorGetB(color) / 255.0f;
+ glUniform4f(mCaches.currentProgram->getUniform("colorBlend"), r, g, b, a);
+ return;
+ }
+
+ SkScalar srcColorMatrix[20];
+ if (filter->asColorMatrix(srcColorMatrix)) {
+
+ float colorMatrix[16];
+ memcpy(colorMatrix, srcColorMatrix, 4 * sizeof(float));
+ memcpy(&colorMatrix[4], &srcColorMatrix[5], 4 * sizeof(float));
+ memcpy(&colorMatrix[8], &srcColorMatrix[10], 4 * sizeof(float));
+ memcpy(&colorMatrix[12], &srcColorMatrix[15], 4 * sizeof(float));
+
+ // Skia uses the range [0..255] for the addition vector, but we need
+ // the [0..1] range to apply the vector in GLSL
+ float colorVector[4];
+ colorVector[0] = srcColorMatrix[4] / 255.0f;
+ colorVector[1] = srcColorMatrix[9] / 255.0f;
+ colorVector[2] = srcColorMatrix[14] / 255.0f;
+ colorVector[3] = srcColorMatrix[19] / 255.0f;
+
+ glUniformMatrix4fv(mCaches.currentProgram->getUniform("colorMatrix"), 1,
+ GL_FALSE, colorMatrix);
+ glUniform4fv(mCaches.currentProgram->getUniform("colorMatrixVector"), 1, colorVector);
+ return;
}
+
+ // it is an error if we ever get here
}
void OpenGLRenderer::setupDrawTextGammaUniforms() {
@@ -1988,7 +1876,8 @@ void OpenGLRenderer::setupDrawTextureTransformUniforms(mat4& transform) {
GL_FALSE, &transform.data[0]);
}
-void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) {
+void OpenGLRenderer::setupDrawMesh(const GLvoid* vertices,
+ const GLvoid* texCoords, GLuint vbo) {
bool force = false;
if (!vertices || vbo) {
force = mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo);
@@ -2004,7 +1893,8 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint v
mCaches.unbindIndicesBuffer();
}
-void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLvoid* colors) {
+void OpenGLRenderer::setupDrawMesh(const GLvoid* vertices,
+ const GLvoid* texCoords, const GLvoid* colors) {
bool force = mCaches.unbindMeshBuffer();
GLsizei stride = sizeof(ColorTextureVertex);
@@ -2021,7 +1911,8 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLvoid*
mCaches.unbindIndicesBuffer();
}
-void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) {
+void OpenGLRenderer::setupDrawMeshIndices(const GLvoid* vertices,
+ const GLvoid* texCoords, GLuint vbo) {
bool force = false;
// If vbo is != 0 we want to treat the vertices parameter as an offset inside
// a VBO. However, if vertices is set to NULL and vbo == 0 then we want to
@@ -2031,7 +1922,7 @@ void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, G
} else {
force = mCaches.unbindMeshBuffer();
}
- mCaches.bindIndicesBuffer();
+ mCaches.bindQuadIndicesBuffer();
mCaches.bindPositionVertexPointer(force, vertices);
if (mCaches.currentProgram->texCoords >= 0) {
@@ -2041,7 +1932,7 @@ void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, G
void OpenGLRenderer::setupDrawIndexedVertices(GLvoid* vertices) {
bool force = mCaches.unbindMeshBuffer();
- mCaches.bindIndicesBuffer();
+ mCaches.bindQuadIndicesBuffer();
mCaches.bindPositionVertexPointer(force, vertices, gVertexStride);
}
@@ -2049,28 +1940,29 @@ void OpenGLRenderer::setupDrawIndexedVertices(GLvoid* vertices) {
// Drawing
///////////////////////////////////////////////////////////////////////////////
-status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty,
- int32_t replayFlags) {
+status_t OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags) {
status_t status;
// All the usual checks and setup operations (quickReject, setupDraw, etc.)
// will be performed by the display list itself
- if (displayList && displayList->isRenderable()) {
+ if (renderNode && renderNode->isRenderable()) {
+ // compute 3d ordering
+ renderNode->computeOrdering();
if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
status = startFrame();
ReplayStateStruct replayStruct(*this, dirty, replayFlags);
- displayList->replay(replayStruct, 0);
+ renderNode->replay(replayStruct, 0);
return status | replayStruct.mDrawGlStatus;
}
bool avoidOverdraw = !mCaches.debugOverdraw && !mCountOverdraw; // shh, don't tell devs!
- DeferredDisplayList deferredList(*(mSnapshot->clipRect), avoidOverdraw);
+ DeferredDisplayList deferredList(*currentClipRect(), avoidOverdraw);
DeferStateStruct deferStruct(deferredList, *this, replayFlags);
- displayList->defer(deferStruct, 0);
+ renderNode->defer(deferStruct, 0);
flushLayers();
status = startFrame();
- return status | deferredList.flush(*this, dirty);
+ return deferredList.flush(*this, dirty) | status;
}
// Even if there is no drawing command(Ex: invisible),
@@ -2078,17 +1970,7 @@ status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty,
return startFrame();
}
-void OpenGLRenderer::outputDisplayList(DisplayList* displayList) {
- if (displayList) {
- displayList->output(1);
- }
-}
-
-void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, SkPaint* paint) {
- int alpha;
- SkXfermode::Mode mode;
- getAlphaAndMode(paint, &alpha, &mode);
-
+void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, const SkPaint* paint) {
int color = paint != NULL ? paint->getColor() : 0;
float x = left;
@@ -2097,20 +1979,20 @@ void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, Sk
texture->setWrap(GL_CLAMP_TO_EDGE, true);
bool ignoreTransform = false;
- if (currentTransform().isPureTranslate()) {
- x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
- y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
+ if (currentTransform()->isPureTranslate()) {
+ x = (int) floorf(left + currentTransform()->getTranslateX() + 0.5f);
+ y = (int) floorf(top + currentTransform()->getTranslateY() + 0.5f);
ignoreTransform = true;
texture->setFilter(GL_NEAREST, true);
} else {
- texture->setFilter(FILTER(paint), true);
+ texture->setFilter(getFilter(paint), true);
}
// No need to check for a UV mapper on the texture object, only ARGB_8888
// bitmaps get packed in the atlas
drawAlpha8TextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
- paint != NULL, color, alpha, mode, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset,
+ paint, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset,
GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform);
}
@@ -2119,44 +2001,37 @@ void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, Sk
* will not set the scissor enable or dirty the current layer, if any.
* The caller is responsible for properly dirtying the current layer.
*/
-status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, AssetAtlas::Entry* entry, int bitmapCount,
- TextureVertex* vertices, bool pureTranslate, const Rect& bounds, SkPaint* paint) {
+status_t OpenGLRenderer::drawBitmaps(const SkBitmap* bitmap, AssetAtlas::Entry* entry,
+ int bitmapCount, TextureVertex* vertices, bool pureTranslate,
+ const Rect& bounds, const SkPaint* paint) {
mCaches.activeTexture(0);
Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap);
if (!texture) return DrawGlInfo::kStatusDone;
const AutoTexture autoCleanup(texture);
- int alpha;
- SkXfermode::Mode mode;
- getAlphaAndMode(paint, &alpha, &mode);
-
texture->setWrap(GL_CLAMP_TO_EDGE, true);
- texture->setFilter(pureTranslate ? GL_NEAREST : FILTER(paint), true);
+ texture->setFilter(pureTranslate ? GL_NEAREST : getFilter(paint), true);
const float x = (int) floorf(bounds.left + 0.5f);
const float y = (int) floorf(bounds.top + 0.5f);
- if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {
- int color = paint != NULL ? paint->getColor() : 0;
+ if (CC_UNLIKELY(bitmap->colorType() == kAlpha_8_SkColorType)) {
drawAlpha8TextureMesh(x, y, x + bounds.getWidth(), y + bounds.getHeight(),
- texture->id, paint != NULL, color, alpha, mode,
- &vertices[0].position[0], &vertices[0].texture[0],
- GL_TRIANGLES, bitmapCount * 6, true, true, false);
+ texture->id, paint, &vertices[0].x, &vertices[0].u,
+ GL_TRIANGLES, bitmapCount * 6, true,
+ kModelViewMode_Translate, false);
} else {
drawTextureMesh(x, y, x + bounds.getWidth(), y + bounds.getHeight(),
- texture->id, alpha / 255.0f, mode, texture->blend,
- &vertices[0].position[0], &vertices[0].texture[0],
- GL_TRIANGLES, bitmapCount * 6, false, true, 0, true, false);
+ texture->id, paint, texture->blend, &vertices[0].x, &vertices[0].u,
+ GL_TRIANGLES, bitmapCount * 6, false, true, 0,
+ kModelViewMode_Translate, false);
}
return DrawGlInfo::kStatusDrew;
}
-status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint) {
- const float right = left + bitmap->width();
- const float bottom = top + bitmap->height();
-
- if (quickReject(left, top, right, bottom)) {
+status_t OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) {
+ if (quickRejectSetupScissor(0, 0, bitmap->width(), bitmap->height())) {
return DrawGlInfo::kStatusDone;
}
@@ -2165,48 +2040,17 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkP
if (!texture) return DrawGlInfo::kStatusDone;
const AutoTexture autoCleanup(texture);
- if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {
- drawAlphaBitmap(texture, left, top, paint);
+ if (CC_UNLIKELY(bitmap->colorType() == kAlpha_8_SkColorType)) {
+ drawAlphaBitmap(texture, 0, 0, paint);
} else {
- drawTextureRect(left, top, right, bottom, texture, paint);
+ drawTextureRect(0, 0, bitmap->width(), bitmap->height(), texture, paint);
}
return DrawGlInfo::kStatusDrew;
}
-status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint) {
- Rect r(0.0f, 0.0f, bitmap->width(), bitmap->height());
- const mat4 transform(*matrix);
- transform.mapRect(r);
-
- if (quickReject(r.left, r.top, r.right, r.bottom)) {
- return DrawGlInfo::kStatusDone;
- }
-
- mCaches.activeTexture(0);
- Texture* texture = getTexture(bitmap);
- if (!texture) return DrawGlInfo::kStatusDone;
- const AutoTexture autoCleanup(texture);
-
- // This could be done in a cheaper way, all we need is pass the matrix
- // to the vertex shader. The save/restore is a bit overkill.
- save(SkCanvas::kMatrix_SaveFlag);
- concatMatrix(matrix);
- if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {
- drawAlphaBitmap(texture, 0.0f, 0.0f, paint);
- } else {
- drawTextureRect(0.0f, 0.0f, bitmap->width(), bitmap->height(), texture, paint);
- }
- restore();
-
- return DrawGlInfo::kStatusDrew;
-}
-
-status_t OpenGLRenderer::drawBitmapData(SkBitmap* bitmap, float left, float top, SkPaint* paint) {
- const float right = left + bitmap->width();
- const float bottom = top + bitmap->height();
-
- if (quickReject(left, top, right, bottom)) {
+status_t OpenGLRenderer::drawBitmapData(const SkBitmap* bitmap, const SkPaint* paint) {
+ if (quickRejectSetupScissor(0, 0, bitmap->width(), bitmap->height())) {
return DrawGlInfo::kStatusDone;
}
@@ -2214,18 +2058,18 @@ status_t OpenGLRenderer::drawBitmapData(SkBitmap* bitmap, float left, float top,
Texture* texture = mCaches.textureCache.getTransient(bitmap);
const AutoTexture autoCleanup(texture);
- if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {
- drawAlphaBitmap(texture, left, top, paint);
+ if (CC_UNLIKELY(bitmap->colorType() == kAlpha_8_SkColorType)) {
+ drawAlphaBitmap(texture, 0, 0, paint);
} else {
- drawTextureRect(left, top, right, bottom, texture, paint);
+ drawTextureRect(0, 0, bitmap->width(), bitmap->height(), texture, paint);
}
return DrawGlInfo::kStatusDrew;
}
-status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight,
- float* vertices, int* colors, SkPaint* paint) {
- if (!vertices || mSnapshot->isIgnored()) {
+status_t OpenGLRenderer::drawBitmapMesh(const SkBitmap* bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint) {
+ if (!vertices || currentSnapshot()->isIgnored()) {
return DrawGlInfo::kStatusDone;
}
@@ -2239,14 +2083,16 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes
const uint32_t count = meshWidth * meshHeight * 6;
- ColorTextureVertex mesh[count];
- ColorTextureVertex* vertex = mesh;
+ Vector<ColorTextureVertex> mesh; // TODO: use C++11 unique_ptr
+ mesh.setCapacity(count);
+ ColorTextureVertex* vertex = mesh.editArray();
bool cleanupColors = false;
if (!colors) {
uint32_t colorsCount = (meshWidth + 1) * (meshHeight + 1);
- colors = new int[colorsCount];
- memset(colors, 0xff, colorsCount * sizeof(int));
+ int* newColors = new int[colorsCount];
+ memset(newColors, 0xff, colorsCount * sizeof(int));
+ colors = newColors;
cleanupColors = true;
}
@@ -2289,7 +2135,7 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes
}
}
- if (quickReject(left, top, right, bottom)) {
+ if (quickRejectSetupScissor(left, top, right, bottom)) {
if (cleanupColors) delete[] colors;
return DrawGlInfo::kStatusDone;
}
@@ -2304,7 +2150,7 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes
const AutoTexture autoCleanup(texture);
texture->setWrap(GL_CLAMP_TO_EDGE, true);
- texture->setFilter(FILTER(paint), true);
+ texture->setFilter(getFilter(paint), true);
int alpha;
SkXfermode::Mode mode;
@@ -2313,21 +2159,21 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes
float a = alpha / 255.0f;
if (hasLayer()) {
- dirtyLayer(left, top, right, bottom, currentTransform());
+ dirtyLayer(left, top, right, bottom, *currentTransform());
}
setupDraw();
setupDrawWithTextureAndColor();
setupDrawColor(a, a, a, a);
- setupDrawColorFilter();
- setupDrawBlending(true, mode, false);
+ setupDrawColorFilter(getColorFilter(paint));
+ setupDrawBlending(paint, true);
setupDrawProgram();
setupDrawDirtyRegionsDisabled();
- setupDrawModelView(0.0f, 0.0f, 1.0f, 1.0f, false);
+ setupDrawModelView(kModelViewMode_TranslateAndScale, false, 0.0f, 0.0f, 1.0f, 1.0f);
setupDrawTexture(texture->id);
setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawMesh(&mesh[0].position[0], &mesh[0].texture[0], &mesh[0].color[0]);
+ setupDrawColorFilterUniforms(getColorFilter(paint));
+ setupDrawMesh(&mesh[0].x, &mesh[0].u, &mesh[0].r);
glDrawArrays(GL_TRIANGLES, 0, count);
@@ -2341,11 +2187,11 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes
return DrawGlInfo::kStatusDrew;
}
-status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
+status_t OpenGLRenderer::drawBitmap(const SkBitmap* bitmap,
float srcLeft, float srcTop, float srcRight, float srcBottom,
float dstLeft, float dstTop, float dstRight, float dstBottom,
- SkPaint* paint) {
- if (quickReject(dstLeft, dstTop, dstRight, dstBottom)) {
+ const SkPaint* paint) {
+ if (quickRejectSetupScissor(dstLeft, dstTop, dstRight, dstBottom)) {
return DrawGlInfo::kStatusDone;
}
@@ -2367,10 +2213,6 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
mCaches.unbindMeshBuffer();
resetDrawTextureTexCoords(u1, v1, u2, v2);
- int alpha;
- SkXfermode::Mode mode;
- getAlphaAndMode(paint, &alpha, &mode);
-
texture->setWrap(GL_CLAMP_TO_EDGE, true);
float scaleX = (dstRight - dstLeft) / (srcRight - srcLeft);
@@ -2380,12 +2222,12 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
// Apply a scale transform on the canvas only when a shader is in use
// Skia handles the ratio between the dst and src rects as a scale factor
// when a shader is set
- bool useScaleTransform = mDrawModifiers.mShader && scaled;
+ bool useScaleTransform = getShader(paint) && scaled;
bool ignoreTransform = false;
- if (CC_LIKELY(currentTransform().isPureTranslate() && !useScaleTransform)) {
- float x = (int) floorf(dstLeft + currentTransform().getTranslateX() + 0.5f);
- float y = (int) floorf(dstTop + currentTransform().getTranslateY() + 0.5f);
+ if (CC_LIKELY(currentTransform()->isPureTranslate() && !useScaleTransform)) {
+ float x = (int) floorf(dstLeft + currentTransform()->getTranslateX() + 0.5f);
+ float y = (int) floorf(dstTop + currentTransform()->getTranslateY() + 0.5f);
dstRight = x + (dstRight - dstLeft);
dstBottom = y + (dstBottom - dstTop);
@@ -2393,10 +2235,10 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
dstLeft = x;
dstTop = y;
- texture->setFilter(scaled ? FILTER(paint) : GL_NEAREST, true);
+ texture->setFilter(scaled ? getFilter(paint) : GL_NEAREST, true);
ignoreTransform = true;
} else {
- texture->setFilter(FILTER(paint), true);
+ texture->setFilter(getFilter(paint), true);
}
if (CC_UNLIKELY(useScaleTransform)) {
@@ -2411,16 +2253,15 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
dstBottom = srcBottom - srcTop;
}
- if (CC_UNLIKELY(bitmap->getConfig() == SkBitmap::kA8_Config)) {
- int color = paint ? paint->getColor() : 0;
+ if (CC_UNLIKELY(bitmap->colorType() == kAlpha_8_SkColorType)) {
drawAlpha8TextureMesh(dstLeft, dstTop, dstRight, dstBottom,
- texture->id, paint != NULL, color, alpha, mode,
- &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+ texture->id, paint,
+ &mMeshVertices[0].x, &mMeshVertices[0].u,
GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform);
} else {
drawTextureMesh(dstLeft, dstTop, dstRight, dstBottom,
- texture->id, alpha / 255.0f, mode, texture->blend,
- &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+ texture->id, paint, texture->blend,
+ &mMeshVertices[0].x, &mMeshVertices[0].u,
GL_TRIANGLE_STRIP, gMeshCount, false, ignoreTransform);
}
@@ -2433,9 +2274,9 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
return DrawGlInfo::kStatusDrew;
}
-status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
- float left, float top, float right, float bottom, SkPaint* paint) {
- if (quickReject(left, top, right, bottom)) {
+status_t OpenGLRenderer::drawPatch(const SkBitmap* bitmap, const Res_png_9patch* patch,
+ float left, float top, float right, float bottom, const SkPaint* paint) {
+ if (quickRejectSetupScissor(left, top, right, bottom)) {
return DrawGlInfo::kStatusDone;
}
@@ -2446,9 +2287,10 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
return drawPatch(bitmap, mesh, entry, left, top, right, bottom, paint);
}
-status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const Patch* mesh, AssetAtlas::Entry* entry,
- float left, float top, float right, float bottom, SkPaint* paint) {
- if (quickReject(left, top, right, bottom)) {
+status_t OpenGLRenderer::drawPatch(const SkBitmap* bitmap, const Patch* mesh,
+ AssetAtlas::Entry* entry, float left, float top, float right, float bottom,
+ const SkPaint* paint) {
+ if (quickRejectSetupScissor(left, top, right, bottom)) {
return DrawGlInfo::kStatusDone;
}
@@ -2461,15 +2303,11 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const Patch* mesh, AssetAtl
texture->setWrap(GL_CLAMP_TO_EDGE, true);
texture->setFilter(GL_LINEAR, true);
- int alpha;
- SkXfermode::Mode mode;
- getAlphaAndMode(paint, &alpha, &mode);
-
- const bool pureTranslate = currentTransform().isPureTranslate();
+ const bool pureTranslate = currentTransform()->isPureTranslate();
// Mark the current layer dirty where we are going to draw the patch
if (hasLayer() && mesh->hasEmptyQuads) {
- const float offsetX = left + currentTransform().getTranslateX();
- const float offsetY = top + currentTransform().getTranslateY();
+ const float offsetX = left + currentTransform()->getTranslateX();
+ const float offsetY = top + currentTransform()->getTranslateY();
const size_t count = mesh->quads.size();
for (size_t i = 0; i < count; i++) {
const Rect& bounds = mesh->quads.itemAt(i);
@@ -2479,27 +2317,26 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const Patch* mesh, AssetAtl
dirtyLayer(x, y, x + bounds.getWidth(), y + bounds.getHeight());
} else {
dirtyLayer(left + bounds.left, top + bounds.top,
- left + bounds.right, top + bounds.bottom, currentTransform());
+ left + bounds.right, top + bounds.bottom, *currentTransform());
}
}
}
+ bool ignoreTransform = false;
if (CC_LIKELY(pureTranslate)) {
- const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
- const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
+ const float x = (int) floorf(left + currentTransform()->getTranslateX() + 0.5f);
+ const float y = (int) floorf(top + currentTransform()->getTranslateY() + 0.5f);
right = x + right - left;
bottom = y + bottom - top;
- drawIndexedTextureMesh(x, y, right, bottom, texture->id, alpha / 255.0f,
- mode, texture->blend, (GLvoid*) mesh->offset, (GLvoid*) mesh->textureOffset,
- GL_TRIANGLES, mesh->indexCount, false, true,
- mCaches.patchCache.getMeshBuffer(), true, !mesh->hasEmptyQuads);
- } else {
- drawIndexedTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f,
- mode, texture->blend, (GLvoid*) mesh->offset, (GLvoid*) mesh->textureOffset,
- GL_TRIANGLES, mesh->indexCount, false, false,
- mCaches.patchCache.getMeshBuffer(), true, !mesh->hasEmptyQuads);
+ left = x;
+ top = y;
+ ignoreTransform = true;
}
+ drawIndexedTextureMesh(left, top, right, bottom, texture->id, paint,
+ texture->blend, (GLvoid*) mesh->offset, (GLvoid*) mesh->textureOffset,
+ GL_TRIANGLES, mesh->indexCount, false, ignoreTransform,
+ mCaches.patchCache.getMeshBuffer(), kModelViewMode_Translate, !mesh->hasEmptyQuads);
}
return DrawGlInfo::kStatusDrew;
@@ -2510,8 +2347,8 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const Patch* mesh, AssetAtl
* will not set the scissor enable or dirty the current layer, if any.
* The caller is responsible for properly dirtying the current layer.
*/
-status_t OpenGLRenderer::drawPatches(SkBitmap* bitmap, AssetAtlas::Entry* entry,
- TextureVertex* vertices, uint32_t indexCount, SkPaint* paint) {
+status_t OpenGLRenderer::drawPatches(const SkBitmap* bitmap, AssetAtlas::Entry* entry,
+ TextureVertex* vertices, uint32_t indexCount, const SkPaint* paint) {
mCaches.activeTexture(0);
Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap);
if (!texture) return DrawGlInfo::kStatusDone;
@@ -2520,58 +2357,67 @@ status_t OpenGLRenderer::drawPatches(SkBitmap* bitmap, AssetAtlas::Entry* entry,
texture->setWrap(GL_CLAMP_TO_EDGE, true);
texture->setFilter(GL_LINEAR, true);
- int alpha;
- SkXfermode::Mode mode;
- getAlphaAndMode(paint, &alpha, &mode);
-
- drawIndexedTextureMesh(0.0f, 0.0f, 1.0f, 1.0f, texture->id, alpha / 255.0f,
- mode, texture->blend, &vertices[0].position[0], &vertices[0].texture[0],
- GL_TRIANGLES, indexCount, false, true, 0, true, false);
+ drawIndexedTextureMesh(0.0f, 0.0f, 1.0f, 1.0f, texture->id, paint,
+ texture->blend, &vertices[0].x, &vertices[0].u,
+ GL_TRIANGLES, indexCount, false, true, 0, kModelViewMode_Translate, false);
return DrawGlInfo::kStatusDrew;
}
-status_t OpenGLRenderer::drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPaint* paint,
- bool useOffset) {
+status_t OpenGLRenderer::drawVertexBuffer(float translateX, float translateY,
+ const VertexBuffer& vertexBuffer, const SkPaint* paint, int displayFlags) {
+ // not missing call to quickReject/dirtyLayer, always done at a higher level
if (!vertexBuffer.getVertexCount()) {
// no vertices to draw
return DrawGlInfo::kStatusDone;
}
+ Rect bounds(vertexBuffer.getBounds());
+ bounds.translate(translateX, translateY);
+ dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform());
+
int color = paint->getColor();
- SkXfermode::Mode mode = getXfermode(paint->getXfermode());
bool isAA = paint->isAntiAlias();
setupDraw();
setupDrawNoTexture();
- if (isAA) setupDrawAA();
+ if (isAA) setupDrawVertexAlpha((displayFlags & kVertexBuffer_ShadowInterp));
setupDrawColor(color, ((color >> 24) & 0xFF) * mSnapshot->alpha);
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(isAA, mode);
+ setupDrawColorFilter(getColorFilter(paint));
+ setupDrawShader(getShader(paint));
+ setupDrawBlending(paint, isAA);
setupDrawProgram();
- setupDrawModelViewIdentity(useOffset);
- setupDrawColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderIdentityUniforms();
+ setupDrawModelView(kModelViewMode_Translate, (displayFlags & kVertexBuffer_Offset),
+ translateX, translateY, 0, 0);
+ setupDrawColorUniforms(getShader(paint));
+ setupDrawColorFilterUniforms(getColorFilter(paint));
+ setupDrawShaderUniforms(getShader(paint));
- void* vertices = vertexBuffer.getBuffer();
+ const void* vertices = vertexBuffer.getBuffer();
bool force = mCaches.unbindMeshBuffer();
mCaches.bindPositionVertexPointer(true, vertices, isAA ? gAlphaVertexStride : gVertexStride);
mCaches.resetTexCoordsVertexPointer();
- mCaches.unbindIndicesBuffer();
int alphaSlot = -1;
if (isAA) {
void* alphaCoords = ((GLbyte*) vertices) + gVertexAlphaOffset;
alphaSlot = mCaches.currentProgram->getAttrib("vtxAlpha");
-
// TODO: avoid enable/disable in back to back uses of the alpha attribute
glEnableVertexAttribArray(alphaSlot);
glVertexAttribPointer(alphaSlot, 1, GL_FLOAT, GL_FALSE, gAlphaVertexStride, alphaCoords);
}
- glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getVertexCount());
+ const VertexBuffer::Mode mode = vertexBuffer.getMode();
+ if (mode == VertexBuffer::kStandard) {
+ mCaches.unbindIndicesBuffer();
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getVertexCount());
+ } else if (mode == VertexBuffer::kOnePolyRingShadow) {
+ mCaches.bindShadowIndicesBuffer();
+ glDrawElements(GL_TRIANGLE_STRIP, ONE_POLY_RING_SHADOW_INDEX_COUNT, GL_UNSIGNED_SHORT, 0);
+ } else if (mode == VertexBuffer::kTwoPolyRingShadow) {
+ mCaches.bindShadowIndicesBuffer();
+ glDrawElements(GL_TRIANGLE_STRIP, TWO_POLY_RING_SHADOW_INDEX_COUNT, GL_UNSIGNED_SHORT, 0);
+ }
if (isAA) {
glDisableVertexAttribArray(alphaSlot);
@@ -2589,17 +2435,10 @@ status_t OpenGLRenderer::drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPa
*
* Doesn't yet support joins, caps, or path effects.
*/
-status_t OpenGLRenderer::drawConvexPath(const SkPath& path, SkPaint* paint) {
+status_t OpenGLRenderer::drawConvexPath(const SkPath& path, const SkPaint* paint) {
VertexBuffer vertexBuffer;
// TODO: try clipping large paths to viewport
- PathTessellator::tessellatePath(path, paint, mSnapshot->transform, vertexBuffer);
-
- if (hasLayer()) {
- SkRect bounds = path.getBounds();
- PathTessellator::expandBoundsForStroke(bounds, paint, false);
- dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, currentTransform());
- }
-
+ PathTessellator::tessellatePath(path, paint, *currentTransform(), vertexBuffer);
return drawVertexBuffer(vertexBuffer, paint);
}
@@ -2614,58 +2453,58 @@ status_t OpenGLRenderer::drawConvexPath(const SkPath& path, SkPaint* paint) {
* TODO: try using a fixed input buffer for non-capped lines as in text rendering. this may reduce
* memory transfer by removing need for degenerate vertices.
*/
-status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
- if (mSnapshot->isIgnored() || count < 4) return DrawGlInfo::kStatusDone;
+status_t OpenGLRenderer::drawLines(const float* points, int count, const SkPaint* paint) {
+ if (currentSnapshot()->isIgnored() || count < 4) return DrawGlInfo::kStatusDone;
count &= ~0x3; // round down to nearest four
VertexBuffer buffer;
- SkRect bounds;
- PathTessellator::tessellateLines(points, count, paint, mSnapshot->transform, bounds, buffer);
+ PathTessellator::tessellateLines(points, count, paint, *currentTransform(), buffer);
+ const Rect& bounds = buffer.getBounds();
- if (quickReject(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom)) {
+ if (quickRejectSetupScissor(bounds.left, bounds.top, bounds.right, bounds.bottom)) {
return DrawGlInfo::kStatusDone;
}
- dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, currentTransform());
-
- bool useOffset = !paint->isAntiAlias();
- return drawVertexBuffer(buffer, paint, useOffset);
+ int displayFlags = paint->isAntiAlias() ? 0 : kVertexBuffer_Offset;
+ return drawVertexBuffer(buffer, paint, displayFlags);
}
-status_t OpenGLRenderer::drawPoints(float* points, int count, SkPaint* paint) {
- if (mSnapshot->isIgnored() || count < 2) return DrawGlInfo::kStatusDone;
+status_t OpenGLRenderer::drawPoints(const float* points, int count, const SkPaint* paint) {
+ if (currentSnapshot()->isIgnored() || count < 2) return DrawGlInfo::kStatusDone;
count &= ~0x1; // round down to nearest two
VertexBuffer buffer;
- SkRect bounds;
- PathTessellator::tessellatePoints(points, count, paint, mSnapshot->transform, bounds, buffer);
+ PathTessellator::tessellatePoints(points, count, paint, *currentTransform(), buffer);
- if (quickReject(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom)) {
+ const Rect& bounds = buffer.getBounds();
+ if (quickRejectSetupScissor(bounds.left, bounds.top, bounds.right, bounds.bottom)) {
return DrawGlInfo::kStatusDone;
}
- dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, currentTransform());
-
- bool useOffset = !paint->isAntiAlias();
- return drawVertexBuffer(buffer, paint, useOffset);
+ int displayFlags = paint->isAntiAlias() ? 0 : kVertexBuffer_Offset;
+ return drawVertexBuffer(buffer, paint, displayFlags);
}
status_t OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
// No need to check against the clip, we fill the clip region
- if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;
+ if (currentSnapshot()->isIgnored()) return DrawGlInfo::kStatusDone;
- Rect& clip(*mSnapshot->clipRect);
+ Rect clip(*currentClipRect());
clip.snapToPixelBoundaries();
- drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color, mode, true);
+ SkPaint paint;
+ paint.setColor(color);
+ paint.setXfermodeMode(mode);
+
+ drawColorRect(clip.left, clip.top, clip.right, clip.bottom, &paint, true);
return DrawGlInfo::kStatusDrew;
}
status_t OpenGLRenderer::drawShape(float left, float top, const PathTexture* texture,
- SkPaint* paint) {
+ const SkPaint* paint) {
if (!texture) return DrawGlInfo::kStatusDone;
const AutoTexture autoCleanup(texture);
@@ -2678,8 +2517,8 @@ status_t OpenGLRenderer::drawShape(float left, float top, const PathTexture* tex
}
status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float bottom,
- float rx, float ry, SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ float rx, float ry, const SkPaint* p) {
+ if (currentSnapshot()->isIgnored() || quickRejectSetupScissor(left, top, right, bottom, p) ||
(p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
@@ -2691,20 +2530,13 @@ status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float
return drawShape(left, top, texture, p);
}
- SkPath path;
- SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
- if (p->getStyle() == SkPaint::kStrokeAndFill_Style) {
- float outset = p->getStrokeWidth() / 2;
- rect.outset(outset, outset);
- rx += outset;
- ry += outset;
- }
- path.addRoundRect(rect, rx, ry);
- return drawConvexPath(path, p);
+ const VertexBuffer* vertexBuffer = mCaches.tessellationCache.getRoundRect(
+ *currentTransform(), *p, right - left, bottom - top, rx, ry);
+ return drawVertexBuffer(left, top, *vertexBuffer, p);
}
-status_t OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(x - radius, y - radius,
+status_t OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p) {
+ if (currentSnapshot()->isIgnored() || quickRejectSetupScissor(x - radius, y - radius,
x + radius, y + radius, p) ||
(p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
@@ -2725,8 +2557,8 @@ status_t OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* p)
}
status_t OpenGLRenderer::drawOval(float left, float top, float right, float bottom,
- SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ const SkPaint* p) {
+ if (currentSnapshot()->isIgnored() || quickRejectSetupScissor(left, top, right, bottom, p) ||
(p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
@@ -2747,16 +2579,12 @@ status_t OpenGLRenderer::drawOval(float left, float top, float right, float bott
}
status_t OpenGLRenderer::drawArc(float left, float top, float right, float bottom,
- float startAngle, float sweepAngle, bool useCenter, SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint* p) {
+ if (currentSnapshot()->isIgnored() || quickRejectSetupScissor(left, top, right, bottom, p) ||
(p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
- if (fabs(sweepAngle) >= 360.0f) {
- return drawOval(left, top, right, bottom, p);
- }
-
// TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180)
if (p->getStyle() != SkPaint::kStroke_Style || p->getPathEffect() != 0 || useCenter) {
mCaches.activeTexture(0);
@@ -2784,8 +2612,9 @@ status_t OpenGLRenderer::drawArc(float left, float top, float right, float botto
// See SkPaintDefaults.h
#define SkPaintDefaults_MiterLimit SkIntToScalar(4)
-status_t OpenGLRenderer::drawRect(float left, float top, float right, float bottom, SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+status_t OpenGLRenderer::drawRect(float left, float top, float right, float bottom,
+ const SkPaint* p) {
+ if (currentSnapshot()->isIgnored() || quickRejectSetupScissor(left, top, right, bottom, p) ||
(p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
@@ -2809,70 +2638,75 @@ status_t OpenGLRenderer::drawRect(float left, float top, float right, float bott
return drawConvexPath(path, p);
}
- if (p->isAntiAlias() && !currentTransform().isSimple()) {
+ if (p->isAntiAlias() && !currentTransform()->isSimple()) {
SkPath path;
path.addRect(left, top, right, bottom);
return drawConvexPath(path, p);
} else {
- drawColorRect(left, top, right, bottom, p->getColor(), getXfermode(p->getXfermode()));
+ drawColorRect(left, top, right, bottom, p);
return DrawGlInfo::kStatusDrew;
}
}
-void OpenGLRenderer::drawTextShadow(SkPaint* paint, const char* text, int bytesCount, int count,
- const float* positions, FontRenderer& fontRenderer, int alpha, SkXfermode::Mode mode,
- float x, float y) {
+void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text,
+ int bytesCount, int count, const float* positions,
+ FontRenderer& fontRenderer, int alpha, float x, float y) {
mCaches.activeTexture(0);
+ TextShadow textShadow;
+ if (!getTextShadow(paint, &textShadow)) {
+ LOG_ALWAYS_FATAL("failed to query shadow attributes");
+ }
+
// NOTE: The drop shadow will not perform gamma correction
// if shader-based correction is enabled
mCaches.dropShadowCache.setFontRenderer(fontRenderer);
const ShadowTexture* shadow = mCaches.dropShadowCache.get(
- paint, text, bytesCount, count, mDrawModifiers.mShadowRadius, positions);
+ paint, text, bytesCount, count, textShadow.radius, positions);
// If the drop shadow exceeds the max texture size or couldn't be
// allocated, skip drawing
if (!shadow) return;
const AutoTexture autoCleanup(shadow);
- const float sx = x - shadow->left + mDrawModifiers.mShadowDx;
- const float sy = y - shadow->top + mDrawModifiers.mShadowDy;
+ const float sx = x - shadow->left + textShadow.dx;
+ const float sy = y - shadow->top + textShadow.dy;
- const int shadowAlpha = ((mDrawModifiers.mShadowColor >> 24) & 0xFF) * mSnapshot->alpha;
- int shadowColor = mDrawModifiers.mShadowColor;
- if (mDrawModifiers.mShader) {
- shadowColor = 0xffffffff;
+ const int shadowAlpha = ((textShadow.color >> 24) & 0xFF) * mSnapshot->alpha;
+ if (getShader(paint)) {
+ textShadow.color = SK_ColorWHITE;
}
setupDraw();
setupDrawWithTexture(true);
- setupDrawAlpha8Color(shadowColor, shadowAlpha < 255 ? shadowAlpha : alpha);
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(true, mode);
+ setupDrawAlpha8Color(textShadow.color, shadowAlpha < 255 ? shadowAlpha : alpha);
+ setupDrawColorFilter(getColorFilter(paint));
+ setupDrawShader(getShader(paint));
+ setupDrawBlending(paint, true);
setupDrawProgram();
- setupDrawModelView(sx, sy, sx + shadow->width, sy + shadow->height);
+ setupDrawModelView(kModelViewMode_TranslateAndScale, false,
+ sx, sy, sx + shadow->width, sy + shadow->height);
setupDrawTexture(shadow->id);
setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderUniforms();
+ setupDrawColorFilterUniforms(getColorFilter(paint));
+ setupDrawShaderUniforms(getShader(paint));
setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
}
bool OpenGLRenderer::canSkipText(const SkPaint* paint) const {
- float alpha = (mDrawModifiers.mHasShadow ? 1.0f : paint->getAlpha()) * mSnapshot->alpha;
+ float alpha = (hasTextShadow(paint) ? 1.0f : paint->getAlpha()) * mSnapshot->alpha;
return alpha == 0.0f && getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
}
status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
- const float* positions, SkPaint* paint) {
- if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
+ const float* positions, const SkPaint* paint) {
+ if (text == NULL || count == 0 || currentSnapshot()->isIgnored() || canSkipText(paint)) {
return DrawGlInfo::kStatusDone;
}
// NOTE: Skia does not support perspective transform on drawPosText yet
- if (!currentTransform().isSimple()) {
+ if (!currentTransform()->isSimple()) {
return DrawGlInfo::kStatusDone;
}
@@ -2880,26 +2714,26 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count
float x = 0.0f;
float y = 0.0f;
- const bool pureTranslate = currentTransform().isPureTranslate();
+ const bool pureTranslate = currentTransform()->isPureTranslate();
if (pureTranslate) {
- x = (int) floorf(x + currentTransform().getTranslateX() + 0.5f);
- y = (int) floorf(y + currentTransform().getTranslateY() + 0.5f);
+ x = (int) floorf(x + currentTransform()->getTranslateX() + 0.5f);
+ y = (int) floorf(y + currentTransform()->getTranslateY() + 0.5f);
}
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, mat4::identity());
+ fontRenderer.setFont(paint, SkMatrix::I());
int alpha;
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
- if (CC_UNLIKELY(mDrawModifiers.mHasShadow)) {
+ if (CC_UNLIKELY(hasTextShadow(paint))) {
drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
- alpha, mode, 0.0f, 0.0f);
+ alpha, 0.0f, 0.0f);
}
// Pick the appropriate texture filtering
- bool linearFilter = currentTransform().changesBounds();
+ bool linearFilter = currentTransform()->changesBounds();
if (pureTranslate && !linearFilter) {
linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
}
@@ -2915,7 +2749,7 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count
positions, hasActiveLayer ? &bounds : NULL, &functor)) {
if (hasActiveLayer) {
if (!pureTranslate) {
- currentTransform().mapRect(bounds);
+ currentTransform()->mapRect(bounds);
}
dirtyLayerUnchecked(bounds, getRegion());
}
@@ -2924,31 +2758,36 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count
return DrawGlInfo::kStatusDrew;
}
-mat4 OpenGLRenderer::findBestFontTransform(const mat4& transform) const {
- mat4 fontTransform;
+bool OpenGLRenderer::findBestFontTransform(const mat4& transform, SkMatrix* outMatrix) const {
if (CC_LIKELY(transform.isPureTranslate())) {
- fontTransform = mat4::identity();
- } else {
- if (CC_UNLIKELY(transform.isPerspective())) {
- fontTransform = mat4::identity();
- } else {
- float sx, sy;
- currentTransform().decomposeScale(sx, sy);
- fontTransform.loadScale(sx, sy, 1.0f);
- }
+ outMatrix->setIdentity();
+ return false;
+ } else if (CC_UNLIKELY(transform.isPerspective())) {
+ outMatrix->setIdentity();
+ return true;
}
- return fontTransform;
+
+ /**
+ * Input is a non-perspective, scaling transform. Generate a scale-only transform,
+ * with values rounded to the nearest int.
+ */
+ float sx, sy;
+ transform.decomposeScale(sx, sy);
+ outMatrix->setScale(
+ roundf(fmaxf(1.0f, sx)),
+ roundf(fmaxf(1.0f, sy)));
+ return true;
}
status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float x, float y,
- const float* positions, SkPaint* paint, float totalAdvance, const Rect& bounds,
+ const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
DrawOpMode drawOpMode) {
if (drawOpMode == kDrawOpMode_Immediate) {
// The checks for corner-case ignorable text and quick rejection is only done for immediate
// drawing as ops from DeferredDisplayList are already filtered for these
- if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint) ||
- quickReject(bounds)) {
+ if (text == NULL || count == 0 || currentSnapshot()->isIgnored() || canSkipText(paint) ||
+ quickRejectSetupScissor(bounds)) {
return DrawGlInfo::kStatusDone;
}
}
@@ -2956,7 +2795,7 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, f
const float oldX = x;
const float oldY = y;
- const mat4& transform = currentTransform();
+ const mat4& transform = *currentTransform();
const bool pureTranslate = transform.isPureTranslate();
if (CC_LIKELY(pureTranslate)) {
@@ -2970,10 +2809,10 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, f
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- if (CC_UNLIKELY(mDrawModifiers.mHasShadow)) {
- fontRenderer.setFont(paint, mat4::identity());
+ if (CC_UNLIKELY(hasTextShadow(paint))) {
+ fontRenderer.setFont(paint, SkMatrix::I());
drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer,
- alpha, mode, oldX, oldY);
+ alpha, oldX, oldY);
}
const bool hasActiveLayer = hasLayer();
@@ -2989,15 +2828,15 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, f
// Applying the full matrix in the shader is the easiest way to handle
// rotation and perspective and allows us to always generated quads in the
// font renderer which greatly simplifies the code, clipping in particular.
- mat4 fontTransform = findBestFontTransform(transform);
+ SkMatrix fontTransform;
+ bool linearFilter = findBestFontTransform(transform, &fontTransform)
+ || fabs(y - (int) y) > 0.0f
+ || fabs(x - (int) x) > 0.0f;
fontRenderer.setFont(paint, fontTransform);
-
- // Pick the appropriate texture filtering
- bool linearFilter = !pureTranslate || fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
fontRenderer.setTextureFiltering(linearFilter);
// TODO: Implement better clipping for scaled/rotated text
- const Rect* clip = !pureTranslate ? NULL : mSnapshot->clipRect;
+ const Rect* clip = !pureTranslate ? NULL : currentClipRect();
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
bool status;
@@ -3022,14 +2861,14 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, f
dirtyLayerUnchecked(layerBounds, getRegion());
}
- drawTextDecorations(text, bytesCount, totalAdvance, oldX, oldY, paint);
+ drawTextDecorations(totalAdvance, oldX, oldY, paint);
return DrawGlInfo::kStatusDrew;
}
-status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count, SkPath* path,
- float hOffset, float vOffset, SkPaint* paint) {
- if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
+status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count,
+ const SkPath* path, float hOffset, float vOffset, const SkPaint* paint) {
+ if (text == NULL || count == 0 || currentSnapshot()->isIgnored() || canSkipText(paint)) {
return DrawGlInfo::kStatusDone;
}
@@ -3037,7 +2876,7 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co
mCaches.enableScissor();
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
- fontRenderer.setFont(paint, mat4::identity());
+ fontRenderer.setFont(paint, SkMatrix::I());
fontRenderer.setTextureFiltering(true);
int alpha;
@@ -3053,7 +2892,7 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co
if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
hOffset, vOffset, hasActiveLayer ? &bounds : NULL, &functor)) {
if (hasActiveLayer) {
- currentTransform().mapRect(bounds);
+ currentTransform()->mapRect(bounds);
dirtyLayerUnchecked(bounds, getRegion());
}
}
@@ -3061,8 +2900,8 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co
return DrawGlInfo::kStatusDrew;
}
-status_t OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
- if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone;
+status_t OpenGLRenderer::drawPath(const SkPath* path, const SkPaint* paint) {
+ if (currentSnapshot()->isIgnored()) return DrawGlInfo::kStatusDone;
mCaches.activeTexture(0);
@@ -3087,14 +2926,14 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) {
if (layer->isTextureLayer()) {
transform = &layer->getTransform();
if (!transform->isIdentity()) {
- save(0);
- currentTransform().multiply(*transform);
+ save(SkCanvas::kMatrix_SaveFlag);
+ concatMatrix(*transform);
}
}
bool clipRequired = false;
- const bool rejected = quickRejectNoScissor(x, y,
- x + layer->layer.getWidth(), y + layer->layer.getHeight(), false, &clipRequired);
+ const bool rejected = calculateQuickRejectForScissor(x, y,
+ x + layer->layer.getWidth(), y + layer->layer.getHeight(), &clipRequired, NULL, false);
if (rejected) {
if (transform && !transform->isIdentity()) {
@@ -3103,39 +2942,40 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) {
return DrawGlInfo::kStatusDone;
}
+ EVENT_LOGD("drawLayer," RECT_STRING ", clipRequired %d", x, y,
+ x + layer->layer.getWidth(), y + layer->layer.getHeight(), clipRequired);
+
updateLayer(layer, true);
mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired);
mCaches.activeTexture(0);
if (CC_LIKELY(!layer->region.isEmpty())) {
- SkiaColorFilter* oldFilter = mDrawModifiers.mColorFilter;
- mDrawModifiers.mColorFilter = layer->getColorFilter();
-
if (layer->region.isRect()) {
DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate,
composeLayerRect(layer, layer->regionRect));
} else if (layer->mesh) {
+
const float a = getLayerAlpha(layer);
setupDraw();
setupDrawWithTexture();
setupDrawColor(a, a, a, a);
- setupDrawColorFilter();
- setupDrawBlending(layer->isBlend() || a < 1.0f, layer->getMode(), false);
+ setupDrawColorFilter(layer->getColorFilter());
+ setupDrawBlending(layer);
setupDrawProgram();
setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
+ setupDrawColorFilterUniforms(layer->getColorFilter());
setupDrawTexture(layer->getTexture());
- if (CC_LIKELY(currentTransform().isPureTranslate())) {
- int tx = (int) floorf(x + currentTransform().getTranslateX() + 0.5f);
- int ty = (int) floorf(y + currentTransform().getTranslateY() + 0.5f);
+ if (CC_LIKELY(currentTransform()->isPureTranslate())) {
+ int tx = (int) floorf(x + currentTransform()->getTranslateX() + 0.5f);
+ int ty = (int) floorf(y + currentTransform()->getTranslateY() + 0.5f);
layer->setFilter(GL_NEAREST);
- setupDrawModelViewTranslate(tx, ty,
+ setupDrawModelView(kModelViewMode_Translate, false, tx, ty,
tx + layer->layer.getWidth(), ty + layer->layer.getHeight(), true);
} else {
layer->setFilter(GL_LINEAR);
- setupDrawModelViewTranslate(x, y,
+ setupDrawModelView(kModelViewMode_Translate, false, x, y,
x + layer->layer.getWidth(), y + layer->layer.getHeight());
}
@@ -3145,7 +2985,7 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) {
while (elementsCount > 0) {
GLsizei drawCount = min(elementsCount, (GLsizei) gMaxNumberOfQuads * 6);
- setupDrawMeshIndices(&mesh[0].position[0], &mesh[0].texture[0]);
+ setupDrawMeshIndices(&mesh[0].x, &mesh[0].u);
DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate,
glDrawElements(GL_TRIANGLES, drawCount, GL_UNSIGNED_SHORT, NULL));
@@ -3156,16 +2996,16 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) {
}
#if DEBUG_LAYERS_AS_REGIONS
- drawRegionRects(layer->region);
+ drawRegionRectsDebug(layer->region);
#endif
}
- mDrawModifiers.mColorFilter = oldFilter;
-
if (layer->debugDrawUpdate) {
layer->debugDrawUpdate = false;
- drawColorRect(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(),
- 0x7f00ff00, SkXfermode::kSrcOver_Mode);
+
+ SkPaint paint;
+ paint.setColor(0x7f00ff00);
+ drawColorRect(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(), &paint);
}
}
layer->hasDrawnSinceUpdate = true;
@@ -3178,49 +3018,6 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) {
}
///////////////////////////////////////////////////////////////////////////////
-// Shaders
-///////////////////////////////////////////////////////////////////////////////
-
-void OpenGLRenderer::resetShader() {
- mDrawModifiers.mShader = NULL;
-}
-
-void OpenGLRenderer::setupShader(SkiaShader* shader) {
- mDrawModifiers.mShader = shader;
- if (mDrawModifiers.mShader) {
- mDrawModifiers.mShader->setCaches(mCaches);
- }
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Color filters
-///////////////////////////////////////////////////////////////////////////////
-
-void OpenGLRenderer::resetColorFilter() {
- mDrawModifiers.mColorFilter = NULL;
-}
-
-void OpenGLRenderer::setupColorFilter(SkiaColorFilter* filter) {
- mDrawModifiers.mColorFilter = filter;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Drop shadow
-///////////////////////////////////////////////////////////////////////////////
-
-void OpenGLRenderer::resetShadow() {
- mDrawModifiers.mHasShadow = false;
-}
-
-void OpenGLRenderer::setupShadow(float radius, float dx, float dy, int color) {
- mDrawModifiers.mHasShadow = true;
- mDrawModifiers.mShadowRadius = radius;
- mDrawModifiers.mShadowDx = dx;
- mDrawModifiers.mShadowDy = dy;
- mDrawModifiers.mShadowColor = color;
-}
-
-///////////////////////////////////////////////////////////////////////////////
// Draw filters
///////////////////////////////////////////////////////////////////////////////
@@ -3238,7 +3035,7 @@ void OpenGLRenderer::setupPaintFilter(int clearBits, int setBits) {
mDrawModifiers.mPaintFilterSetBits = setBits & SkPaint::kAllFlags;
}
-SkPaint* OpenGLRenderer::filterPaint(SkPaint* paint) {
+const SkPaint* OpenGLRenderer::filterPaint(const SkPaint* paint) {
if (CC_LIKELY(!mDrawModifiers.mHasDrawFilter || !paint)) {
return paint;
}
@@ -3256,7 +3053,7 @@ SkPaint* OpenGLRenderer::filterPaint(SkPaint* paint) {
// Drawing implementation
///////////////////////////////////////////////////////////////////////////////
-Texture* OpenGLRenderer::getTexture(SkBitmap* bitmap) {
+Texture* OpenGLRenderer::getTexture(const SkBitmap* bitmap) {
Texture* texture = mCaches.assetAtlas.getEntryTexture(bitmap);
if (!texture) {
return mCaches.textureCache.get(bitmap);
@@ -3265,8 +3062,8 @@ Texture* OpenGLRenderer::getTexture(SkBitmap* bitmap) {
}
void OpenGLRenderer::drawPathTexture(const PathTexture* texture,
- float x, float y, SkPaint* paint) {
- if (quickReject(x, y, x + texture->width, y + texture->height)) {
+ float x, float y, const SkPaint* paint) {
+ if (quickRejectSetupScissor(x, y, x + texture->width, y + texture->height)) {
return;
}
@@ -3277,15 +3074,16 @@ void OpenGLRenderer::drawPathTexture(const PathTexture* texture,
setupDraw();
setupDrawWithTexture(true);
setupDrawAlpha8Color(paint->getColor(), alpha);
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(true, mode);
+ setupDrawColorFilter(getColorFilter(paint));
+ setupDrawShader(getShader(paint));
+ setupDrawBlending(paint, true);
setupDrawProgram();
- setupDrawModelView(x, y, x + texture->width, y + texture->height);
+ setupDrawModelView(kModelViewMode_TranslateAndScale, false,
+ x, y, x + texture->width, y + texture->height);
setupDrawTexture(texture->id);
setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderUniforms();
+ setupDrawColorFilterUniforms(getColorFilter(paint));
+ setupDrawShaderUniforms(getShader(paint));
setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset);
glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
@@ -3296,8 +3094,8 @@ void OpenGLRenderer::drawPathTexture(const PathTexture* texture,
#define kStdUnderline_Offset (1.0f / 9.0f)
#define kStdUnderline_Thickness (1.0f / 18.0f)
-void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float underlineWidth,
- float x, float y, SkPaint* paint) {
+void OpenGLRenderer::drawTextDecorations(float underlineWidth, float x, float y,
+ const SkPaint* paint) {
// Handle underline and strike-through
uint32_t flags = paint->getFlags();
if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
@@ -3341,27 +3139,66 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float
}
}
-status_t OpenGLRenderer::drawRects(const float* rects, int count, SkPaint* paint) {
- if (mSnapshot->isIgnored()) {
+status_t OpenGLRenderer::drawRects(const float* rects, int count, const SkPaint* paint) {
+ if (currentSnapshot()->isIgnored()) {
return DrawGlInfo::kStatusDone;
}
- int color = paint->getColor();
- // If a shader is set, preserve only the alpha
- if (mDrawModifiers.mShader) {
- color |= 0x00ffffff;
+ return drawColorRects(rects, count, paint, false, true, true);
+}
+
+static void mapPointFakeZ(Vector3& point, const mat4& transformXY, const mat4& transformZ) {
+ // map z coordinate with true 3d matrix
+ point.z = transformZ.mapZ(point);
+
+ // map x,y coordinates with draw/Skia matrix
+ transformXY.mapPoint(point.x, point.y);
+}
+
+status_t OpenGLRenderer::drawShadow(float casterAlpha,
+ const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) {
+ if (currentSnapshot()->isIgnored()) return DrawGlInfo::kStatusDone;
+
+ // TODO: use quickRejectWithScissor. For now, always force enable scissor.
+ mCaches.enableScissor();
+
+ SkPaint paint;
+ paint.setAntiAlias(true); // want to use AlphaVertex
+
+ // The caller has made sure casterAlpha > 0.
+ float ambientShadowAlpha = mAmbientShadowAlpha;
+ if (CC_UNLIKELY(mCaches.propertyAmbientShadowStrength >= 0)) {
+ ambientShadowAlpha = mCaches.propertyAmbientShadowStrength;
+ }
+ if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) {
+ paint.setARGB(casterAlpha * ambientShadowAlpha, 0, 0, 0);
+ drawVertexBuffer(*ambientShadowVertexBuffer, &paint, kVertexBuffer_ShadowInterp);
+ }
+
+ float spotShadowAlpha = mSpotShadowAlpha;
+ if (CC_UNLIKELY(mCaches.propertySpotShadowStrength >= 0)) {
+ spotShadowAlpha = mCaches.propertySpotShadowStrength;
+ }
+ if (spotShadowVertexBuffer && spotShadowAlpha > 0) {
+ paint.setARGB(casterAlpha * spotShadowAlpha, 0, 0, 0);
+ drawVertexBuffer(*spotShadowVertexBuffer, &paint, kVertexBuffer_ShadowInterp);
}
- SkXfermode::Mode mode = getXfermode(paint->getXfermode());
- return drawColorRects(rects, count, color, mode);
+ return DrawGlInfo::kStatusDrew;
}
-status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color,
- SkXfermode::Mode mode, bool ignoreTransform, bool dirty, bool clip) {
+status_t OpenGLRenderer::drawColorRects(const float* rects, int count, const SkPaint* paint,
+ bool ignoreTransform, bool dirty, bool clip) {
if (count == 0) {
return DrawGlInfo::kStatusDone;
}
+ int color = paint->getColor();
+ // If a shader is set, preserve only the alpha
+ if (getShader(paint)) {
+ color |= 0x00ffffff;
+ }
+
float left = FLT_MAX;
float top = FLT_MAX;
float right = FLT_MIN;
@@ -3387,69 +3224,68 @@ status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color
bottom = fmaxf(bottom, b);
}
- if (clip && quickReject(left, top, right, bottom)) {
+ if (clip && quickRejectSetupScissor(left, top, right, bottom)) {
return DrawGlInfo::kStatusDone;
}
setupDraw();
setupDrawNoTexture();
- setupDrawColor(color, ((color >> 24) & 0xFF) * mSnapshot->alpha);
- setupDrawShader();
- setupDrawColorFilter();
- setupDrawBlending(mode);
+ setupDrawColor(color, ((color >> 24) & 0xFF) * currentSnapshot()->alpha);
+ setupDrawShader(getShader(paint));
+ setupDrawColorFilter(getColorFilter(paint));
+ setupDrawBlending(paint);
setupDrawProgram();
setupDrawDirtyRegionsDisabled();
- setupDrawModelView(0.0f, 0.0f, 1.0f, 1.0f, ignoreTransform, true);
- setupDrawColorUniforms();
- setupDrawShaderUniforms();
- setupDrawColorFilterUniforms();
+ setupDrawModelView(kModelViewMode_Translate, false,
+ 0.0f, 0.0f, 0.0f, 0.0f, ignoreTransform);
+ setupDrawColorUniforms(getShader(paint));
+ setupDrawShaderUniforms(getShader(paint));
+ setupDrawColorFilterUniforms(getColorFilter(paint));
if (dirty && hasLayer()) {
- dirtyLayer(left, top, right, bottom, currentTransform());
+ dirtyLayer(left, top, right, bottom, *currentTransform());
}
- drawIndexedQuads(&mesh[0], count / 4);
+ issueIndexedQuadDraw(&mesh[0], count / 4);
return DrawGlInfo::kStatusDrew;
}
void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom,
- int color, SkXfermode::Mode mode, bool ignoreTransform) {
+ const SkPaint* paint, bool ignoreTransform) {
+ int color = paint->getColor();
// If a shader is set, preserve only the alpha
- if (mDrawModifiers.mShader) {
+ if (getShader(paint)) {
color |= 0x00ffffff;
}
setupDraw();
setupDrawNoTexture();
- setupDrawColor(color, ((color >> 24) & 0xFF) * mSnapshot->alpha);
- setupDrawShader();
- setupDrawColorFilter();
- setupDrawBlending(mode);
+ setupDrawColor(color, ((color >> 24) & 0xFF) * currentSnapshot()->alpha);
+ setupDrawShader(getShader(paint));
+ setupDrawColorFilter(getColorFilter(paint));
+ setupDrawBlending(paint);
setupDrawProgram();
- setupDrawModelView(left, top, right, bottom, ignoreTransform);
- setupDrawColorUniforms();
- setupDrawShaderUniforms(ignoreTransform);
- setupDrawColorFilterUniforms();
+ setupDrawModelView(kModelViewMode_TranslateAndScale, false,
+ left, top, right, bottom, ignoreTransform);
+ setupDrawColorUniforms(getShader(paint));
+ setupDrawShaderUniforms(getShader(paint), ignoreTransform);
+ setupDrawColorFilterUniforms(getColorFilter(paint));
setupDrawSimpleMesh();
glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
}
void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
- Texture* texture, SkPaint* paint) {
- int alpha;
- SkXfermode::Mode mode;
- getAlphaAndMode(paint, &alpha, &mode);
-
+ Texture* texture, const SkPaint* paint) {
texture->setWrap(GL_CLAMP_TO_EDGE, true);
GLvoid* vertices = (GLvoid*) NULL;
GLvoid* texCoords = (GLvoid*) gMeshTextureOffset;
if (texture->uvMapper) {
- vertices = &mMeshVertices[0].position[0];
- texCoords = &mMeshVertices[0].texture[0];
+ vertices = &mMeshVertices[0].x;
+ texCoords = &mMeshVertices[0].u;
Rect uvs(0.0f, 0.0f, 1.0f, 1.0f);
texture->uvMapper->map(uvs);
@@ -3457,17 +3293,17 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b
resetDrawTextureTexCoords(uvs.left, uvs.top, uvs.right, uvs.bottom);
}
- if (CC_LIKELY(currentTransform().isPureTranslate())) {
- const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
- const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
+ if (CC_LIKELY(currentTransform()->isPureTranslate())) {
+ const float x = (int) floorf(left + currentTransform()->getTranslateX() + 0.5f);
+ const float y = (int) floorf(top + currentTransform()->getTranslateY() + 0.5f);
texture->setFilter(GL_NEAREST, true);
drawTextureMesh(x, y, x + texture->width, y + texture->height, texture->id,
- alpha / 255.0f, mode, texture->blend, vertices, texCoords,
+ paint, texture->blend, vertices, texCoords,
GL_TRIANGLE_STRIP, gMeshCount, false, true);
} else {
- texture->setFilter(FILTER(paint), true);
- drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode,
+ texture->setFilter(getFilter(paint), true);
+ drawTextureMesh(left, top, right, bottom, texture->id, paint,
texture->blend, vertices, texCoords, GL_TRIANGLE_STRIP, gMeshCount);
}
@@ -3476,86 +3312,85 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b
}
}
-void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
- GLuint texture, float alpha, SkXfermode::Mode mode, bool blend) {
- drawTextureMesh(left, top, right, bottom, texture, alpha, mode, blend,
- (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount);
-}
-
void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float bottom,
- GLuint texture, float alpha, SkXfermode::Mode mode, bool blend,
+ GLuint texture, const SkPaint* paint, bool blend,
GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
- bool swapSrcDst, bool ignoreTransform, GLuint vbo, bool ignoreScale, bool dirty) {
+ bool swapSrcDst, bool ignoreTransform, GLuint vbo,
+ ModelViewMode modelViewMode, bool dirty) {
+
+ int a;
+ SkXfermode::Mode mode;
+ getAlphaAndMode(paint, &a, &mode);
+ const float alpha = a / 255.0f;
setupDraw();
setupDrawWithTexture();
setupDrawColor(alpha, alpha, alpha, alpha);
- setupDrawColorFilter();
- setupDrawBlending(blend, mode, swapSrcDst);
+ setupDrawColorFilter(getColorFilter(paint));
+ setupDrawBlending(paint, blend, swapSrcDst);
setupDrawProgram();
if (!dirty) setupDrawDirtyRegionsDisabled();
- if (!ignoreScale) {
- setupDrawModelView(left, top, right, bottom, ignoreTransform);
- } else {
- setupDrawModelViewTranslate(left, top, right, bottom, ignoreTransform);
- }
+ setupDrawModelView(modelViewMode, false, left, top, right, bottom, ignoreTransform);
setupDrawTexture(texture);
setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
+ setupDrawColorFilterUniforms(getColorFilter(paint));
setupDrawMesh(vertices, texCoords, vbo);
glDrawArrays(drawMode, 0, elementsCount);
}
void OpenGLRenderer::drawIndexedTextureMesh(float left, float top, float right, float bottom,
- GLuint texture, float alpha, SkXfermode::Mode mode, bool blend,
+ GLuint texture, const SkPaint* paint, bool blend,
GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
- bool swapSrcDst, bool ignoreTransform, GLuint vbo, bool ignoreScale, bool dirty) {
+ bool swapSrcDst, bool ignoreTransform, GLuint vbo,
+ ModelViewMode modelViewMode, bool dirty) {
+
+ int a;
+ SkXfermode::Mode mode;
+ getAlphaAndMode(paint, &a, &mode);
+ const float alpha = a / 255.0f;
setupDraw();
setupDrawWithTexture();
setupDrawColor(alpha, alpha, alpha, alpha);
- setupDrawColorFilter();
- setupDrawBlending(blend, mode, swapSrcDst);
+ setupDrawColorFilter(getColorFilter(paint));
+ setupDrawBlending(paint, blend, swapSrcDst);
setupDrawProgram();
if (!dirty) setupDrawDirtyRegionsDisabled();
- if (!ignoreScale) {
- setupDrawModelView(left, top, right, bottom, ignoreTransform);
- } else {
- setupDrawModelViewTranslate(left, top, right, bottom, ignoreTransform);
- }
+ setupDrawModelView(modelViewMode, false, left, top, right, bottom, ignoreTransform);
setupDrawTexture(texture);
setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
+ setupDrawColorFilterUniforms(getColorFilter(paint));
setupDrawMeshIndices(vertices, texCoords, vbo);
glDrawElements(drawMode, elementsCount, GL_UNSIGNED_SHORT, NULL);
}
void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, float bottom,
- GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode,
+ GLuint texture, const SkPaint* paint,
GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
- bool ignoreTransform, bool ignoreScale, bool dirty) {
+ bool ignoreTransform, ModelViewMode modelViewMode, bool dirty) {
+
+ int color = paint != NULL ? paint->getColor() : 0;
+ int alpha;
+ SkXfermode::Mode mode;
+ getAlphaAndMode(paint, &alpha, &mode);
setupDraw();
setupDrawWithTexture(true);
- if (hasColor) {
+ if (paint != NULL) {
setupDrawAlpha8Color(color, alpha);
}
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(true, mode);
+ setupDrawColorFilter(getColorFilter(paint));
+ setupDrawShader(getShader(paint));
+ setupDrawBlending(paint, true);
setupDrawProgram();
if (!dirty) setupDrawDirtyRegionsDisabled();
- if (!ignoreScale) {
- setupDrawModelView(left, top, right, bottom, ignoreTransform);
- } else {
- setupDrawModelViewTranslate(left, top, right, bottom, ignoreTransform);
- }
+ setupDrawModelView(modelViewMode, false, left, top, right, bottom, ignoreTransform);
setupDrawTexture(texture);
setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderUniforms();
+ setupDrawColorFilterUniforms(getColorFilter(paint));
+ setupDrawShaderUniforms(getShader(paint), ignoreTransform);
setupDrawMesh(vertices, texCoords);
glDrawArrays(drawMode, 0, elementsCount);
@@ -3563,6 +3398,13 @@ void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, f
void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
ProgramDescription& description, bool swapSrcDst) {
+
+ if (mSnapshot->roundRectClipState != NULL /*&& !mSkipOutlineClip*/) {
+ blend = true;
+ mDescription.hasRoundRectClip = true;
+ }
+ mSkipOutlineClip = true;
+
if (mCountOverdraw) {
if (!mCaches.blend) glEnable(GL_BLEND);
if (mCaches.lastSrcMode != GL_ONE || mCaches.lastDstMode != GL_ONE) {
@@ -3636,23 +3478,23 @@ void OpenGLRenderer::resetDrawTextureTexCoords(float u1, float v1, float u2, flo
TextureVertex::setUV(v++, u2, v2);
}
-void OpenGLRenderer::getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const {
+void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const {
getAlphaAndModeDirect(paint, alpha, mode);
if (mDrawModifiers.mOverrideLayerAlpha < 1.0f) {
// if drawing a layer, ignore the paint's alpha
*alpha = mDrawModifiers.mOverrideLayerAlpha * 255;
}
- *alpha *= mSnapshot->alpha;
+ *alpha *= currentSnapshot()->alpha;
}
-float OpenGLRenderer::getLayerAlpha(Layer* layer) const {
+float OpenGLRenderer::getLayerAlpha(const Layer* layer) const {
float alpha;
if (mDrawModifiers.mOverrideLayerAlpha < 1.0f) {
alpha = mDrawModifiers.mOverrideLayerAlpha;
} else {
alpha = layer->getAlpha() / 255.0f;
}
- return alpha * mSnapshot->alpha;
+ return alpha * currentSnapshot()->alpha;
}
}; // namespace uirenderer
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 2e03a1b..e9ca5d9 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -21,12 +21,14 @@
#include <GLES2/gl2ext.h>
#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColorFilter.h>
#include <SkMatrix.h>
#include <SkPaint.h>
#include <SkRegion.h>
-#include <SkShader.h>
#include <SkXfermode.h>
+#include <utils/Blur.h>
#include <utils/Functor.h>
#include <utils/RefBase.h>
#include <utils/SortedVector.h>
@@ -41,16 +43,25 @@
#include "Matrix.h"
#include "Program.h"
#include "Rect.h"
+#include "Renderer.h"
#include "Snapshot.h"
-#include "Vertex.h"
-#include "SkiaShader.h"
-#include "SkiaColorFilter.h"
+#include "StatefulBaseRenderer.h"
#include "UvMapper.h"
+#include "Vertex.h"
#include "Caches.h"
+#include "CanvasProperty.h"
+
+class SkShader;
namespace android {
namespace uirenderer {
+class DeferredDisplayState;
+class RenderState;
+class RenderNode;
+class TextSetupFunctor;
+class VertexBuffer;
+
struct DrawModifiers {
DrawModifiers() {
reset();
@@ -60,17 +71,8 @@ struct DrawModifiers {
memset(this, 0, sizeof(DrawModifiers));
}
- SkiaShader* mShader;
- SkiaColorFilter* mColorFilter;
float mOverrideLayerAlpha;
- // Drop shadow
- bool mHasShadow;
- float mShadowRadius;
- float mShadowDx;
- float mShadowDy;
- int mShadowColor;
-
// Draw filters
bool mHasDrawFilter;
int mPaintFilterClearBits;
@@ -82,12 +84,6 @@ enum StateDeferFlags {
kStateDeferFlag_Clip = 0x2
};
-enum DrawOpMode {
- kDrawOpMode_Immediate,
- kDrawOpMode_Defer,
- kDrawOpMode_Flush
-};
-
enum ClipSideFlags {
kClipSide_None = 0x0,
kClipSide_Left = 0x1,
@@ -98,235 +94,117 @@ enum ClipSideFlags {
kClipSide_ConservativeFull = 0x1F
};
-///////////////////////////////////////////////////////////////////////////////
-// Renderer
-///////////////////////////////////////////////////////////////////////////////
-
-class DeferredDisplayState;
-class DisplayList;
-class TextSetupFunctor;
-class VertexBuffer;
+enum VertexBufferDisplayFlags {
+ kVertexBuffer_Offset = 0x1,
+ kVertexBuffer_ShadowInterp = 0x2,
+};
/**
- * OpenGL renderer used to draw accelerated 2D graphics. The API is a
- * simplified version of Skia's Canvas API.
+ * Defines additional transformation that should be applied by the model view matrix, beyond that of
+ * the currentTransform()
*/
-class OpenGLRenderer {
-public:
- ANDROID_API OpenGLRenderer();
- virtual ~OpenGLRenderer();
-
- /**
- * Sets the name of this renderer. The name is optional and
- * empty by default. If the pointer is null the name is set
- * to the empty string.
- */
- ANDROID_API void setName(const char* name);
-
+enum ModelViewMode {
/**
- * Returns the name of this renderer as UTF8 string.
- * The returned pointer is never null.
+ * Used when the model view should simply translate geometry passed to the shader. The resulting
+ * matrix will be a simple translation.
*/
- ANDROID_API const char* getName() const;
+ kModelViewMode_Translate = 0,
/**
- * Read externally defined properties to control the behavior
- * of the renderer.
+ * Used when the model view should translate and scale geometry. The resulting matrix will be a
+ * translation + scale. This is frequently used together with VBO 0, the (0,0,1,1) rect.
*/
- ANDROID_API void initProperties();
+ kModelViewMode_TranslateAndScale = 1,
+};
- /**
- * Indicates whether this renderer executes drawing commands immediately.
- * If this method returns true, the drawing commands will be executed
- * later.
- */
- virtual bool isDeferred();
+///////////////////////////////////////////////////////////////////////////////
+// Renderer
+///////////////////////////////////////////////////////////////////////////////
+/**
+ * OpenGL Renderer implementation.
+ */
+class OpenGLRenderer : public StatefulBaseRenderer {
+public:
+ OpenGLRenderer(RenderState& renderState);
+ virtual ~OpenGLRenderer();
- /**
- * Sets the dimension of the underlying drawing surface. This method must
- * be called at least once every time the drawing surface changes size.
- *
- * @param width The width in pixels of the underlysing surface
- * @param height The height in pixels of the underlysing surface
- */
- virtual void setViewport(int width, int height);
+ void initProperties();
+ void initLight(const Vector3& lightCenter, float lightRadius,
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
- /**
- * Prepares the renderer to draw a frame. This method must be invoked
- * at the beginning of each frame. When this method is invoked, the
- * entire drawing surface is assumed to be redrawn.
- *
- * @param opaque If true, the target surface is considered opaque
- * and will not be cleared. If false, the target surface
- * will be cleared
- */
- ANDROID_API status_t prepare(bool opaque);
-
- /**
- * Prepares the renderer to draw a frame. This method must be invoked
- * at the beginning of each frame. Only the specified rectangle of the
- * frame is assumed to be dirty. A clip will automatically be set to
- * the specified rectangle.
- *
- * @param left The left coordinate of the dirty rectangle
- * @param top The top coordinate of the dirty rectangle
- * @param right The right coordinate of the dirty rectangle
- * @param bottom The bottom coordinate of the dirty rectangle
- * @param opaque If true, the target surface is considered opaque
- * and will not be cleared. If false, the target surface
- * will be cleared in the specified dirty rectangle
- */
+ virtual void onViewportInitialized();
virtual status_t prepareDirty(float left, float top, float right, float bottom, bool opaque);
-
- /**
- * Indicates the end of a frame. This method must be invoked whenever
- * the caller is done rendering a frame.
- */
virtual void finish();
- /**
- * This method must be invoked before handing control over to a draw functor.
- * See callDrawGLFunction() for instance.
- *
- * This command must not be recorded inside display lists.
- */
- virtual void interrupt();
-
- /**
- * This method must be invoked after getting control back from a draw functor.
- *
- * This command must not be recorded inside display lists.
- */
- virtual void resume();
-
- ANDROID_API void setCountOverdrawEnabled(bool enabled) {
+ void setCountOverdrawEnabled(bool enabled) {
mCountOverdraw = enabled;
}
- ANDROID_API float getOverdraw() {
+ float getOverdraw() {
return mCountOverdraw ? mOverdraw : 0.0f;
}
- ANDROID_API status_t invokeFunctors(Rect& dirty);
- ANDROID_API void detachFunctor(Functor* functor);
- ANDROID_API void attachFunctor(Functor* functor);
virtual status_t callDrawGLFunction(Functor* functor, Rect& dirty);
- ANDROID_API void pushLayerUpdate(Layer* layer);
- ANDROID_API void cancelLayerUpdate(Layer* layer);
- ANDROID_API void clearLayerUpdates();
- ANDROID_API void flushLayerUpdates();
-
- ANDROID_API int getSaveCount() const;
- virtual int save(int flags);
- virtual void restore();
- virtual void restoreToCount(int saveCount);
-
- ANDROID_API int saveLayer(float left, float top, float right, float bottom,
- SkPaint* paint, int flags) {
- SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode;
- if (paint) mode = getXfermode(paint->getXfermode());
- return saveLayer(left, top, right, bottom, paint ? paint->getAlpha() : 255, mode, flags);
- }
- ANDROID_API int saveLayerAlpha(float left, float top, float right, float bottom,
- int alpha, int flags) {
- return saveLayer(left, top, right, bottom, alpha, SkXfermode::kSrcOver_Mode, flags);
- }
- virtual int saveLayer(float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags);
-
- int saveLayerDeferred(float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags);
-
- virtual void translate(float dx, float dy);
- virtual void rotate(float degrees);
- virtual void scale(float sx, float sy);
- virtual void skew(float sx, float sy);
-
- bool hasRectToRectTransform();
- ANDROID_API void getMatrix(SkMatrix* matrix);
- virtual void setMatrix(SkMatrix* matrix);
- virtual void concatMatrix(SkMatrix* matrix);
-
- ANDROID_API const Rect& getClipBounds();
-
- /**
- * Performs a quick reject but adjust the bounds to account for stroke width if necessary,
- * and handling snapOut for AA geometry.
- */
- bool quickRejectPreStroke(float left, float top, float right, float bottom, SkPaint* paint);
+ void pushLayerUpdate(Layer* layer);
+ void cancelLayerUpdate(Layer* layer);
+ void clearLayerUpdates();
+ void flushLayerUpdates();
- /**
- * Returns false and sets scissor based upon bounds if drawing won't be clipped out
- */
- bool quickReject(float left, float top, float right, float bottom, bool snapOut = false);
- bool quickReject(const Rect& bounds) {
- return quickReject(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ virtual int saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* paint, int flags) {
+ return saveLayer(left, top, right, bottom, paint, flags, NULL);
}
- /**
- * Same as quickReject, without the scissor, instead returning clipRequired through pointer.
- * clipRequired will be only set if not rejected
- */
- ANDROID_API bool quickRejectNoScissor(float left, float top, float right, float bottom,
- bool snapOut = false, bool* clipRequired = NULL);
- bool quickRejectNoScissor(const Rect& bounds, bool* clipRequired = NULL) {
- return quickRejectNoScissor(bounds.left, bounds.top, bounds.right, bounds.bottom,
- clipRequired);
- }
+ // Specialized saveLayer implementation, which will pass the convexMask to an FBO layer, if
+ // created, which will in turn clip to that mask when drawn back/restored.
+ int saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* paint, int flags, const SkPath* convexMask);
- virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
- virtual bool clipPath(SkPath* path, SkRegion::Op op);
- virtual bool clipRegion(SkRegion* region, SkRegion::Op op);
- virtual Rect* getClipRect();
+ int saveLayerDeferred(float left, float top, float right, float bottom,
+ const SkPaint* paint, int flags);
- virtual status_t drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t replayFlags);
- virtual void outputDisplayList(DisplayList* displayList);
+ virtual status_t drawRenderNode(RenderNode* displayList, Rect& dirty, int32_t replayFlags = 1);
virtual status_t drawLayer(Layer* layer, float x, float y);
- virtual status_t drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
- status_t drawBitmaps(SkBitmap* bitmap, AssetAtlas::Entry* entry, int bitmapCount,
- TextureVertex* vertices, bool pureTranslate, const Rect& bounds, SkPaint* paint);
- virtual status_t drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
- virtual status_t drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
+ virtual status_t drawBitmap(const SkBitmap* bitmap, const SkPaint* paint);
+ status_t drawBitmaps(const SkBitmap* bitmap, AssetAtlas::Entry* entry, int bitmapCount,
+ TextureVertex* vertices, bool pureTranslate, const Rect& bounds, const SkPaint* paint);
+ virtual status_t drawBitmap(const SkBitmap* bitmap, float srcLeft, float srcTop,
float srcRight, float srcBottom, float dstLeft, float dstTop,
- float dstRight, float dstBottom, SkPaint* paint);
- virtual status_t drawBitmapData(SkBitmap* bitmap, float left, float top, SkPaint* paint);
- virtual status_t drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight,
- float* vertices, int* colors, SkPaint* paint);
- status_t drawPatches(SkBitmap* bitmap, AssetAtlas::Entry* entry,
- TextureVertex* vertices, uint32_t indexCount, SkPaint* paint);
- virtual status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
- float left, float top, float right, float bottom, SkPaint* paint);
- status_t drawPatch(SkBitmap* bitmap, const Patch* mesh, AssetAtlas::Entry* entry,
- float left, float top, float right, float bottom, SkPaint* paint);
+ float dstRight, float dstBottom, const SkPaint* paint);
+ virtual status_t drawBitmapData(const SkBitmap* bitmap, const SkPaint* paint);
+ virtual status_t drawBitmapMesh(const SkBitmap* bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint);
+ status_t drawPatches(const SkBitmap* bitmap, AssetAtlas::Entry* entry,
+ TextureVertex* vertices, uint32_t indexCount, const SkPaint* paint);
+ virtual status_t drawPatch(const SkBitmap* bitmap, const Res_png_9patch* patch,
+ float left, float top, float right, float bottom, const SkPaint* paint);
+ status_t drawPatch(const SkBitmap* bitmap, const Patch* mesh, AssetAtlas::Entry* entry,
+ float left, float top, float right, float bottom, const SkPaint* paint);
virtual status_t drawColor(int color, SkXfermode::Mode mode);
- virtual status_t drawRect(float left, float top, float right, float bottom, SkPaint* paint);
+ virtual status_t drawRect(float left, float top, float right, float bottom,
+ const SkPaint* paint);
virtual status_t drawRoundRect(float left, float top, float right, float bottom,
- float rx, float ry, SkPaint* paint);
- virtual status_t drawCircle(float x, float y, float radius, SkPaint* paint);
- virtual status_t drawOval(float left, float top, float right, float bottom, SkPaint* paint);
+ float rx, float ry, const SkPaint* paint);
+ virtual status_t drawCircle(float x, float y, float radius, const SkPaint* paint);
+ virtual status_t drawOval(float left, float top, float right, float bottom,
+ const SkPaint* paint);
virtual status_t drawArc(float left, float top, float right, float bottom,
- float startAngle, float sweepAngle, bool useCenter, SkPaint* paint);
- virtual status_t drawPath(SkPath* path, SkPaint* paint);
- virtual status_t drawLines(float* points, int count, SkPaint* paint);
- virtual status_t drawPoints(float* points, int count, SkPaint* paint);
- virtual status_t drawTextOnPath(const char* text, int bytesCount, int count, SkPath* path,
- float hOffset, float vOffset, SkPaint* paint);
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint);
+ virtual status_t drawPath(const SkPath* path, const SkPaint* paint);
+ virtual status_t drawLines(const float* points, int count, const SkPaint* paint);
+ virtual status_t drawPoints(const float* points, int count, const SkPaint* paint);
+ virtual status_t drawTextOnPath(const char* text, int bytesCount, int count, const SkPath* path,
+ float hOffset, float vOffset, const SkPaint* paint);
virtual status_t drawPosText(const char* text, int bytesCount, int count,
- const float* positions, SkPaint* paint);
+ const float* positions, const SkPaint* paint);
virtual status_t drawText(const char* text, int bytesCount, int count, float x, float y,
- const float* positions, SkPaint* paint, float totalAdvance, const Rect& bounds,
+ const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
DrawOpMode drawOpMode = kDrawOpMode_Immediate);
- virtual status_t drawRects(const float* rects, int count, SkPaint* paint);
-
- virtual void resetShader();
- virtual void setupShader(SkiaShader* shader);
-
- virtual void resetColorFilter();
- virtual void setupColorFilter(SkiaColorFilter* filter);
+ virtual status_t drawRects(const float* rects, int count, const SkPaint* paint);
- virtual void resetShadow();
- virtual void setupShadow(float radius, float dx, float dy, int color);
+ status_t drawShadow(float casterAlpha,
+ const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer);
virtual void resetPaintFilter();
virtual void setupPaintFilter(int clearBits, int setBits);
@@ -334,7 +212,7 @@ public:
// If this value is set to < 1.0, it overrides alpha set on layer (see drawBitmap, drawLayer)
void setOverrideLayerAlpha(float alpha) { mDrawModifiers.mOverrideLayerAlpha = alpha; }
- SkPaint* filterPaint(SkPaint* paint);
+ const SkPaint* filterPaint(const SkPaint* paint);
/**
* Store the current display state (most importantly, the current clip and transform), and
@@ -349,8 +227,8 @@ public:
const DrawModifiers& getDrawModifiers() { return mDrawModifiers; }
void setDrawModifiers(const DrawModifiers& drawModifiers) { mDrawModifiers = drawModifiers; }
- ANDROID_API bool isCurrentTransformSimple() {
- return mSnapshot->transform->isSimple();
+ bool isCurrentTransformSimple() {
+ return currentTransform()->isSimple();
}
Caches& getCaches() {
@@ -362,8 +240,8 @@ public:
return mSnapshot->clipRegion->isEmpty();
}
- int getViewportWidth() { return getSnapshot()->viewport.getWidth(); }
- int getViewportHeight() { return getSnapshot()->viewport.getHeight(); }
+ int getViewportWidth() { return currentSnapshot()->getViewportWidth(); }
+ int getViewportHeight() { return currentSnapshot()->getViewportHeight(); }
/**
* Scales the alpha on the current snapshot. This alpha value will be modulated
@@ -379,6 +257,11 @@ public:
void eventMark(const char* name) const;
/**
+ * Inserts a formatted event marker in the stream of GL commands.
+ */
+ void eventMarkDEBUG(const char *fmt, ...) const;
+
+ /**
* Inserts a named group marker in the stream of GL commands. This marker
* can be used by tools to group commands into logical groups. A call to
* this method must always be followed later on by a call to endMark().
@@ -400,12 +283,12 @@ public:
* @param alpha Where to store the resulting alpha
* @param mode Where to store the resulting xfermode
*/
- static inline void getAlphaAndModeDirect(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
+ static inline void getAlphaAndModeDirect(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
*mode = getXfermodeDirect(paint);
*alpha = getAlphaDirect(paint);
}
- static inline SkXfermode::Mode getXfermodeDirect(SkPaint* paint) {
+ static inline SkXfermode::Mode getXfermodeDirect(const SkPaint* paint) {
if (!paint) return SkXfermode::kSrcOver_Mode;
return getXfermode(paint->getXfermode());
}
@@ -415,11 +298,38 @@ public:
return paint->getAlpha();
}
+ struct TextShadow {
+ SkScalar radius;
+ float dx;
+ float dy;
+ SkColor color;
+ };
+
+ static inline bool getTextShadow(const SkPaint* paint, TextShadow* textShadow) {
+ SkDrawLooper::BlurShadowRec blur;
+ if (paint && paint->getLooper() && paint->getLooper()->asABlurShadow(&blur)) {
+ if (textShadow) {
+ textShadow->radius = Blur::convertSigmaToRadius(blur.fSigma);
+ textShadow->dx = blur.fOffset.fX;
+ textShadow->dy = blur.fOffset.fY;
+ textShadow->color = blur.fColor;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ static inline bool hasTextShadow(const SkPaint* paint) {
+ return getTextShadow(paint, NULL);
+ }
+
/**
- * Return the best transform to use to rasterize text given a full
- * transform matrix.
+ * Build the best transform to use to rasterize text given a full
+ * transform matrix, and whether filteration is needed.
+ *
+ * Returns whether filtration is needed
*/
- mat4 findBestFontTransform(const mat4& transform) const;
+ bool findBestFontTransform(const mat4& transform, SkMatrix* outMatrix) const;
#if DEBUG_MERGE_BEHAVIOR
void drawScreenSpaceColorRect(float left, float top, float right, float bottom, int color) {
@@ -435,13 +345,12 @@ public:
}
#endif
-protected:
- /**
- * Computes the projection matrix, initialize the first snapshot
- * and stores the dimensions of the render target.
- */
- void initViewport(int width, int height);
+ const Vector3& getLightCenter() const { return currentSnapshot()->getRelativeLightCenter(); }
+ float getLightRadius() const { return mLightRadius; }
+ uint8_t getAmbientShadowAlpha() const { return mAmbientShadowAlpha; }
+ uint8_t getSpotShadowAlpha() const { return mSpotShadowAlpha; }
+protected:
/**
* Perform the setup specific to a frame. This method does not
* issue any OpenGL commands.
@@ -477,6 +386,13 @@ protected:
*/
void attachStencilBufferToLayer(Layer* layer);
+ bool quickRejectSetupScissor(float left, float top, float right, float bottom,
+ const SkPaint* paint = NULL);
+ bool quickRejectSetupScissor(const Rect& bounds, const SkPaint* paint = NULL) {
+ return quickRejectSetupScissor(bounds.left, bounds.top,
+ bounds.right, bounds.bottom, paint);
+ }
+
/**
* Compose the layer defined in the current snapshot with the layer
* defined by the previous snapshot.
@@ -486,7 +402,7 @@ protected:
* @param curent The current snapshot containing the layer to compose
* @param previous The previous snapshot to compose the current layer with
*/
- virtual void composeLayer(sp<Snapshot> current, sp<Snapshot> previous);
+ virtual void composeLayer(const Snapshot& current, const Snapshot& previous);
/**
* Marks the specified region as dirty at the specified bounds.
@@ -494,13 +410,6 @@ protected:
void dirtyLayerUnchecked(Rect& bounds, Region* region);
/**
- * Returns the current snapshot.
- */
- sp<Snapshot> getSnapshot() const {
- return mSnapshot;
- }
-
- /**
* Returns the region of the current layer.
*/
virtual Region* getRegion() const {
@@ -517,7 +426,7 @@ protected:
/**
* Returns the name of the FBO this renderer is rendering into.
*/
- virtual GLint getTargetFbo() const {
+ virtual GLuint getTargetFbo() const {
return 0;
}
@@ -538,25 +447,29 @@ protected:
* @param alpha Where to store the resulting alpha
* @param mode Where to store the resulting xfermode
*/
- inline void getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const;
+ inline void getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const;
/**
* Gets the alpha from a layer, accounting for snapshot alpha and overrideLayerAlpha
*
* @param layer The layer from which the alpha is extracted
*/
- inline float getLayerAlpha(Layer* layer) const;
+ inline float getLayerAlpha(const Layer* layer) const;
/**
- * Safely retrieves the mode from the specified xfermode. If the specified
- * xfermode is null, the mode is assumed to be SkXfermode::kSrcOver_Mode.
+ * Safely retrieves the ColorFilter from the given Paint. If the paint is
+ * null then null is returned.
*/
- static inline SkXfermode::Mode getXfermode(SkXfermode* mode) {
- SkXfermode::Mode resultMode;
- if (!SkXfermode::AsMode(mode, &resultMode)) {
- resultMode = SkXfermode::kSrcOver_Mode;
- }
- return resultMode;
+ static inline SkColorFilter* getColorFilter(const SkPaint* paint) {
+ return paint ? paint->getColorFilter() : NULL;
+ }
+
+ /**
+ * Safely retrieves the Shader from the given Paint. If the paint is
+ * null then null is returned.
+ */
+ static inline const SkShader* getShader(const SkPaint* paint) {
+ return paint ? paint->getShader() : NULL;
}
/**
@@ -566,6 +479,8 @@ protected:
return false;
}
+ inline RenderState& renderState() { return mRenderState; }
+
private:
/**
* Discards the content of the framebuffer if supported by the driver.
@@ -582,12 +497,11 @@ private:
/**
* Tells the GPU what part of the screen is about to be redrawn.
- * This method will use the clip rect that we started drawing the
- * frame with.
+ * This method will use the current layer space clip rect.
* This method needs to be invoked every time getTargetFbo() is
* bound again.
*/
- void startTiling(const sp<Snapshot>& snapshot, bool opaque = false, bool expand = false);
+ void startTilingCurrentClip(bool opaque = false, bool expand = false);
/**
* Tells the GPU what part of the screen is about to be redrawn.
@@ -602,23 +516,7 @@ private:
*/
void endTiling();
- /**
- * Saves the current state of the renderer as a new snapshot.
- * The new snapshot is saved in mSnapshot and the previous snapshot
- * is linked from mSnapshot->previous.
- *
- * @param flags The save flags; see SkCanvas for more information
- *
- * @return The new save count. This value can be passed to #restoreToCount()
- */
- int saveSnapshot(int flags);
-
- /**
- * Restores the current snapshot; mSnapshot becomes mSnapshot->previous.
- *
- * @return True if the clip was modified.
- */
- bool restoreSnapshot();
+ void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored);
/**
* Sets the clipping rectangle using glScissor. The clip is defined by
@@ -654,12 +552,12 @@ private:
* @param alpha The translucency of the layer
* @param mode The blending mode of the layer
* @param flags The layer save flags
- * @param previousFbo The name of the current framebuffer
+ * @param mask A mask to use when drawing the layer back, may be empty
*
* @return True if the layer was successfully created, false otherwise
*/
bool createLayer(float left, float top, float right, float bottom,
- int alpha, SkXfermode::Mode mode, int flags, GLuint previousFbo);
+ const SkPaint* paint, int flags, const SkPath* convexMask);
/**
* Creates a new layer stored in the specified snapshot as an FBO.
@@ -667,9 +565,8 @@ private:
* @param layer The layer to store as an FBO
* @param snapshot The snapshot associated with the new layer
* @param bounds The bounds of the layer
- * @param previousFbo The name of the current framebuffer
*/
- bool createFboLayer(Layer* layer, Rect& bounds, Rect& clip, GLuint previousFbo);
+ bool createFboLayer(Layer* layer, Rect& bounds, Rect& clip);
/**
* Compose the specified layer as a region.
@@ -716,12 +613,11 @@ private:
* @param top The top coordinate of the rectangle
* @param right The right coordinate of the rectangle
* @param bottom The bottom coordinate of the rectangle
- * @param color The rectangle's ARGB color, defined as a packed 32 bits word
- * @param mode The Skia xfermode to use
+ * @param paint The paint containing the color, blending mode, etc.
* @param ignoreTransform True if the current transform should be ignored
*/
void drawColorRect(float left, float top, float right, float bottom,
- int color, SkXfermode::Mode mode, bool ignoreTransform = false);
+ const SkPaint* paint, bool ignoreTransform = false);
/**
* Draws a series of colored rectangles with the specified color. The specified
@@ -730,15 +626,13 @@ private:
*
* @param rects A list of rectangles, 4 floats (left, top, right, bottom)
* per rectangle
- * @param color The rectangles' ARGB color, defined as a packed 32 bits word
- * @param mode The Skia xfermode to use
+ * @param paint The paint containing the color, blending mode, etc.
* @param ignoreTransform True if the current transform should be ignored
* @param dirty True if calling this method should dirty the current layer
* @param clip True if the rects should be clipped, false otherwise
*/
- status_t drawColorRects(const float* rects, int count, int color,
- SkXfermode::Mode mode, bool ignoreTransform = false,
- bool dirty = true, bool clip = true);
+ status_t drawColorRects(const float* rects, int count, const SkPaint* paint,
+ bool ignoreTransform = false, bool dirty = true, bool clip = true);
/**
* Draws the shape represented by the specified path texture.
@@ -751,7 +645,7 @@ private:
* @param texture The texture reprsenting the shape
* @param paint The paint to draw the shape with
*/
- status_t drawShape(float left, float top, const PathTexture* texture, SkPaint* paint);
+ status_t drawShape(float left, float top, const PathTexture* texture, const SkPaint* paint);
/**
* Draws the specified texture as an alpha bitmap. Alpha bitmaps obey
@@ -762,41 +656,33 @@ private:
* @param top The y coordinate of the bitmap
* @param paint The paint to render with
*/
- void drawAlphaBitmap(Texture* texture, float left, float top, SkPaint* paint);
+ void drawAlphaBitmap(Texture* texture, float left, float top, const SkPaint* paint);
/**
* Renders a strip of polygons with the specified paint, used for tessellated geometry.
*
* @param vertexBuffer The VertexBuffer to be drawn
* @param paint The paint to render with
- * @param useOffset Offset the vertexBuffer (used in drawing non-AA lines)
+ * @param flags flags with which to draw
*/
- status_t drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPaint* paint,
- bool useOffset = false);
+ status_t drawVertexBuffer(float translateX, float translateY, const VertexBuffer& vertexBuffer,
+ const SkPaint* paint, int flags = 0);
/**
- * Renders the convex hull defined by the specified path as a strip of polygons.
- *
- * @param path The hull of the path to draw
- * @param paint The paint to render with
+ * Convenience for translating method
*/
- status_t drawConvexPath(const SkPath& path, SkPaint* paint);
+ status_t drawVertexBuffer(const VertexBuffer& vertexBuffer,
+ const SkPaint* paint, int flags = 0) {
+ return drawVertexBuffer(0.0f, 0.0f, vertexBuffer, paint, flags);
+ }
/**
- * Draws a textured rectangle with the specified texture. The specified coordinates
- * are transformed by the current snapshot's transform matrix.
+ * Renders the convex hull defined by the specified path as a strip of polygons.
*
- * @param left The left coordinate of the rectangle
- * @param top The top coordinate of the rectangle
- * @param right The right coordinate of the rectangle
- * @param bottom The bottom coordinate of the rectangle
- * @param texture The texture name to map onto the rectangle
- * @param alpha An additional translucency parameter, between 0.0f and 1.0f
- * @param mode The blending mode
- * @param blend True if the texture contains an alpha channel
+ * @param path The hull of the path to draw
+ * @param paint The paint to render with
*/
- void drawTextureRect(float left, float top, float right, float bottom, GLuint texture,
- float alpha, SkXfermode::Mode mode, bool blend);
+ status_t drawConvexPath(const SkPath& path, const SkPaint* paint);
/**
* Draws a textured rectangle with the specified texture. The specified coordinates
@@ -810,7 +696,7 @@ private:
* @param paint The paint containing the alpha, blending mode, etc.
*/
void drawTextureRect(float left, float top, float right, float bottom,
- Texture* texture, SkPaint* paint);
+ Texture* texture, const SkPaint* paint);
/**
* Draws a textured mesh with the specified texture. If the indices are omitted,
@@ -822,8 +708,7 @@ private:
* @param right The right coordinate of the rectangle
* @param bottom The bottom coordinate of the rectangle
* @param texture The texture name to map onto the rectangle
- * @param alpha An additional translucency parameter, between 0.0f and 1.0f
- * @param mode The blending mode
+ * @param paint The paint containing the alpha, blending mode, colorFilter, etc.
* @param blend True if the texture contains an alpha channel
* @param vertices The vertices that define the mesh
* @param texCoords The texture coordinates of each vertex
@@ -831,32 +716,33 @@ private:
* @param swapSrcDst Whether or not the src and dst blending operations should be swapped
* @param ignoreTransform True if the current transform should be ignored
* @param vbo The VBO used to draw the mesh
- * @param ignoreScale True if the model view matrix should not be scaled
+ * @param modelViewMode Defines whether the model view matrix should be scaled
* @param dirty True if calling this method should dirty the current layer
*/
void drawTextureMesh(float left, float top, float right, float bottom, GLuint texture,
- float alpha, SkXfermode::Mode mode, bool blend,
+ const SkPaint* paint, bool blend,
GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
bool swapSrcDst = false, bool ignoreTransform = false, GLuint vbo = 0,
- bool ignoreScale = false, bool dirty = true);
+ ModelViewMode modelViewMode = kModelViewMode_TranslateAndScale, bool dirty = true);
void drawIndexedTextureMesh(float left, float top, float right, float bottom, GLuint texture,
- float alpha, SkXfermode::Mode mode, bool blend,
+ const SkPaint* paint, bool blend,
GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
bool swapSrcDst = false, bool ignoreTransform = false, GLuint vbo = 0,
- bool ignoreScale = false, bool dirty = true);
+ ModelViewMode modelViewMode = kModelViewMode_TranslateAndScale, bool dirty = true);
void drawAlpha8TextureMesh(float left, float top, float right, float bottom,
- GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode,
+ GLuint texture, const SkPaint* paint,
GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount,
- bool ignoreTransform, bool ignoreScale = false, bool dirty = true);
+ bool ignoreTransform, ModelViewMode modelViewMode = kModelViewMode_TranslateAndScale,
+ bool dirty = true);
/**
* Draws the specified list of vertices as quads using indexed GL_TRIANGLES.
* If the number of vertices to draw exceeds the number of indices we have
* pre-allocated, this method will generate several glDrawElements() calls.
*/
- void drawIndexedQuads(Vertex* mesh, GLsizei quadsCount);
+ void issueIndexedQuadDraw(Vertex* mesh, GLsizei quadsCount);
/**
* Draws text underline and strike-through if needed.
@@ -868,8 +754,7 @@ private:
* @param y The y coordinate where the text will be drawn
* @param paint The paint to draw the text with
*/
- void drawTextDecorations(const char* text, int bytesCount, float totalAdvance,
- float x, float y, SkPaint* paint);
+ void drawTextDecorations(float totalAdvance, float x, float y, const SkPaint* paint);
/**
* Draws shadow layer on text (with optional positions).
@@ -881,12 +766,11 @@ private:
* @param positions The x, y positions of individual glyphs (or NULL)
* @param fontRenderer The font renderer object
* @param alpha The alpha value for drawing the shadow
- * @param mode The xfermode for drawing the shadow
* @param x The x coordinate where the shadow will be drawn
* @param y The y coordinate where the shadow will be drawn
*/
- void drawTextShadow(SkPaint* paint, const char* text, int bytesCount, int count,
- const float* positions, FontRenderer& fontRenderer, int alpha, SkXfermode::Mode mode,
+ void drawTextShadow(const SkPaint* paint, const char* text, int bytesCount, int count,
+ const float* positions, FontRenderer& fontRenderer, int alpha,
float x, float y);
/**
@@ -898,7 +782,7 @@ private:
* @param y The y coordinate where the texture will be drawn
* @param paint The paint to draw the texture with
*/
- void drawPathTexture(const PathTexture* texture, float x, float y, SkPaint* paint);
+ void drawPathTexture(const PathTexture* texture, float x, float y, const SkPaint* paint);
/**
* Resets the texture coordinates stored in mMeshVertices. Setting the values
@@ -965,38 +849,56 @@ private:
void setupDrawWithTextureAndColor(bool isAlpha8 = false);
void setupDrawWithExternalTexture();
void setupDrawNoTexture();
- void setupDrawAA();
+ void setupDrawVertexAlpha(bool useShadowAlphaInterp);
void setupDrawColor(int color, int alpha);
void setupDrawColor(float r, float g, float b, float a);
void setupDrawAlpha8Color(int color, int alpha);
void setupDrawTextGamma(const SkPaint* paint);
- void setupDrawShader();
- void setupDrawColorFilter();
- void setupDrawBlending(SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode,
- bool swapSrcDst = false);
- void setupDrawBlending(bool blend = true, SkXfermode::Mode mode = SkXfermode::kSrcOver_Mode,
- bool swapSrcDst = false);
+ void setupDrawShader(const SkShader* shader);
+ void setupDrawColorFilter(const SkColorFilter* filter);
+ void setupDrawBlending(const Layer* layer, bool swapSrcDst = false);
+ void setupDrawBlending(const SkPaint* paint, bool blend = true, bool swapSrcDst = false);
void setupDrawProgram();
void setupDrawDirtyRegionsDisabled();
- void setupDrawModelViewIdentity(bool offset = false);
- void setupDrawModelView(float left, float top, float right, float bottom,
- bool ignoreTransform = false, bool ignoreModelView = false);
- void setupDrawModelViewTranslate(float left, float top, float right, float bottom,
- bool ignoreTransform = false);
- void setupDrawColorUniforms();
+
+ /**
+ * Setup the current program matrices based upon the nature of the geometry.
+ *
+ * @param mode If kModelViewMode_Translate, the geometry must be translated by the left and top
+ * parameters. If kModelViewMode_TranslateAndScale, the geometry that exists in the (0,0, 1,1)
+ * space must be scaled up and translated to fill the quad provided in (l,t,r,b). These
+ * transformations are stored in the modelView matrix and uploaded to the shader.
+ *
+ * @param offset Set to true if the the matrix should be fudged (translated) slightly to disambiguate
+ * geometry pixel positioning. See Vertex::GeometryFudgeFactor().
+ *
+ * @param ignoreTransform Set to true if l,t,r,b coordinates already in layer space,
+ * currentTransform() will be ignored. (e.g. when drawing clip in layer coordinates to stencil,
+ * or when simple translation has been extracted)
+ */
+ void setupDrawModelView(ModelViewMode mode, bool offset,
+ float left, float top, float right, float bottom, bool ignoreTransform = false);
+ void setupDrawColorUniforms(bool hasShader);
void setupDrawPureColorUniforms();
- void setupDrawShaderIdentityUniforms();
- void setupDrawShaderUniforms(bool ignoreTransform = false);
- void setupDrawColorFilterUniforms();
+
+ /**
+ * Setup uniforms for the current shader.
+ *
+ * @param shader SkShader on the current paint.
+ *
+ * @param ignoreTransform Set to true to ignore the transform in shader.
+ */
+ void setupDrawShaderUniforms(const SkShader* shader, bool ignoreTransform = false);
+ void setupDrawColorFilterUniforms(const SkColorFilter* paint);
void setupDrawSimpleMesh();
void setupDrawTexture(GLuint texture);
void setupDrawExternalTexture(GLuint texture);
void setupDrawTextureTransform();
void setupDrawTextureTransformUniforms(mat4& transform);
void setupDrawTextGammaUniforms();
- void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords = NULL, GLuint vbo = 0);
- void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLvoid* colors);
- void setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, GLuint vbo = 0);
+ void setupDrawMesh(const GLvoid* vertices, const GLvoid* texCoords = NULL, GLuint vbo = 0);
+ void setupDrawMesh(const GLvoid* vertices, const GLvoid* texCoords, const GLvoid* colors);
+ void setupDrawMeshIndices(const GLvoid* vertices, const GLvoid* texCoords, GLuint vbo = 0);
void setupDrawIndexedVertices(GLvoid* vertices);
void accountForClear(SkXfermode::Mode mode);
@@ -1004,18 +906,19 @@ private:
void updateLayers();
void flushLayers();
+#if DEBUG_LAYERS_AS_REGIONS
/**
* Renders the specified region as a series of rectangles. This method
* is used for debugging only.
*/
- void drawRegionRects(const Region& region);
+ void drawRegionRectsDebug(const Region& region);
+#endif
/**
* Renders the specified region as a series of rectangles. The region
* must be in screen-space coordinates.
*/
- void drawRegionRects(const SkRegion& region, int color, SkXfermode::Mode mode,
- bool dirty = false);
+ void drawRegionRects(const SkRegion& region, const SkPaint& paint, bool dirty = false);
/**
* Draws the current clip region if any. Only when DEBUG_CLIP_REGIONS
@@ -1034,10 +937,6 @@ private:
mDirtyClip = true;
}
- inline mat4& currentTransform() const {
- return *mSnapshot->transform;
- }
-
inline const UvMapper& getMapper(const Texture* texture) {
return texture && texture->uvMapper ? *texture->uvMapper : mUvMapper;
}
@@ -1047,23 +946,24 @@ private:
* come from the texture cache or an atlas. If this method returns
* NULL, the texture could not be found and/or allocated.
*/
- Texture* getTexture(SkBitmap* bitmap);
+ Texture* getTexture(const SkBitmap* bitmap);
- // Dimensions of the drawing surface
- int mWidth, mHeight;
-
- // Matrix used for ortho projection in shaders
- mat4 mOrthoMatrix;
-
- // Model-view matrix used to position/size objects
- mat4 mModelView;
+ /**
+ * Model-view matrix used to position/size objects
+ *
+ * Stores operation-local modifications to the draw matrix that aren't incorporated into the
+ * currentTransform().
+ *
+ * If generated with kModelViewMode_Translate, mModelViewMatrix will reflect an x/y offset,
+ * e.g. the offset in drawLayer(). If generated with kModelViewMode_TranslateAndScale,
+ * mModelViewMatrix will reflect a translation and scale, e.g. the translation and scale
+ * required to make VBO 0 (a rect of (0,0,1,1)) scaled to match the x,y offset, and width/height
+ * of a bitmap.
+ *
+ * Used as input to SkiaShader transformation.
+ */
+ mat4 mModelViewMatrix;
- // Number of saved states
- int mSaveCount;
- // Base state
- sp<Snapshot> mFirstSnapshot;
- // Current state
- sp<Snapshot> mSnapshot;
// State used to define the clipping region
Rect mTilingClip;
// Is the target render surface opaque
@@ -1084,17 +984,13 @@ private:
// Various caches
Caches& mCaches;
Extensions& mExtensions;
+ RenderState& mRenderState;
// List of rectangles to clear after saveLayer() is invoked
Vector<Rect*> mLayers;
- // List of functors to invoke after a frame is drawn
- SortedVector<Functor*> mFunctors;
// List of layers to update at the beginning of a frame
Vector<Layer*> mLayerUpdates;
- // Indicates whether the clip must be restored
- bool mDirtyClip;
-
// The following fields are used to setup drawing
// Used to describe the shaders to generate
ProgramDescription mDescription;
@@ -1122,10 +1018,14 @@ private:
bool mCountOverdraw;
float mOverdraw;
- // Optional name of the renderer
- String8 mName;
+ bool mSkipOutlineClip;
+
+ // Lighting + shadows
+ Vector3 mLightCenter;
+ float mLightRadius;
+ uint8_t mAmbientShadowAlpha;
+ uint8_t mSpotShadowAlpha;
- friend class DisplayListRenderer;
friend class Layer;
friend class TextSetupFunctor;
friend class DrawBitmapOp;
diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h
new file mode 100644
index 0000000..6dacd5e
--- /dev/null
+++ b/libs/hwui/Outline.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef OUTLINE_H
+#define OUTLINE_H
+
+#include <SkPath.h>
+
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+class Outline {
+public:
+ Outline()
+ : mShouldClip(false)
+ , mType(kOutlineType_None)
+ , mRadius(0)
+ , mAlpha(0.0f) {}
+
+ void setRoundRect(int left, int top, int right, int bottom, float radius, float alpha) {
+ mType = kOutlineType_RoundRect;
+ mBounds.set(left, top, right, bottom);
+ mRadius = radius;
+ mPath.reset();
+ mPath.addRoundRect(SkRect::MakeLTRB(left, top, right, bottom),
+ radius, radius);
+ mAlpha = alpha;
+ }
+
+ void setConvexPath(const SkPath* outline, float alpha) {
+ if (!outline) {
+ setEmpty();
+ return;
+ }
+ mType = kOutlineType_ConvexPath;
+ mPath = *outline;
+ mBounds.set(outline->getBounds());
+ mAlpha = alpha;
+ }
+
+ void setEmpty() {
+ mType = kOutlineType_Empty;
+ mPath.reset();
+ mAlpha = 0.0f;
+ }
+
+ void setNone() {
+ mType = kOutlineType_None;
+ mPath.reset();
+ mAlpha = 0.0f;
+ }
+
+ bool isEmpty() const {
+ return mType == kOutlineType_Empty;
+ }
+
+ float getAlpha() const {
+ return mAlpha;
+ }
+
+ void setShouldClip(bool clip) {
+ mShouldClip = clip;
+ }
+
+ bool getShouldClip() const {
+ return mShouldClip;
+ }
+
+ bool willClip() const {
+ // only round rect outlines can be used for clipping
+ return mShouldClip && (mType == kOutlineType_RoundRect);
+ }
+
+ bool getAsRoundRect(Rect* outRect, float* outRadius) const {
+ if (mType == kOutlineType_RoundRect) {
+ outRect->set(mBounds);
+ *outRadius = mRadius;
+ return true;
+ }
+ return false;
+ }
+
+ const SkPath* getPath() const {
+ if (mType == kOutlineType_None || mType == kOutlineType_Empty) return NULL;
+
+ return &mPath;
+ }
+
+private:
+ enum OutlineType {
+ kOutlineType_None = 0,
+ kOutlineType_Empty = 1,
+ kOutlineType_ConvexPath = 2,
+ kOutlineType_RoundRect = 3
+ };
+
+ bool mShouldClip;
+ OutlineType mType;
+ Rect mBounds;
+ float mRadius;
+ float mAlpha;
+ SkPath mPath;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* OUTLINE_H */
diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h
index b5e8838..1ba045d 100644
--- a/libs/hwui/Patch.h
+++ b/libs/hwui/Patch.h
@@ -36,7 +36,8 @@ namespace uirenderer {
// 9-patch structures
///////////////////////////////////////////////////////////////////////////////
-struct Patch {
+class Patch {
+public:
Patch();
~Patch();
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 0794aec..9ba8854 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -19,6 +19,7 @@
#include <SkBitmap.h>
#include <SkCanvas.h>
+#include <SkColor.h>
#include <SkPaint.h>
#include <SkPath.h>
#include <SkRect.h>
@@ -51,7 +52,7 @@ PathDescription::PathDescription():
memset(&shape, 0, sizeof(Shape));
}
-PathDescription::PathDescription(ShapeType type, SkPaint* paint):
+PathDescription::PathDescription(ShapeType type, const SkPaint* paint):
type(type),
join(paint->getStrokeJoin()),
cap(paint->getStrokeCap()),
@@ -74,15 +75,11 @@ hash_t PathDescription::hash() const {
return JenkinsHashWhiten(hash);
}
-int PathDescription::compare(const PathDescription& rhs) const {
- return memcmp(this, &rhs, sizeof(PathDescription));
-}
-
///////////////////////////////////////////////////////////////////////////////
// Utilities
///////////////////////////////////////////////////////////////////////////////
-bool PathCache::canDrawAsConvexPath(SkPath* path, SkPaint* paint) {
+bool PathCache::canDrawAsConvexPath(SkPath* path, const SkPaint* paint) {
// NOTE: This should only be used after PathTessellator handles joins properly
return paint->getPathEffect() == NULL && path->getConvexity() == SkPath::kConvex_Convexity;
}
@@ -108,15 +105,14 @@ void PathCache::computeBounds(const SkRect& bounds, const SkPaint* paint,
}
static void initBitmap(SkBitmap& bitmap, uint32_t width, uint32_t height) {
- bitmap.setConfig(SkBitmap::kA8_Config, width, height);
- bitmap.allocPixels();
+ bitmap.allocPixels(SkImageInfo::MakeA8(width, height));
bitmap.eraseColor(0);
}
static void initPaint(SkPaint& paint) {
// Make sure the paint is opaque, color, alpha, filter, etc.
// will be applied later when compositing the alpha8 texture
- paint.setColor(0xff000000);
+ paint.setColor(SK_ColorBLACK);
paint.setAlpha(255);
paint.setColorFilter(NULL);
paint.setMaskFilter(NULL);
@@ -163,14 +159,7 @@ PathCache::PathCache():
} else {
INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_PATH_CACHE_SIZE);
}
- init();
-}
-PathCache::~PathCache() {
- mCache.clear();
-}
-
-void PathCache::init() {
mCache.setOnEntryRemovedListener(this);
GLint maxTextureSize;
@@ -180,6 +169,10 @@ void PathCache::init() {
mDebugEnabled = readDebugLevel() & kDebugCaches;
}
+PathCache::~PathCache() {
+ mCache.clear();
+}
+
///////////////////////////////////////////////////////////////////////////////
// Size management
///////////////////////////////////////////////////////////////////////////////
@@ -341,12 +334,12 @@ PathCache::PathProcessor::PathProcessor(Caches& caches):
}
void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
- sp<PathTask> t = static_cast<PathTask* >(task.get());
+ PathTask* t = static_cast<PathTask*>(task.get());
ATRACE_NAME("pathPrecache");
float left, top, offset;
uint32_t width, height;
- PathCache::computePathBounds(t->path, &t->paint, left, top, offset, width, height);
+ PathCache::computePathBounds(&t->path, &t->paint, left, top, offset, width, height);
PathTexture* texture = t->texture;
texture->left = left;
@@ -357,7 +350,7 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
if (width <= mMaxTextureSize && height <= mMaxTextureSize) {
SkBitmap* bitmap = new SkBitmap();
- drawPath(t->path, &t->paint, *bitmap, left, top, offset, width, height);
+ drawPath(&t->path, &t->paint, *bitmap, left, top, offset, width, height);
t->setResult(bitmap);
} else {
texture->width = 0;
@@ -415,7 +408,7 @@ void PathCache::clearGarbage() {
* in the cache. The source path is also used to reclaim garbage when a
* Dalvik Path object is collected.
*/
-static SkPath* getSourcePath(SkPath* path) {
+static const SkPath* getSourcePath(const SkPath* path) {
const SkPath* sourcePath = path->getSourcePath();
if (sourcePath && sourcePath->getGenerationID() == path->getGenerationID()) {
return const_cast<SkPath*>(sourcePath);
@@ -423,7 +416,7 @@ static SkPath* getSourcePath(SkPath* path) {
return path;
}
-PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
+PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) {
path = getSourcePath(path);
PathDescription entry(kShapePath, paint);
@@ -461,7 +454,7 @@ PathTexture* PathCache::get(SkPath* path, SkPaint* paint) {
return texture;
}
-void PathCache::precache(SkPath* path, SkPaint* paint) {
+void PathCache::precache(const SkPath* path, const SkPaint* paint) {
if (!Caches::getInstance().tasks.canRunTasks()) {
return;
}
@@ -509,7 +502,7 @@ void PathCache::precache(SkPath* path, SkPaint* paint) {
///////////////////////////////////////////////////////////////////////////////
PathTexture* PathCache::getRoundRect(float width, float height,
- float rx, float ry, SkPaint* paint) {
+ float rx, float ry, const SkPaint* paint) {
PathDescription entry(kShapeRoundRect, paint);
entry.shape.roundRect.mWidth = width;
entry.shape.roundRect.mHeight = height;
@@ -534,7 +527,7 @@ PathTexture* PathCache::getRoundRect(float width, float height,
// Circles
///////////////////////////////////////////////////////////////////////////////
-PathTexture* PathCache::getCircle(float radius, SkPaint* paint) {
+PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) {
PathDescription entry(kShapeCircle, paint);
entry.shape.circle.mRadius = radius;
@@ -554,7 +547,7 @@ PathTexture* PathCache::getCircle(float radius, SkPaint* paint) {
// Ovals
///////////////////////////////////////////////////////////////////////////////
-PathTexture* PathCache::getOval(float width, float height, SkPaint* paint) {
+PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint) {
PathDescription entry(kShapeOval, paint);
entry.shape.oval.mWidth = width;
entry.shape.oval.mHeight = height;
@@ -577,7 +570,7 @@ PathTexture* PathCache::getOval(float width, float height, SkPaint* paint) {
// Rects
///////////////////////////////////////////////////////////////////////////////
-PathTexture* PathCache::getRect(float width, float height, SkPaint* paint) {
+PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint) {
PathDescription entry(kShapeRect, paint);
entry.shape.rect.mWidth = width;
entry.shape.rect.mHeight = height;
@@ -601,7 +594,7 @@ PathTexture* PathCache::getRect(float width, float height, SkPaint* paint) {
///////////////////////////////////////////////////////////////////////////////
PathTexture* PathCache::getArc(float width, float height,
- float startAngle, float sweepAngle, bool useCenter, SkPaint* paint) {
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) {
PathDescription entry(kShapeArc, paint);
entry.shape.arc.mWidth = width;
entry.shape.arc.mHeight = height;
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index 24f88f1..bc34188 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -26,13 +26,14 @@
#include "Debug.h"
#include "Properties.h"
#include "Texture.h"
+#include "utils/Macros.h"
#include "utils/Pair.h"
class SkBitmap;
class SkCanvas;
class SkPaint;
class SkPath;
-class SkRect;
+struct SkRect;
namespace android {
namespace uirenderer {
@@ -107,6 +108,7 @@ enum ShapeType {
};
struct PathDescription {
+ DESCRIPTION_TYPE(PathDescription);
ShapeType type;
SkPaint::Join join;
SkPaint::Cap cap;
@@ -116,7 +118,7 @@ struct PathDescription {
SkPathEffect* pathEffect;
union Shape {
struct Path {
- SkPath* mPath;
+ const SkPath* mPath;
} path;
struct RoundRect {
float mWidth;
@@ -145,32 +147,9 @@ struct PathDescription {
} shape;
PathDescription();
- PathDescription(ShapeType shapeType, SkPaint* paint);
+ PathDescription(ShapeType shapeType, const SkPaint* paint);
hash_t hash() const;
-
- int compare(const PathDescription& rhs) const;
-
- bool operator==(const PathDescription& other) const {
- return compare(other) == 0;
- }
-
- bool operator!=(const PathDescription& other) const {
- return compare(other) != 0;
- }
-
- friend inline int strictly_order_type(
- const PathDescription& lhs, const PathDescription& rhs) {
- return lhs.compare(rhs) < 0;
- }
-
- friend inline int compare_type(const PathDescription& lhs, const PathDescription& rhs) {
- return lhs.compare(rhs);
- }
-
- friend inline hash_t hash_type(const PathDescription& entry) {
- return entry.hash();
- }
};
/**
@@ -207,13 +186,13 @@ public:
*/
uint32_t getSize();
- PathTexture* getRoundRect(float width, float height, float rx, float ry, SkPaint* paint);
- PathTexture* getCircle(float radius, SkPaint* paint);
- PathTexture* getOval(float width, float height, SkPaint* paint);
- PathTexture* getRect(float width, float height, SkPaint* paint);
+ PathTexture* getRoundRect(float width, float height, float rx, float ry, const SkPaint* paint);
+ PathTexture* getCircle(float radius, const SkPaint* paint);
+ PathTexture* getOval(float width, float height, const SkPaint* paint);
+ PathTexture* getRect(float width, float height, const SkPaint* paint);
PathTexture* getArc(float width, float height, float startAngle, float sweepAngle,
- bool useCenter, SkPaint* paint);
- PathTexture* get(SkPath* path, SkPaint* paint);
+ bool useCenter, const SkPaint* paint);
+ PathTexture* get(const SkPath* path, const SkPaint* paint);
/**
* Removes the specified path. This is meant to be called from threads
@@ -239,9 +218,9 @@ public:
/**
* Precaches the specified path using background threads.
*/
- void precache(SkPath* path, SkPaint* paint);
+ void precache(const SkPath* path, const SkPaint* paint);
- static bool canDrawAsConvexPath(SkPath* path, SkPaint* paint);
+ static bool canDrawAsConvexPath(SkPath* path, const SkPaint* paint);
static void computePathBounds(const SkPath* path, const SkPaint* paint,
float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
static void computeBounds(const SkRect& bounds, const SkPaint* paint,
@@ -292,17 +271,20 @@ private:
class PathTask: public Task<SkBitmap*> {
public:
- PathTask(SkPath* path, SkPaint* paint, PathTexture* texture):
- path(path), paint(*paint), texture(texture) {
+ PathTask(const SkPath* path, const SkPaint* paint, PathTexture* texture):
+ path(*path), paint(*paint), texture(texture) {
}
~PathTask() {
delete future()->get();
}
- SkPath* path;
- //copied, since input paint may not be immutable
- SkPaint paint;
+ // copied, since input path not refcounted / guaranteed to survive for duration of task
+ // TODO: avoid deep copy with refcounting
+ const SkPath path;
+
+ // copied, since input paint may not be immutable
+ const SkPaint paint;
PathTexture* texture;
};
diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp
index 3970913..e30ac19 100644
--- a/libs/hwui/PathTessellator.cpp
+++ b/libs/hwui/PathTessellator.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#define LOG_TAG "PathTessellator"
+#define LOG_TAG "OpenGLRenderer"
#define LOG_NDEBUG 1
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#define ATRACE_TAG ATRACE_TAG_VIEW
#define VERTEX_DEBUG 0
@@ -24,11 +24,11 @@
#define DEBUG_DUMP_ALPHA_BUFFER() \
for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { \
ALOGD("point %d at %f %f, alpha %f", \
- i, buffer[i].position[0], buffer[i].position[1], buffer[i].alpha); \
+ i, buffer[i].x, buffer[i].y, buffer[i].alpha); \
}
#define DEBUG_DUMP_BUFFER() \
for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { \
- ALOGD("point %d at %f %f", i, buffer[i].position[0], buffer[i].position[1]); \
+ ALOGD("point %d at %f %f", i, buffer[i].x, buffer[i].y); \
}
#else
#define DEBUG_DUMP_ALPHA_BUFFER()
@@ -49,31 +49,34 @@
#include "Matrix.h"
#include "Vector.h"
#include "Vertex.h"
+#include "utils/MathUtils.h"
namespace android {
namespace uirenderer {
-#define THRESHOLD 0.5f
+#define OUTLINE_REFINE_THRESHOLD_SQUARED (0.5f * 0.5f)
#define ROUND_CAP_THRESH 0.25f
#define PI 3.1415926535897932f
+#define MAX_DEPTH 15
-void PathTessellator::expandBoundsForStroke(SkRect& bounds, const SkPaint* paint,
- bool forceExpand) {
- if (forceExpand || paint->getStyle() != SkPaint::kFill_Style) {
- float outset = paint->getStrokeWidth() * 0.5f;
- if (outset == 0) outset = 0.5f; // account for hairline
- bounds.outset(outset, outset);
+/**
+ * Extracts the x and y scale from the transform as positive values, and clamps them
+ */
+void PathTessellator::extractTessellationScales(const Matrix4& transform,
+ float* scaleX, float* scaleY) {
+ if (CC_LIKELY(transform.isPureTranslate())) {
+ *scaleX = 1.0f;
+ *scaleY = 1.0f;
+ } else {
+ float m00 = transform.data[Matrix4::kScaleX];
+ float m01 = transform.data[Matrix4::kSkewY];
+ float m10 = transform.data[Matrix4::kSkewX];
+ float m11 = transform.data[Matrix4::kScaleY];
+ *scaleX = MathUtils::clampTessellationScale(sqrt(m00 * m00 + m01 * m01));
+ *scaleY = MathUtils::clampTessellationScale(sqrt(m10 * m10 + m11 * m11));
}
}
-inline static void copyVertex(Vertex* destPtr, const Vertex* srcPtr) {
- Vertex::set(destPtr, srcPtr->position[0], srcPtr->position[1]);
-}
-
-inline static void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) {
- AlphaVertex::set(destPtr, srcPtr->position[0], srcPtr->position[1], srcPtr->alpha);
-}
-
/**
* Produces a pseudo-normal for a vertex, given the normals of the two incoming lines. If the offset
* from each vertex in a perimeter is calculated, the resultant lines connecting the offset vertices
@@ -84,7 +87,7 @@ inline static void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcP
*
* NOTE: assumes angles between normals 90 degrees or less
*/
-inline static vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& normalB) {
+inline static Vector2 totalOffsetFromNormals(const Vector2& normalA, const Vector2& normalB) {
return (normalA + normalB) / (1 + fabs(normalA.dot(normalB)));
}
@@ -93,24 +96,23 @@ inline static vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& norma
*/
struct PaintInfo {
public:
- PaintInfo(const SkPaint* paint, const mat4 *transform) :
+ PaintInfo(const SkPaint* paint, const mat4& transform) :
style(paint->getStyle()), cap(paint->getStrokeCap()), isAA(paint->isAntiAlias()),
- inverseScaleX(1.0f), inverseScaleY(1.0f),
halfStrokeWidth(paint->getStrokeWidth() * 0.5f), maxAlpha(1.0f) {
// compute inverse scales
- if (CC_UNLIKELY(!transform->isPureTranslate())) {
- float m00 = transform->data[Matrix4::kScaleX];
- float m01 = transform->data[Matrix4::kSkewY];
- float m10 = transform->data[Matrix4::kSkewX];
- float m11 = transform->data[Matrix4::kScaleY];
- float scaleX = sqrt(m00 * m00 + m01 * m01);
- float scaleY = sqrt(m10 * m10 + m11 * m11);
- inverseScaleX = (scaleX != 0) ? (1.0f / scaleX) : 1.0f;
- inverseScaleY = (scaleY != 0) ? (1.0f / scaleY) : 1.0f;
+ if (CC_LIKELY(transform.isPureTranslate())) {
+ inverseScaleX = 1.0f;
+ inverseScaleY = 1.0f;
+ } else {
+ float scaleX, scaleY;
+ PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
+ inverseScaleX = 1.0f / scaleX;
+ inverseScaleY = 1.0f / scaleY;
}
if (isAA && halfStrokeWidth != 0 && inverseScaleX == inverseScaleY &&
2 * halfStrokeWidth < inverseScaleX) {
+ // AA, with non-hairline stroke, width < 1 pixel. Scale alpha and treat as hairline.
maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX;
halfStrokeWidth = 0.0f;
}
@@ -124,7 +126,7 @@ public:
float halfStrokeWidth;
float maxAlpha;
- inline void scaleOffsetForStrokeWidth(vec2& offset) const {
+ inline void scaleOffsetForStrokeWidth(Vector2& offset) const {
if (halfStrokeWidth == 0.0f) {
// hairline - compensate for scale
offset.x *= 0.5f * inverseScaleX;
@@ -138,9 +140,8 @@ public:
* NOTE: the input will not always be a normal, especially for sharp edges - it should be the
* result of totalOffsetFromNormals (see documentation there)
*/
- inline vec2 deriveAAOffset(const vec2& offset) const {
- return vec2(offset.x * 0.5f * inverseScaleX,
- offset.y * 0.5f * inverseScaleY);
+ inline Vector2 deriveAAOffset(const Vector2& offset) const {
+ return (Vector2){offset.x * 0.5f * inverseScaleX, offset.y * 0.5f * inverseScaleY};
}
/**
@@ -159,6 +160,17 @@ public:
}
return 0;
}
+
+ /**
+ * Outset the bounds of point data (for line endpoints or points) to account for AA stroke
+ * geometry.
+ */
+ void expandBoundsForStroke(Rect* bounds) const {
+ float outset = halfStrokeWidth;
+ if (outset == 0) outset = 0.5f;
+ bounds->outset(outset * inverseScaleX + Vertex::GeometryFudgeFactor(),
+ outset * inverseScaleY + Vertex::GeometryFudgeFactor());
+ }
};
void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) {
@@ -170,9 +182,9 @@ void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer&
int srcAindex = 0;
int srcBindex = perimeter.size() - 1;
while (srcAindex <= srcBindex) {
- copyVertex(&buffer[currentIndex++], &perimeter[srcAindex]);
+ buffer[currentIndex++] = perimeter[srcAindex];
if (srcAindex == srcBindex) break;
- copyVertex(&buffer[currentIndex++], &perimeter[srcBindex]);
+ buffer[currentIndex++] = perimeter[srcBindex];
srcAindex++;
srcBindex--;
}
@@ -192,25 +204,23 @@ void getStrokeVerticesFromPerimeter(const PaintInfo& paintInfo, const Vector<Ver
int currentIndex = 0;
const Vertex* last = &(perimeter[perimeter.size() - 1]);
const Vertex* current = &(perimeter[0]);
- vec2 lastNormal(current->position[1] - last->position[1],
- last->position[0] - current->position[0]);
+ Vector2 lastNormal = {current->y - last->y, last->x - current->x};
lastNormal.normalize();
for (unsigned int i = 0; i < perimeter.size(); i++) {
const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
- vec2 nextNormal(next->position[1] - current->position[1],
- current->position[0] - next->position[0]);
+ Vector2 nextNormal = {next->y - current->y, current->x - next->x};
nextNormal.normalize();
- vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
paintInfo.scaleOffsetForStrokeWidth(totalOffset);
Vertex::set(&buffer[currentIndex++],
- current->position[0] + totalOffset.x,
- current->position[1] + totalOffset.y);
+ current->x + totalOffset.x,
+ current->y + totalOffset.y);
Vertex::set(&buffer[currentIndex++],
- current->position[0] - totalOffset.x,
- current->position[1] - totalOffset.y);
+ current->x - totalOffset.x,
+ current->y - totalOffset.y);
last = current;
current = next;
@@ -218,20 +228,21 @@ void getStrokeVerticesFromPerimeter(const PaintInfo& paintInfo, const Vector<Ver
}
// wrap around to beginning
- copyVertex(&buffer[currentIndex++], &buffer[0]);
- copyVertex(&buffer[currentIndex++], &buffer[1]);
+ buffer[currentIndex++] = buffer[0];
+ buffer[currentIndex++] = buffer[1];
DEBUG_DUMP_BUFFER();
}
static inline void storeBeginEnd(const PaintInfo& paintInfo, const Vertex& center,
- const vec2& normal, Vertex* buffer, int& currentIndex, bool begin) {
- vec2 strokeOffset = normal;
+ const Vector2& normal, Vertex* buffer, int& currentIndex, bool begin) {
+ Vector2 strokeOffset = normal;
paintInfo.scaleOffsetForStrokeWidth(strokeOffset);
- vec2 referencePoint(center.position[0], center.position[1]);
+ Vector2 referencePoint = {center.x, center.y};
if (paintInfo.cap == SkPaint::kSquare_Cap) {
- referencePoint += vec2(-strokeOffset.y, strokeOffset.x) * (begin ? -1 : 1);
+ Vector2 rotated = {-strokeOffset.y, strokeOffset.x};
+ referencePoint += rotated * (begin ? -1 : 1);
}
Vertex::set(&buffer[currentIndex++], referencePoint + strokeOffset);
@@ -255,11 +266,11 @@ void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo,
if (extra > 0) {
// tessellate both round caps
float beginTheta = atan2(
- - (vertices[0].position[0] - vertices[1].position[0]),
- vertices[0].position[1] - vertices[1].position[1]);
+ - (vertices[0].x - vertices[1].x),
+ vertices[0].y - vertices[1].y);
float endTheta = atan2(
- - (vertices[lastIndex].position[0] - vertices[lastIndex - 1].position[0]),
- vertices[lastIndex].position[1] - vertices[lastIndex - 1].position[1]);
+ - (vertices[lastIndex].x - vertices[lastIndex - 1].x),
+ vertices[lastIndex].y - vertices[lastIndex - 1].y);
const float dTheta = PI / (extra + 1);
const float radialScale = 2.0f / (1 + cos(dTheta));
@@ -272,40 +283,38 @@ void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo,
}
beginTheta += dTheta;
- vec2 beginRadialOffset(cos(beginTheta), sin(beginTheta));
+ Vector2 beginRadialOffset = {cos(beginTheta), sin(beginTheta)};
paintInfo.scaleOffsetForStrokeWidth(beginRadialOffset);
Vertex::set(&buffer[capOffset],
- vertices[0].position[0] + beginRadialOffset.x,
- vertices[0].position[1] + beginRadialOffset.y);
+ vertices[0].x + beginRadialOffset.x,
+ vertices[0].y + beginRadialOffset.y);
endTheta += dTheta;
- vec2 endRadialOffset(cos(endTheta), sin(endTheta));
+ Vector2 endRadialOffset = {cos(endTheta), sin(endTheta)};
paintInfo.scaleOffsetForStrokeWidth(endRadialOffset);
Vertex::set(&buffer[allocSize - 1 - capOffset],
- vertices[lastIndex].position[0] + endRadialOffset.x,
- vertices[lastIndex].position[1] + endRadialOffset.y);
+ vertices[lastIndex].x + endRadialOffset.x,
+ vertices[lastIndex].y + endRadialOffset.y);
}
}
int currentIndex = extra;
const Vertex* last = &(vertices[0]);
const Vertex* current = &(vertices[1]);
- vec2 lastNormal(current->position[1] - last->position[1],
- last->position[0] - current->position[0]);
+ Vector2 lastNormal = {current->y - last->y, last->x - current->x};
lastNormal.normalize();
storeBeginEnd(paintInfo, vertices[0], lastNormal, buffer, currentIndex, true);
for (unsigned int i = 1; i < vertices.size() - 1; i++) {
const Vertex* next = &(vertices[i + 1]);
- vec2 nextNormal(next->position[1] - current->position[1],
- current->position[0] - next->position[0]);
+ Vector2 nextNormal = {next->y - current->y, current->x - next->x};
nextNormal.normalize();
- vec2 strokeOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ Vector2 strokeOffset = totalOffsetFromNormals(lastNormal, nextNormal);
paintInfo.scaleOffsetForStrokeWidth(strokeOffset);
- vec2 center(current->position[0], current->position[1]);
+ Vector2 center = {current->x, current->y};
Vertex::set(&buffer[currentIndex++], center + strokeOffset);
Vertex::set(&buffer[currentIndex++], center - strokeOffset);
@@ -329,7 +338,7 @@ void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo,
* 3 - zig zag back and forth inside the shape to fill it (using perimeter.size() vertices)
*/
void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter,
- VertexBuffer& vertexBuffer) {
+ VertexBuffer& vertexBuffer, float maxAlpha = 1.0f) {
AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2);
// generate alpha points - fill Alpha vertex gaps in between each point with
@@ -337,27 +346,25 @@ void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Ver
int currentIndex = 0;
const Vertex* last = &(perimeter[perimeter.size() - 1]);
const Vertex* current = &(perimeter[0]);
- vec2 lastNormal(current->position[1] - last->position[1],
- last->position[0] - current->position[0]);
+ Vector2 lastNormal = {current->y - last->y, last->x - current->x};
lastNormal.normalize();
for (unsigned int i = 0; i < perimeter.size(); i++) {
const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
- vec2 nextNormal(next->position[1] - current->position[1],
- current->position[0] - next->position[0]);
+ Vector2 nextNormal = {next->y - current->y, current->x - next->x};
nextNormal.normalize();
// AA point offset from original point is that point's normal, such that each side is offset
// by .5 pixels
- vec2 totalOffset = paintInfo.deriveAAOffset(totalOffsetFromNormals(lastNormal, nextNormal));
+ Vector2 totalOffset = paintInfo.deriveAAOffset(totalOffsetFromNormals(lastNormal, nextNormal));
AlphaVertex::set(&buffer[currentIndex++],
- current->position[0] + totalOffset.x,
- current->position[1] + totalOffset.y,
+ current->x + totalOffset.x,
+ current->y + totalOffset.y,
0.0f);
AlphaVertex::set(&buffer[currentIndex++],
- current->position[0] - totalOffset.x,
- current->position[1] - totalOffset.y,
- 1.0f);
+ current->x - totalOffset.x,
+ current->y - totalOffset.y,
+ maxAlpha);
last = current;
current = next;
@@ -365,8 +372,8 @@ void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Ver
}
// wrap around to beginning
- copyAlphaVertex(&buffer[currentIndex++], &buffer[0]);
- copyAlphaVertex(&buffer[currentIndex++], &buffer[1]);
+ buffer[currentIndex++] = buffer[0];
+ buffer[currentIndex++] = buffer[1];
// zig zag between all previous points on the inside of the hull to create a
// triangle strip that fills the hull, repeating the first inner point to
@@ -374,9 +381,9 @@ void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Ver
int srcAindex = 0;
int srcBindex = perimeter.size() - 1;
while (srcAindex <= srcBindex) {
- copyAlphaVertex(&buffer[currentIndex++], &buffer[srcAindex * 2 + 1]);
+ buffer[currentIndex++] = buffer[srcAindex * 2 + 1];
if (srcAindex == srcBindex) break;
- copyAlphaVertex(&buffer[currentIndex++], &buffer[srcBindex * 2 + 1]);
+ buffer[currentIndex++] = buffer[srcBindex * 2 + 1];
srcAindex++;
srcBindex--;
}
@@ -391,7 +398,7 @@ void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Ver
* getStrokeVerticesFromUnclosedVerticesAA() below.
*/
inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>& vertices,
- AlphaVertex* buffer, bool isFirst, vec2 normal, int offset) {
+ AlphaVertex* buffer, bool isFirst, Vector2 normal, int offset) {
const int extra = paintInfo.capExtraDivisions();
const int extraOffset = (extra + 1) / 2;
const int capIndex = isFirst
@@ -400,27 +407,28 @@ inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>&
if (isFirst) normal *= -1;
// TODO: this normal should be scaled by radialScale if extra != 0, see totalOffsetFromNormals()
- vec2 AAOffset = paintInfo.deriveAAOffset(normal);
+ Vector2 AAOffset = paintInfo.deriveAAOffset(normal);
- vec2 strokeOffset = normal;
+ Vector2 strokeOffset = normal;
paintInfo.scaleOffsetForStrokeWidth(strokeOffset);
- vec2 outerOffset = strokeOffset + AAOffset;
- vec2 innerOffset = strokeOffset - AAOffset;
+ Vector2 outerOffset = strokeOffset + AAOffset;
+ Vector2 innerOffset = strokeOffset - AAOffset;
- vec2 capAAOffset;
+ Vector2 capAAOffset = {0, 0};
if (paintInfo.cap != SkPaint::kRound_Cap) {
// if the cap is square or butt, the inside primary cap vertices will be inset in two
// directions - both normal to the stroke, and parallel to it.
- capAAOffset = vec2(-AAOffset.y, AAOffset.x);
+ capAAOffset = (Vector2){-AAOffset.y, AAOffset.x};
}
// determine referencePoint, the center point for the 4 primary cap vertices
const Vertex* point = isFirst ? vertices.begin() : (vertices.end() - 1);
- vec2 referencePoint(point->position[0], point->position[1]);
+ Vector2 referencePoint = {point->x, point->y};
if (paintInfo.cap == SkPaint::kSquare_Cap) {
// To account for square cap, move the primary cap vertices (that create the AA edge) by the
// stroke offset vector (rotated to be parallel to the stroke)
- referencePoint += vec2(-strokeOffset.y, strokeOffset.x);
+ Vector2 rotated = {-strokeOffset.y, strokeOffset.x};
+ referencePoint += rotated;
}
AlphaVertex::set(&buffer[capIndex + 0],
@@ -453,7 +461,7 @@ inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>&
for (int i = 0; i < extra; i++) {
theta += dTheta;
- vec2 radialOffset(cos(theta), sin(theta));
+ Vector2 radialOffset = {cos(theta), sin(theta)};
// scale to compensate for pinching at sharp angles, see totalOffsetFromNormals()
radialOffset *= radialScale;
@@ -471,8 +479,8 @@ inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>&
if (isFirst && i == extra - extraOffset) {
//copy most recent two points to first two points
- copyAlphaVertex(&buffer[0], &buffer[capPerimIndex - 2]);
- copyAlphaVertex(&buffer[1], &buffer[capPerimIndex - 1]);
+ buffer[0] = buffer[capPerimIndex - 2];
+ buffer[1] = buffer[capPerimIndex - 1];
capPerimIndex = 2; // start writing the rest of the round cap at index 2
}
@@ -482,28 +490,28 @@ inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>&
const int startCapFillIndex = capIndex + 2 * (extra - extraOffset) + 4;
int capFillIndex = startCapFillIndex;
for (int i = 0; i < extra + 2; i += 2) {
- copyAlphaVertex(&buffer[capFillIndex++], &buffer[1 + i]);
+ buffer[capFillIndex++] = buffer[1 + i];
// TODO: to support odd numbers of divisions, break here on the last iteration
- copyAlphaVertex(&buffer[capFillIndex++], &buffer[startCapFillIndex - 3 - i]);
+ buffer[capFillIndex++] = buffer[startCapFillIndex - 3 - i];
}
} else {
int capFillIndex = 6 * vertices.size() + 2 + 6 * extra - (extra + 2);
for (int i = 0; i < extra + 2; i += 2) {
- copyAlphaVertex(&buffer[capFillIndex++], &buffer[capIndex + 1 + i]);
+ buffer[capFillIndex++] = buffer[capIndex + 1 + i];
// TODO: to support odd numbers of divisions, break here on the last iteration
- copyAlphaVertex(&buffer[capFillIndex++], &buffer[capIndex + 3 + 2 * extra - i]);
+ buffer[capFillIndex++] = buffer[capIndex + 3 + 2 * extra - i];
}
}
return;
}
if (isFirst) {
- copyAlphaVertex(&buffer[0], &buffer[postCapIndex + 2]);
- copyAlphaVertex(&buffer[1], &buffer[postCapIndex + 3]);
- copyAlphaVertex(&buffer[postCapIndex + 4], &buffer[1]); // degenerate tris (the only two!)
- copyAlphaVertex(&buffer[postCapIndex + 5], &buffer[postCapIndex + 1]);
+ buffer[0] = buffer[postCapIndex + 2];
+ buffer[1] = buffer[postCapIndex + 3];
+ buffer[postCapIndex + 4] = buffer[1]; // degenerate tris (the only two!)
+ buffer[postCapIndex + 5] = buffer[postCapIndex + 1];
} else {
- copyAlphaVertex(&buffer[6 * vertices.size()], &buffer[postCapIndex + 1]);
- copyAlphaVertex(&buffer[6 * vertices.size() + 1], &buffer[postCapIndex + 3]);
+ buffer[6 * vertices.size()] = buffer[postCapIndex + 1];
+ buffer[6 * vertices.size() + 1] = buffer[postCapIndex + 3];
}
}
@@ -576,8 +584,7 @@ void getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo& paintInfo,
const Vertex* last = &(vertices[0]);
const Vertex* current = &(vertices[1]);
- vec2 lastNormal(current->position[1] - last->position[1],
- last->position[0] - current->position[0]);
+ Vector2 lastNormal = {current->y - last->y, last->x - current->x};
lastNormal.normalize();
// TODO: use normal from bezier traversal for cap, instead of from vertices
@@ -585,43 +592,42 @@ void getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo& paintInfo,
for (unsigned int i = 1; i < vertices.size() - 1; i++) {
const Vertex* next = &(vertices[i + 1]);
- vec2 nextNormal(next->position[1] - current->position[1],
- current->position[0] - next->position[0]);
+ Vector2 nextNormal = {next->y - current->y, current->x - next->x};
nextNormal.normalize();
- vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
- vec2 AAOffset = paintInfo.deriveAAOffset(totalOffset);
+ Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ Vector2 AAOffset = paintInfo.deriveAAOffset(totalOffset);
- vec2 innerOffset = totalOffset;
+ Vector2 innerOffset = totalOffset;
paintInfo.scaleOffsetForStrokeWidth(innerOffset);
- vec2 outerOffset = innerOffset + AAOffset;
+ Vector2 outerOffset = innerOffset + AAOffset;
innerOffset -= AAOffset;
AlphaVertex::set(&buffer[currentAAOuterIndex++],
- current->position[0] + outerOffset.x,
- current->position[1] + outerOffset.y,
+ current->x + outerOffset.x,
+ current->y + outerOffset.y,
0.0f);
AlphaVertex::set(&buffer[currentAAOuterIndex++],
- current->position[0] + innerOffset.x,
- current->position[1] + innerOffset.y,
+ current->x + innerOffset.x,
+ current->y + innerOffset.y,
paintInfo.maxAlpha);
AlphaVertex::set(&buffer[currentStrokeIndex++],
- current->position[0] + innerOffset.x,
- current->position[1] + innerOffset.y,
+ current->x + innerOffset.x,
+ current->y + innerOffset.y,
paintInfo.maxAlpha);
AlphaVertex::set(&buffer[currentStrokeIndex++],
- current->position[0] - innerOffset.x,
- current->position[1] - innerOffset.y,
+ current->x - innerOffset.x,
+ current->y - innerOffset.y,
paintInfo.maxAlpha);
AlphaVertex::set(&buffer[currentAAInnerIndex--],
- current->position[0] - innerOffset.x,
- current->position[1] - innerOffset.y,
+ current->x - innerOffset.x,
+ current->y - innerOffset.y,
paintInfo.maxAlpha);
AlphaVertex::set(&buffer[currentAAInnerIndex--],
- current->position[0] - outerOffset.x,
- current->position[1] - outerOffset.y,
+ current->x - outerOffset.x,
+ current->y - outerOffset.y,
0.0f);
current = next;
@@ -646,48 +652,46 @@ void getStrokeVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<V
const Vertex* last = &(perimeter[perimeter.size() - 1]);
const Vertex* current = &(perimeter[0]);
- vec2 lastNormal(current->position[1] - last->position[1],
- last->position[0] - current->position[0]);
+ Vector2 lastNormal = {current->y - last->y, last->x - current->x};
lastNormal.normalize();
for (unsigned int i = 0; i < perimeter.size(); i++) {
const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]);
- vec2 nextNormal(next->position[1] - current->position[1],
- current->position[0] - next->position[0]);
+ Vector2 nextNormal = {next->y - current->y, current->x - next->x};
nextNormal.normalize();
- vec2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
- vec2 AAOffset = paintInfo.deriveAAOffset(totalOffset);
+ Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal);
+ Vector2 AAOffset = paintInfo.deriveAAOffset(totalOffset);
- vec2 innerOffset = totalOffset;
+ Vector2 innerOffset = totalOffset;
paintInfo.scaleOffsetForStrokeWidth(innerOffset);
- vec2 outerOffset = innerOffset + AAOffset;
+ Vector2 outerOffset = innerOffset + AAOffset;
innerOffset -= AAOffset;
AlphaVertex::set(&buffer[currentAAOuterIndex++],
- current->position[0] + outerOffset.x,
- current->position[1] + outerOffset.y,
+ current->x + outerOffset.x,
+ current->y + outerOffset.y,
0.0f);
AlphaVertex::set(&buffer[currentAAOuterIndex++],
- current->position[0] + innerOffset.x,
- current->position[1] + innerOffset.y,
+ current->x + innerOffset.x,
+ current->y + innerOffset.y,
paintInfo.maxAlpha);
AlphaVertex::set(&buffer[currentStrokeIndex++],
- current->position[0] + innerOffset.x,
- current->position[1] + innerOffset.y,
+ current->x + innerOffset.x,
+ current->y + innerOffset.y,
paintInfo.maxAlpha);
AlphaVertex::set(&buffer[currentStrokeIndex++],
- current->position[0] - innerOffset.x,
- current->position[1] - innerOffset.y,
+ current->x - innerOffset.x,
+ current->y - innerOffset.y,
paintInfo.maxAlpha);
AlphaVertex::set(&buffer[currentAAInnerIndex++],
- current->position[0] - innerOffset.x,
- current->position[1] - innerOffset.y,
+ current->x - innerOffset.x,
+ current->y - innerOffset.y,
paintInfo.maxAlpha);
AlphaVertex::set(&buffer[currentAAInnerIndex++],
- current->position[0] - outerOffset.x,
- current->position[1] - outerOffset.y,
+ current->x - outerOffset.x,
+ current->y - outerOffset.y,
0.0f);
last = current;
@@ -696,23 +700,23 @@ void getStrokeVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<V
}
// wrap each strip around to beginning, creating degenerate tris to bridge strips
- copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[0]);
- copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
- copyAlphaVertex(&buffer[currentAAOuterIndex++], &buffer[1]);
+ buffer[currentAAOuterIndex++] = buffer[0];
+ buffer[currentAAOuterIndex++] = buffer[1];
+ buffer[currentAAOuterIndex++] = buffer[1];
- copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset]);
- copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
- copyAlphaVertex(&buffer[currentStrokeIndex++], &buffer[offset + 1]);
+ buffer[currentStrokeIndex++] = buffer[offset];
+ buffer[currentStrokeIndex++] = buffer[offset + 1];
+ buffer[currentStrokeIndex++] = buffer[offset + 1];
- copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset]);
- copyAlphaVertex(&buffer[currentAAInnerIndex++], &buffer[2 * offset + 1]);
+ buffer[currentAAInnerIndex++] = buffer[2 * offset];
+ buffer[currentAAInnerIndex++] = buffer[2 * offset + 1];
// don't need to create last degenerate tri
DEBUG_DUMP_ALPHA_BUFFER();
}
void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint,
- const mat4 *transform, VertexBuffer& vertexBuffer) {
+ const mat4& transform, VertexBuffer& vertexBuffer) {
ATRACE_CALL();
const PaintInfo paintInfo(paint, transform);
@@ -733,7 +737,8 @@ void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint,
// force close if we're filling the path, since fill path expects closed perimeter.
bool forceClose = paintInfo.style != SkPaint::kStroke_Style;
bool wasClosed = approximatePathOutlineVertices(path, forceClose,
- threshInvScaleX * threshInvScaleX, threshInvScaleY * threshInvScaleY, tempVertices);
+ threshInvScaleX * threshInvScaleX, threshInvScaleY * threshInvScaleY,
+ OUTLINE_REFINE_THRESHOLD_SQUARED, tempVertices);
if (!tempVertices.size()) {
// path was empty, return without allocating vertex buffer
@@ -743,7 +748,7 @@ void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint,
#if VERTEX_DEBUG
for (unsigned int i = 0; i < tempVertices.size(); i++) {
ALOGD("orig path: point at %f %f",
- tempVertices[i].position[0], tempVertices[i].position[1]);
+ tempVertices[i].x, tempVertices[i].y);
}
#endif
@@ -771,21 +776,15 @@ void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint,
getFillVerticesFromPerimeterAA(paintInfo, tempVertices, vertexBuffer);
}
}
-}
-static void expandRectToCoverVertex(SkRect& rect, float x, float y) {
- rect.fLeft = fminf(rect.fLeft, x);
- rect.fTop = fminf(rect.fTop, y);
- rect.fRight = fmaxf(rect.fRight, x);
- rect.fBottom = fmaxf(rect.fBottom, y);
-}
-static void expandRectToCoverVertex(SkRect& rect, const Vertex& vertex) {
- expandRectToCoverVertex(rect, vertex.position[0], vertex.position[1]);
+ Rect bounds(path.getBounds());
+ paintInfo.expandBoundsForStroke(&bounds);
+ vertexBuffer.setBounds(bounds);
}
template <class TYPE>
static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer,
- const float* points, int count, SkRect& bounds) {
+ const float* points, int count, Rect& bounds) {
bounds.set(points[0], points[1], points[0], points[1]);
int numPoints = count / 2;
@@ -793,14 +792,14 @@ static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer,
dstBuffer.alloc<TYPE>(numPoints * verticesPerPoint + (numPoints - 1) * 2);
for (int i = 0; i < count; i += 2) {
- expandRectToCoverVertex(bounds, points[i + 0], points[i + 1]);
+ bounds.expandToCoverVertex(points[i + 0], points[i + 1]);
dstBuffer.copyInto<TYPE>(srcBuffer, points[i + 0], points[i + 1]);
}
dstBuffer.createDegenerateSeparators<TYPE>(verticesPerPoint);
}
-void PathTessellator::tessellatePoints(const float* points, int count, SkPaint* paint,
- const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer) {
+void PathTessellator::tessellatePoints(const float* points, int count, const SkPaint* paint,
+ const mat4& transform, VertexBuffer& vertexBuffer) {
const PaintInfo paintInfo(paint, transform);
// determine point shape
@@ -818,10 +817,12 @@ void PathTessellator::tessellatePoints(const float* points, int count, SkPaint*
Vector<Vertex> outlineVertices;
approximatePathOutlineVertices(path, true,
paintInfo.inverseScaleX * paintInfo.inverseScaleX,
- paintInfo.inverseScaleY * paintInfo.inverseScaleY, outlineVertices);
+ paintInfo.inverseScaleY * paintInfo.inverseScaleY,
+ OUTLINE_REFINE_THRESHOLD_SQUARED, outlineVertices);
if (!outlineVertices.size()) return;
+ Rect bounds;
// tessellate, then duplicate outline across points
int numPoints = count / 2;
VertexBuffer tempBuffer;
@@ -829,15 +830,18 @@ void PathTessellator::tessellatePoints(const float* points, int count, SkPaint*
getFillVerticesFromPerimeter(outlineVertices, tempBuffer);
instanceVertices<Vertex>(tempBuffer, vertexBuffer, points, count, bounds);
} else {
- getFillVerticesFromPerimeterAA(paintInfo, outlineVertices, tempBuffer);
+ // note: pass maxAlpha directly, since we want fill to be alpha modulated
+ getFillVerticesFromPerimeterAA(paintInfo, outlineVertices, tempBuffer, paintInfo.maxAlpha);
instanceVertices<AlphaVertex>(tempBuffer, vertexBuffer, points, count, bounds);
}
- expandBoundsForStroke(bounds, paint, true); // force-expand bounds to incorporate stroke
+ // expand bounds from vertex coords to pixel data
+ paintInfo.expandBoundsForStroke(&bounds);
+ vertexBuffer.setBounds(bounds);
}
-void PathTessellator::tessellateLines(const float* points, int count, SkPaint* paint,
- const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer) {
+void PathTessellator::tessellateLines(const float* points, int count, const SkPaint* paint,
+ const mat4& transform, VertexBuffer& vertexBuffer) {
ATRACE_CALL();
const PaintInfo paintInfo(paint, transform);
@@ -857,6 +861,7 @@ void PathTessellator::tessellateLines(const float* points, int count, SkPaint* p
tempVertices.push();
tempVertices.push();
Vertex* tempVerticesData = tempVertices.editArray();
+ Rect bounds;
bounds.set(points[0], points[1], points[0], points[1]);
for (int i = 0; i < count; i += 4) {
Vertex::set(&(tempVerticesData[0]), points[i + 0], points[i + 1]);
@@ -869,24 +874,31 @@ void PathTessellator::tessellateLines(const float* points, int count, SkPaint* p
}
// calculate bounds
- expandRectToCoverVertex(bounds, tempVerticesData[0]);
- expandRectToCoverVertex(bounds, tempVerticesData[1]);
+ bounds.expandToCoverVertex(tempVerticesData[0].x, tempVerticesData[0].y);
+ bounds.expandToCoverVertex(tempVerticesData[1].x, tempVerticesData[1].y);
}
- expandBoundsForStroke(bounds, paint, true); // force-expand bounds to incorporate stroke
-
// since multiple objects tessellated into buffer, separate them with degen tris
if (paintInfo.isAA) {
vertexBuffer.createDegenerateSeparators<AlphaVertex>(lineAllocSize);
} else {
vertexBuffer.createDegenerateSeparators<Vertex>(lineAllocSize);
}
+
+ // expand bounds from vertex coords to pixel data
+ paintInfo.expandBoundsForStroke(&bounds);
+ vertexBuffer.setBounds(bounds);
}
///////////////////////////////////////////////////////////////////////////////
// Simple path line approximation
///////////////////////////////////////////////////////////////////////////////
+bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, float thresholdSquared,
+ Vector<Vertex>& outputVertices) {
+ return approximatePathOutlineVertices(path, true, 1.0f, 1.0f, thresholdSquared, outputVertices);
+}
+
void pushToVector(Vector<Vertex>& vertices, float x, float y) {
// TODO: make this not yuck
vertices.push();
@@ -895,7 +907,8 @@ void pushToVector(Vector<Vertex>& vertices, float x, float y) {
}
bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool forceClose,
- float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
+ float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
+ Vector<Vertex>& outputVertices) {
ATRACE_CALL();
// TODO: to support joins other than sharp miter, join vertices should be labelled in the
@@ -922,7 +935,7 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
pts[0].x(), pts[0].y(),
pts[2].x(), pts[2].y(),
pts[1].x(), pts[1].y(),
- sqrInvScaleX, sqrInvScaleY, outputVertices);
+ sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices);
break;
case SkPath::kCubic_Verb:
ALOGV("kCubic_Verb");
@@ -931,7 +944,7 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
pts[1].x(), pts[1].y(),
pts[3].x(), pts[3].y(),
pts[2].x(), pts[2].y(),
- sqrInvScaleX, sqrInvScaleY, outputVertices);
+ sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices);
break;
default:
break;
@@ -939,8 +952,8 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
}
int size = outputVertices.size();
- if (size >= 2 && outputVertices[0].position[0] == outputVertices[size - 1].position[0] &&
- outputVertices[0].position[1] == outputVertices[size - 1].position[1]) {
+ if (size >= 2 && outputVertices[0].x == outputVertices[size - 1].x &&
+ outputVertices[0].y == outputVertices[size - 1].y) {
outputVertices.pop();
return true;
}
@@ -954,7 +967,8 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo
void PathTessellator::recursiveCubicBezierVertices(
float p1x, float p1y, float c1x, float c1y,
float p2x, float p2y, float c2x, float c2y,
- float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
+ float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
+ Vector<Vertex>& outputVertices, int depth) {
float dx = p2x - p1x;
float dy = p2y - p1y;
float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx);
@@ -962,8 +976,8 @@ void PathTessellator::recursiveCubicBezierVertices(
float d = d1 + d2;
// multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
-
- if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
+ if (depth >= MAX_DEPTH
+ || d * d <= thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
// below thresh, draw line by adding endpoint
pushToVector(outputVertices, p2x, p2y);
} else {
@@ -987,11 +1001,11 @@ void PathTessellator::recursiveCubicBezierVertices(
recursiveCubicBezierVertices(
p1x, p1y, p1c1x, p1c1y,
mx, my, p1c1c2x, p1c1c2y,
- sqrInvScaleX, sqrInvScaleY, outputVertices);
+ sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1);
recursiveCubicBezierVertices(
mx, my, p2c1c2x, p2c1c2y,
p2x, p2y, p2c2x, p2c2y,
- sqrInvScaleX, sqrInvScaleY, outputVertices);
+ sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1);
}
}
@@ -999,12 +1013,15 @@ void PathTessellator::recursiveQuadraticBezierVertices(
float ax, float ay,
float bx, float by,
float cx, float cy,
- float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex>& outputVertices) {
+ float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
+ Vector<Vertex>& outputVertices, int depth) {
float dx = bx - ax;
float dy = by - ay;
float d = (cx - bx) * dy - (cy - by) * dx;
- if (d * d < THRESHOLD * THRESHOLD * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
+ // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors
+ if (depth >= MAX_DEPTH
+ || d * d <= thresholdSquared * (dx * dx * sqrInvScaleY + dy * dy * sqrInvScaleX)) {
// below thresh, draw line by adding endpoint
pushToVector(outputVertices, bx, by);
} else {
@@ -1018,9 +1035,9 @@ void PathTessellator::recursiveQuadraticBezierVertices(
float my = (acy + bcy) * 0.5f;
recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy,
- sqrInvScaleX, sqrInvScaleY, outputVertices);
+ sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1);
recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy,
- sqrInvScaleX, sqrInvScaleY, outputVertices);
+ sqrInvScaleX, sqrInvScaleY, thresholdSquared, outputVertices, depth + 1);
}
}
diff --git a/libs/hwui/PathTessellator.h b/libs/hwui/PathTessellator.h
index 85797fc..8ac9a3b 100644
--- a/libs/hwui/PathTessellator.h
+++ b/libs/hwui/PathTessellator.h
@@ -22,100 +22,79 @@
#include "Matrix.h"
#include "Rect.h"
#include "Vertex.h"
+#include "VertexBuffer.h"
namespace android {
namespace uirenderer {
-class VertexBuffer {
+class PathTessellator {
public:
- VertexBuffer():
- mBuffer(0),
- mVertexCount(0),
- mCleanupMethod(NULL)
- {}
-
- ~VertexBuffer() {
- if (mCleanupMethod) mCleanupMethod(mBuffer);
- }
-
/**
- This should be the only method used by the PathTessellator. Subsequent calls to alloc will
- allocate space within the first allocation (useful if you want to eventually allocate
- multiple regions within a single VertexBuffer, such as with PathTessellator::tesselateLines()
+ * Populates scaleX and scaleY with the 'tessellation scale' of the transform - the effective X
+ * and Y scales that tessellation will take into account when generating the 1.0 pixel thick
+ * ramp.
+ *
+ * Two instances of the same shape (size, paint, etc.) will only generate the same vertices if
+ * their tessellation scales are equal.
*/
- template <class TYPE>
- TYPE* alloc(int vertexCount) {
- if (mVertexCount) {
- TYPE* reallocBuffer = (TYPE*)mReallocBuffer;
- // already have allocated the buffer, re-allocate space within
- if (mReallocBuffer != mBuffer) {
- // not first re-allocation, leave space for degenerate triangles to separate strips
- reallocBuffer += 2;
- }
- mReallocBuffer = reallocBuffer + vertexCount;
- return reallocBuffer;
- }
- mVertexCount = vertexCount;
- mReallocBuffer = mBuffer = (void*)new TYPE[vertexCount];
- mCleanupMethod = &(cleanup<TYPE>);
-
- return (TYPE*)mBuffer;
- }
-
- template <class TYPE>
- void copyInto(const VertexBuffer& srcBuffer, float xOffset, float yOffset) {
- int verticesToCopy = srcBuffer.getVertexCount();
-
- TYPE* dst = alloc<TYPE>(verticesToCopy);
- TYPE* src = (TYPE*)srcBuffer.getBuffer();
-
- for (int i = 0; i < verticesToCopy; i++) {
- TYPE::copyWithOffset(&dst[i], src[i], xOffset, yOffset);
- }
- }
-
- void* getBuffer() const { return mBuffer; } // shouldn't be const, since not a const ptr?
- unsigned int getVertexCount() const { return mVertexCount; }
-
- template <class TYPE>
- void createDegenerateSeparators(int allocSize) {
- TYPE* end = (TYPE*)mBuffer + mVertexCount;
- for (TYPE* degen = (TYPE*)mBuffer + allocSize; degen < end; degen += 2 + allocSize) {
- memcpy(degen, degen - 1, sizeof(TYPE));
- memcpy(degen + 1, degen + 2, sizeof(TYPE));
- }
- }
-
-private:
- template <class TYPE>
- static void cleanup(void* buffer) {
- delete[] (TYPE*)buffer;
- }
-
- void* mBuffer;
- unsigned int mVertexCount;
-
- void* mReallocBuffer; // used for multi-allocation
-
- void (*mCleanupMethod)(void*);
-};
-
-class PathTessellator {
-public:
- static void expandBoundsForStroke(SkRect& bounds, const SkPaint* paint, bool forceExpand);
+ static void extractTessellationScales(const Matrix4& transform, float* scaleX, float* scaleY);
+ /**
+ * Populates a VertexBuffer with a tessellated approximation of the input convex path, as a single
+ * triangle strip. Note: joins are not currently supported.
+ *
+ * @param path The path to be approximated
+ * @param paint The paint the path will be drawn with, indicating AA, painting style
+ * (stroke vs fill), stroke width, stroke cap & join style, etc.
+ * @param transform The transform the path is to be drawn with, used to drive stretch-aware path
+ * vertex approximation, and correct AA ramp offsetting.
+ * @param vertexBuffer The output buffer
+ */
static void tessellatePath(const SkPath& path, const SkPaint* paint,
- const mat4 *transform, VertexBuffer& vertexBuffer);
+ const mat4& transform, VertexBuffer& vertexBuffer);
- static void tessellatePoints(const float* points, int count, SkPaint* paint,
- const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer);
+ /**
+ * Populates a VertexBuffer with a tessellated approximation of points as a single triangle
+ * strip (with degenerate tris separating), respecting the shape defined by the paint cap.
+ *
+ * @param points The center vertices of the points to be drawn
+ * @param count The number of floats making up the point vertices
+ * @param paint The paint the points will be drawn with indicating AA, stroke width & cap
+ * @param transform The transform the points will be drawn with, used to drive stretch-aware path
+ * vertex approximation, and correct AA ramp offsetting
+ * @param vertexBuffer The output buffer
+ */
+ static void tessellatePoints(const float* points, int count, const SkPaint* paint,
+ const mat4& transform, VertexBuffer& vertexBuffer);
- static void tessellateLines(const float* points, int count, SkPaint* paint,
- const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer);
+ /**
+ * Populates a VertexBuffer with a tessellated approximation of lines as a single triangle
+ * strip (with degenerate tris separating).
+ *
+ * @param points Pairs of endpoints defining the lines to be drawn
+ * @param count The number of floats making up the line vertices
+ * @param paint The paint the lines will be drawn with indicating AA, stroke width & cap
+ * @param transform The transform the points will be drawn with, used to drive stretch-aware path
+ * vertex approximation, and correct AA ramp offsetting
+ * @param vertexBuffer The output buffer
+ */
+ static void tessellateLines(const float* points, int count, const SkPaint* paint,
+ const mat4& transform, VertexBuffer& vertexBuffer);
+
+ /**
+ * Approximates a convex, CW outline into a Vector of 2d vertices.
+ *
+ * @param path The outline to be approximated
+ * @param thresholdSquared The threshold of acceptable error (in pixels) when approximating
+ * @param outputVertices An empty Vector which will be populated with the output
+ */
+ static bool approximatePathOutlineVertices(const SkPath &path, float thresholdSquared,
+ Vector<Vertex> &outputVertices);
private:
static bool approximatePathOutlineVertices(const SkPath &path, bool forceClose,
- float sqrInvScaleX, float sqrInvScaleY, Vector<Vertex> &outputVertices);
+ float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
+ Vector<Vertex> &outputVertices);
/*
endpoints a & b,
@@ -125,8 +104,8 @@ private:
float ax, float ay,
float bx, float by,
float cx, float cy,
- float sqrInvScaleX, float sqrInvScaleY,
- Vector<Vertex> &outputVertices);
+ float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
+ Vector<Vertex> &outputVertices, int depth = 0);
/*
endpoints p1, p2
@@ -137,8 +116,8 @@ private:
float c1x, float c1y,
float p2x, float p2y,
float c2x, float c2y,
- float sqrInvScaleX, float sqrInvScaleY,
- Vector<Vertex> &outputVertices);
+ float sqrInvScaleX, float sqrInvScaleY, float thresholdSquared,
+ Vector<Vertex> &outputVertices, int depth = 0);
};
}; // namespace uirenderer
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
index 58f5325..cc72ae0 100644
--- a/libs/hwui/Program.cpp
+++ b/libs/hwui/Program.cpp
@@ -60,7 +60,6 @@ Program::Program(const ProgramDescription& description, const char* vertex, cons
GLint status;
glGetProgramiv(mProgramId, GL_LINK_STATUS, &status);
if (status != GL_TRUE) {
- ALOGE("Error while linking shaders:");
GLint infoLen = 0;
glGetProgramiv(mProgramId, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1) {
@@ -68,14 +67,7 @@ Program::Program(const ProgramDescription& description, const char* vertex, cons
glGetProgramInfoLog(mProgramId, infoLen, 0, &log[0]);
ALOGE("%s", log);
}
-
- glDetachShader(mProgramId, mVertexShader);
- glDetachShader(mProgramId, mFragmentShader);
-
- glDeleteShader(mVertexShader);
- glDeleteShader(mFragmentShader);
-
- glDeleteProgram(mProgramId);
+ LOG_ALWAYS_FATAL("Error while linking shaders");
} else {
mInitialized = true;
}
@@ -153,8 +145,7 @@ GLuint Program::buildShader(const char* source, GLenum type) {
// use a fixed size instead
GLchar log[512];
glGetShaderInfoLog(shader, sizeof(log), 0, &log[0]);
- ALOGE("Error while compiling shader: %s", log);
- glDeleteShader(shader);
+ LOG_ALWAYS_FATAL("Error while compiling shader: %s", log);
return 0;
}
@@ -173,7 +164,7 @@ void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix,
// up and to the left.
// This offset value is based on an assumption that some hardware may use as
// little as 12.4 precision, so we offset by slightly more than 1/16.
- p.translate(Vertex::gGeometryFudgeFactor, Vertex::gGeometryFudgeFactor);
+ p.translate(Vertex::GeometryFudgeFactor(), Vertex::GeometryFudgeFactor());
glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]);
}
mProjection = projectionMatrix;
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index f6ac8ec..56773f4 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -45,18 +45,18 @@ namespace uirenderer {
#define COLOR_COMPONENT_THRESHOLD 1.0f
#define COLOR_COMPONENT_INV_THRESHOLD 0.0f
-#define PROGRAM_KEY_TEXTURE 0x1
-#define PROGRAM_KEY_A8_TEXTURE 0x2
-#define PROGRAM_KEY_BITMAP 0x4
-#define PROGRAM_KEY_GRADIENT 0x8
-#define PROGRAM_KEY_BITMAP_FIRST 0x10
-#define PROGRAM_KEY_COLOR_MATRIX 0x20
-#define PROGRAM_KEY_COLOR_LIGHTING 0x40
-#define PROGRAM_KEY_COLOR_BLEND 0x80
-#define PROGRAM_KEY_BITMAP_NPOT 0x100
-#define PROGRAM_KEY_SWAP_SRC_DST 0x2000
-
-#define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600
+#define PROGRAM_KEY_TEXTURE 0x01
+#define PROGRAM_KEY_A8_TEXTURE 0x02
+#define PROGRAM_KEY_BITMAP 0x04
+#define PROGRAM_KEY_GRADIENT 0x08
+#define PROGRAM_KEY_BITMAP_FIRST 0x10
+#define PROGRAM_KEY_COLOR_MATRIX 0x20
+#define PROGRAM_KEY_COLOR_BLEND 0x40
+#define PROGRAM_KEY_BITMAP_NPOT 0x80
+
+#define PROGRAM_KEY_SWAP_SRC_DST 0x2000
+
+#define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600
#define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800
// Encode the xfermodes on 6 bits
@@ -71,19 +71,21 @@ namespace uirenderer {
#define PROGRAM_GRADIENT_TYPE_SHIFT 33 // 2 bits for gradient type
#define PROGRAM_MODULATE_SHIFT 35
-#define PROGRAM_HAS_AA_SHIFT 36
+#define PROGRAM_HAS_VERTEX_ALPHA_SHIFT 36
+#define PROGRAM_USE_SHADOW_ALPHA_INTERP_SHIFT 37
-#define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 37
-#define PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT 38
+#define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 38
+#define PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT 39
-#define PROGRAM_HAS_GAMMA_CORRECTION 39
+#define PROGRAM_HAS_GAMMA_CORRECTION 40
-#define PROGRAM_IS_SIMPLE_GRADIENT 40
+#define PROGRAM_IS_SIMPLE_GRADIENT 41
-#define PROGRAM_HAS_COLORS 41
+#define PROGRAM_HAS_COLORS 42
-#define PROGRAM_HAS_DEBUG_HIGHLIGHT 42
-#define PROGRAM_EMULATE_STENCIL 43
+#define PROGRAM_HAS_DEBUG_HIGHLIGHT 43
+#define PROGRAM_EMULATE_STENCIL 44
+#define PROGRAM_HAS_ROUND_RECT_CLIP 45
///////////////////////////////////////////////////////////////////////////////
// Types
@@ -104,7 +106,6 @@ struct ProgramDescription {
enum ColorModifier {
kColorNone = 0,
kColorMatrix,
- kColorLighting,
kColorBlend
};
@@ -134,7 +135,8 @@ struct ProgramDescription {
bool hasBitmap;
bool isBitmapNpot;
- bool isAA; // drawing with a per-vertex alpha
+ bool hasVertexAlpha;
+ bool useShadowAlphaInterp;
bool hasGradient;
Gradient gradientType;
@@ -160,6 +162,7 @@ struct ProgramDescription {
bool hasDebugHighlight;
bool emulateStencil;
+ bool hasRoundRectClip;
/**
* Resets this description. All fields are reset back to the default
@@ -173,7 +176,8 @@ struct ProgramDescription {
hasColors = false;
- isAA = false;
+ hasVertexAlpha = false;
+ useShadowAlphaInterp = false;
modulate = false;
@@ -200,6 +204,8 @@ struct ProgramDescription {
gamma = 2.2f;
hasDebugHighlight = false;
+ emulateStencil = false;
+ hasRoundRectClip = false;
}
/**
@@ -207,7 +213,7 @@ struct ProgramDescription {
* the fragment shader. When this method returns true, the program should
* be provided with a modulation color.
*/
- bool setColor(const float r, const float g, const float b, const float a) {
+ bool setColorModulate(const float a) {
modulate = a < COLOR_COMPONENT_THRESHOLD;
return modulate;
}
@@ -217,7 +223,7 @@ struct ProgramDescription {
* the fragment shader. When this method returns true, the program should
* be provided with a modulation color.
*/
- bool setAlpha8Color(const float r, const float g, const float b, const float a) {
+ bool setAlpha8ColorModulate(const float r, const float g, const float b, const float a) {
modulate = a < COLOR_COMPONENT_THRESHOLD || r > COLOR_COMPONENT_INV_THRESHOLD ||
g > COLOR_COMPONENT_INV_THRESHOLD || b > COLOR_COMPONENT_INV_THRESHOLD;
return modulate;
@@ -248,9 +254,6 @@ struct ProgramDescription {
case kColorMatrix:
key |= PROGRAM_KEY_COLOR_MATRIX;
break;
- case kColorLighting:
- key |= PROGRAM_KEY_COLOR_LIGHTING;
- break;
case kColorBlend:
key |= PROGRAM_KEY_COLOR_BLEND;
key |= (colorMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_COLOR_OP_SHIFT;
@@ -261,7 +264,8 @@ struct ProgramDescription {
key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT;
if (swapSrcDst) key |= PROGRAM_KEY_SWAP_SRC_DST;
if (modulate) key |= programid(0x1) << PROGRAM_MODULATE_SHIFT;
- if (isAA) key |= programid(0x1) << PROGRAM_HAS_AA_SHIFT;
+ if (hasVertexAlpha) key |= programid(0x1) << PROGRAM_HAS_VERTEX_ALPHA_SHIFT;
+ if (useShadowAlphaInterp) key |= programid(0x1) << PROGRAM_USE_SHADOW_ALPHA_INTERP_SHIFT;
if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT;
if (hasTextureTransform) key |= programid(0x1) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT;
if (hasGammaCorrection) key |= programid(0x1) << PROGRAM_HAS_GAMMA_CORRECTION;
@@ -269,6 +273,7 @@ struct ProgramDescription {
if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS;
if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT;
if (emulateStencil) key |= programid(0x1) << PROGRAM_EMULATE_STENCIL;
+ if (hasRoundRectClip) key |= programid(0x1) << PROGRAM_HAS_ROUND_RECT_CLIP;
return key;
}
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index a5ce6f6..c802b18 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -46,7 +46,7 @@ const char* gVS_Header_Attributes_TexCoords =
"attribute vec2 texCoords;\n";
const char* gVS_Header_Attributes_Colors =
"attribute vec4 colors;\n";
-const char* gVS_Header_Attributes_AAVertexShapeParameters =
+const char* gVS_Header_Attributes_VertexAlphaParameters =
"attribute float vtxAlpha;\n";
const char* gVS_Header_Uniforms_TextureTransform =
"uniform mat4 mainTextureTransform;\n";
@@ -58,11 +58,13 @@ const char* gVS_Header_Uniforms_HasGradient =
const char* gVS_Header_Uniforms_HasBitmap =
"uniform mat4 textureTransform;\n"
"uniform mediump vec2 textureDimension;\n";
+const char* gVS_Header_Uniforms_HasRoundRectClip =
+ "uniform mat4 roundRectInvTransform;\n";
const char* gVS_Header_Varyings_HasTexture =
"varying vec2 outTexCoords;\n";
const char* gVS_Header_Varyings_HasColors =
"varying vec4 outColors;\n";
-const char* gVS_Header_Varyings_IsAAVertexShape =
+const char* gVS_Header_Varyings_HasVertexAlpha =
"varying float alpha;\n";
const char* gVS_Header_Varyings_HasBitmap =
"varying highp vec2 outBitmapTexCoords;\n";
@@ -85,6 +87,8 @@ const char* gVS_Header_Varyings_HasGradient[6] = {
"varying highp vec2 sweep;\n"
"varying vec2 ditherTexCoords;\n",
};
+const char* gVS_Header_Varyings_HasRoundRectClip =
+ "varying vec2 roundRectPos;\n";
const char* gVS_Main =
"\nvoid main(void) {\n";
const char* gVS_Main_OutTexCoords =
@@ -115,9 +119,14 @@ const char* gVS_Main_OutGradient[6] = {
const char* gVS_Main_OutBitmapTexCoords =
" outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n";
const char* gVS_Main_Position =
- " gl_Position = projection * transform * position;\n";
-const char* gVS_Main_AAVertexShape =
+ " vec4 transformedPosition = projection * transform * position;\n"
+ " gl_Position = transformedPosition;\n";
+
+const char* gVS_Main_VertexAlpha =
" alpha = vtxAlpha;\n";
+
+const char* gVS_Main_HasRoundRectClip =
+ " roundRectPos = (roundRectInvTransform * transformedPosition).xy;\n";
const char* gVS_Footer =
"}\n\n";
@@ -148,21 +157,22 @@ const char* gFS_Uniforms_GradientSampler[2] = {
};
const char* gFS_Uniforms_BitmapSampler =
"uniform sampler2D bitmapSampler;\n";
-const char* gFS_Uniforms_ColorOp[4] = {
+const char* gFS_Uniforms_ColorOp[3] = {
// None
"",
// Matrix
"uniform mat4 colorMatrix;\n"
"uniform vec4 colorMatrixVector;\n",
- // Lighting
- "uniform vec4 lightingMul;\n"
- "uniform vec4 lightingAdd;\n",
// PorterDuff
"uniform vec4 colorBlend;\n"
};
const char* gFS_Uniforms_Gamma =
"uniform float gamma;\n";
+const char* gFS_Uniforms_HasRoundRectClip =
+ "uniform vec4 roundRectInnerRectLTRB;\n"
+ "uniform float roundRectRadius;\n";
+
const char* gFS_Main =
"\nvoid main(void) {\n"
" lowp vec4 fragColor;\n";
@@ -227,8 +237,10 @@ const char* gFS_Main_FetchColor =
" fragColor = color;\n";
const char* gFS_Main_ModulateColor =
" fragColor *= color.a;\n";
-const char* gFS_Main_AccountForAAVertexShape =
+const char* gFS_Main_ApplyVertexAlphaLinearInterp =
" fragColor *= alpha;\n";
+const char* gFS_Main_ApplyVertexAlphaShadowInterp =
+ " fragColor *= (1.0 - cos(alpha)) / 2.0;\n";
const char* gFS_Main_FetchTexture[2] = {
// Don't modulate
@@ -311,20 +323,27 @@ const char* gFS_Main_FragColor_Blend =
" gl_FragColor = blendFramebuffer(fragColor, gl_LastFragColor);\n";
const char* gFS_Main_FragColor_Blend_Swap =
" gl_FragColor = blendFramebuffer(gl_LastFragColor, fragColor);\n";
-const char* gFS_Main_ApplyColorOp[4] = {
+const char* gFS_Main_ApplyColorOp[3] = {
// None
"",
// Matrix
" fragColor *= colorMatrix;\n"
" fragColor += colorMatrixVector;\n"
" fragColor.rgb *= fragColor.a;\n",
- // Lighting
- " float lightingAlpha = fragColor.a;\n"
- " fragColor = min(fragColor * lightingMul + (lightingAdd * lightingAlpha), lightingAlpha);\n"
- " fragColor.a = lightingAlpha;\n",
// PorterDuff
" fragColor = blendColors(colorBlend, fragColor);\n"
};
+
+// Note: LTRB -> xyzw
+const char* gFS_Main_FragColor_HasRoundRectClip =
+ " mediump vec2 fragToLT = roundRectInnerRectLTRB.xy - roundRectPos;\n"
+ " mediump vec2 fragFromRB = roundRectPos - roundRectInnerRectLTRB.zw;\n"
+
+ // divide + multiply by 128 to avoid falling out of range in length() function
+ " mediump vec2 dist = max(max(fragToLT, fragFromRB), vec2(0.0, 0.0)) / 128.0;\n"
+ " mediump float linearDist = roundRectRadius - (length(dist) * 128.0);\n"
+ " gl_FragColor *= clamp(linearDist, 0.0, 1.0);\n";
+
const char* gFS_Main_DebugHighlight =
" gl_FragColor.rgb = vec3(0.0, gl_FragColor.a, 0.0);\n";
const char* gFS_Main_EmulateStencil =
@@ -452,8 +471,8 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
if (description.hasTexture || description.hasExternalTexture) {
shader.append(gVS_Header_Attributes_TexCoords);
}
- if (description.isAA) {
- shader.append(gVS_Header_Attributes_AAVertexShapeParameters);
+ if (description.hasVertexAlpha) {
+ shader.append(gVS_Header_Attributes_VertexAlphaParameters);
}
if (description.hasColors) {
shader.append(gVS_Header_Attributes_Colors);
@@ -469,12 +488,15 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
if (description.hasBitmap) {
shader.append(gVS_Header_Uniforms_HasBitmap);
}
+ if (description.hasRoundRectClip) {
+ shader.append(gVS_Header_Uniforms_HasRoundRectClip);
+ }
// Varyings
if (description.hasTexture || description.hasExternalTexture) {
shader.append(gVS_Header_Varyings_HasTexture);
}
- if (description.isAA) {
- shader.append(gVS_Header_Varyings_IsAAVertexShape);
+ if (description.hasVertexAlpha) {
+ shader.append(gVS_Header_Varyings_HasVertexAlpha);
}
if (description.hasColors) {
shader.append(gVS_Header_Varyings_HasColors);
@@ -485,6 +507,9 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
if (description.hasBitmap) {
shader.append(gVS_Header_Varyings_HasBitmap);
}
+ if (description.hasRoundRectClip) {
+ shader.append(gVS_Header_Varyings_HasRoundRectClip);
+ }
// Begin the shader
shader.append(gVS_Main); {
@@ -493,8 +518,8 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
} else if (description.hasTexture || description.hasExternalTexture) {
shader.append(gVS_Main_OutTexCoords);
}
- if (description.isAA) {
- shader.append(gVS_Main_AAVertexShape);
+ if (description.hasVertexAlpha) {
+ shader.append(gVS_Main_VertexAlpha);
}
if (description.hasColors) {
shader.append(gVS_Main_OutColors);
@@ -507,6 +532,9 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description
if (description.hasGradient) {
shader.append(gVS_Main_OutGradient[gradientIndex(description)]);
}
+ if (description.hasRoundRectClip) {
+ shader.append(gVS_Main_HasRoundRectClip);
+ }
}
// End the shader
shader.append(gVS_Footer);
@@ -541,8 +569,8 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
if (description.hasTexture || description.hasExternalTexture) {
shader.append(gVS_Header_Varyings_HasTexture);
}
- if (description.isAA) {
- shader.append(gVS_Header_Varyings_IsAAVertexShape);
+ if (description.hasVertexAlpha) {
+ shader.append(gVS_Header_Varyings_HasVertexAlpha);
}
if (description.hasColors) {
shader.append(gVS_Header_Varyings_HasColors);
@@ -553,6 +581,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
if (description.hasBitmap) {
shader.append(gVS_Header_Varyings_HasBitmap);
}
+ if (description.hasRoundRectClip) {
+ shader.append(gVS_Header_Varyings_HasRoundRectClip);
+ }
// Uniforms
int modulateOp = MODULATE_OP_NO_MODULATE;
@@ -575,11 +606,18 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
if (description.hasGammaCorrection) {
shader.append(gFS_Uniforms_Gamma);
}
+ if (description.hasRoundRectClip) {
+ shader.append(gFS_Uniforms_HasRoundRectClip);
+ }
// Optimization for common cases
- if (!description.isAA && !blendFramebuffer && !description.hasColors &&
- description.colorOp == ProgramDescription::kColorNone &&
- !description.hasDebugHighlight && !description.emulateStencil) {
+ if (!description.hasVertexAlpha
+ && !blendFramebuffer
+ && !description.hasColors
+ && description.colorOp == ProgramDescription::kColorNone
+ && !description.hasDebugHighlight
+ && !description.emulateStencil
+ && !description.hasRoundRectClip) {
bool fast = false;
const bool noShader = !description.hasGradient && !description.hasBitmap;
@@ -715,8 +753,12 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
// Apply the color op if needed
shader.append(gFS_Main_ApplyColorOp[description.colorOp]);
- if (description.isAA) {
- shader.append(gFS_Main_AccountForAAVertexShape);
+ if (description.hasVertexAlpha) {
+ if (description.useShadowAlphaInterp) {
+ shader.append(gFS_Main_ApplyVertexAlphaShadowInterp);
+ } else {
+ shader.append(gFS_Main_ApplyVertexAlphaLinearInterp);
+ }
}
// Output the fragment
@@ -729,6 +771,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
if (description.hasColors) {
shader.append(gFS_Main_FragColor_HasColors);
}
+ if (description.hasRoundRectClip) {
+ shader.append(gFS_Main_FragColor_HasRoundRectClip);
+ }
if (description.hasDebugHighlight) {
shader.append(gFS_Main_DebugHighlight);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 20b8f2f..feaee8e 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -89,6 +89,36 @@ enum DebugLevel {
#define PROPERTY_DEBUG_NV_PROFILING "debug.hwui.nv_profiling"
/**
+ * System property used to enable or disable hardware rendering profiling.
+ * The default value of this property is assumed to be false.
+ *
+ * When profiling is enabled, the adb shell dumpsys gfxinfo command will
+ * output extra information about the time taken to execute by the last
+ * frames.
+ *
+ * Possible values:
+ * "true", to enable profiling
+ * "visual_bars", to enable profiling and visualize the results on screen
+ * "false", to disable profiling
+ */
+#define PROPERTY_PROFILE "debug.hwui.profile"
+#define PROPERTY_PROFILE_VISUALIZE_BARS "visual_bars"
+
+/**
+ * System property used to specify the number of frames to be used
+ * when doing hardware rendering profiling.
+ * The default value of this property is #PROFILE_MAX_FRAMES.
+ *
+ * When profiling is enabled, the adb shell dumpsys gfxinfo command will
+ * output extra information about the time taken to execute by the last
+ * frames.
+ *
+ * Possible values:
+ * "60", to set the limit of frames to 60
+ */
+#define PROPERTY_PROFILE_MAXFRAMES "debug.hwui.profile.maxframes"
+
+/**
* Used to enable/disable non-rectangular clipping debugging.
*
* The accepted values are:
@@ -146,6 +176,7 @@ enum DebugLevel {
#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"
@@ -192,6 +223,7 @@ enum DebugLevel {
#define DEFAULT_LAYER_CACHE_SIZE 16.0f
#define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f
#define DEFAULT_PATH_CACHE_SIZE 10.0f
+#define DEFAULT_VERTEX_CACHE_SIZE 1.0f
#define DEFAULT_PATCH_CACHE_SIZE 128 // in kB
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
#define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index dabd8d4..13265a9 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -18,6 +18,7 @@
#define ANDROID_HWUI_RECT_H
#include <cmath>
+#include <SkRect.h>
#include <utils/Log.h>
@@ -26,9 +27,11 @@
namespace android {
namespace uirenderer {
-#define RECT_STRING "%7.2f %7.2f %7.2f %7.2f"
+#define RECT_STRING "%5.2f %5.2f %5.2f %5.2f"
#define RECT_ARGS(r) \
(r).left, (r).top, (r).right, (r).bottom
+#define SK_RECT_ARGS(r) \
+ (r).left(), (r).top(), (r).right(), (r).bottom()
///////////////////////////////////////////////////////////////////////////////
// Structs
@@ -68,6 +71,13 @@ public:
bottom(height) {
}
+ inline Rect(const SkRect& rect):
+ left(rect.fLeft),
+ top(rect.fTop),
+ right(rect.fRight),
+ bottom(rect.fBottom) {
+ }
+
friend int operator==(const Rect& a, const Rect& b) {
return !memcmp(&a, &b, sizeof(a));
}
@@ -165,6 +175,10 @@ public:
bottom += dy;
}
+ void inset(float delta) {
+ outset(-delta);
+ }
+
void outset(float delta) {
left -= delta;
top -= delta;
@@ -172,6 +186,13 @@ public:
bottom += delta;
}
+ void outset(float xdelta, float ydelta) {
+ left -= xdelta;
+ top -= ydelta;
+ right += xdelta;
+ bottom += ydelta;
+ }
+
/**
* Similar to snapToPixelBoundaries, but estimates bounds conservatively to handle GL rounding
* errors.
@@ -190,19 +211,19 @@ public:
* from this inset will only incur similarly small errors in output, due to transparency
* in extreme outside of the geometry.
*/
- left = floorf(left + Vertex::gGeometryFudgeFactor);
- top = floorf(top + Vertex::gGeometryFudgeFactor);
- right = ceilf(right - Vertex::gGeometryFudgeFactor);
- bottom = ceilf(bottom - Vertex::gGeometryFudgeFactor);
+ left = floorf(left + Vertex::GeometryFudgeFactor());
+ top = floorf(top + Vertex::GeometryFudgeFactor());
+ right = ceilf(right - Vertex::GeometryFudgeFactor());
+ bottom = ceilf(bottom - Vertex::GeometryFudgeFactor());
} else {
/* For other geometry, we do the regular rounding in order to snap, but also outset the
* bounds by a fudge factor. This ensures that ambiguous geometry (e.g. a non-AA Rect
* with top left at (0.5, 0.5)) will err on the side of a larger damage rect.
*/
- left = floorf(left + 0.5f - Vertex::gGeometryFudgeFactor);
- top = floorf(top + 0.5f - Vertex::gGeometryFudgeFactor);
- right = floorf(right + 0.5f + Vertex::gGeometryFudgeFactor);
- bottom = floorf(bottom + 0.5f + Vertex::gGeometryFudgeFactor);
+ left = floorf(left + 0.5f - Vertex::GeometryFudgeFactor());
+ top = floorf(top + 0.5f - Vertex::GeometryFudgeFactor());
+ right = floorf(right + 0.5f + Vertex::GeometryFudgeFactor());
+ bottom = floorf(bottom + 0.5f + Vertex::GeometryFudgeFactor());
}
}
@@ -213,8 +234,22 @@ public:
bottom = floorf(bottom + 0.5f);
}
- void dump() const {
- ALOGD("Rect[l=%f t=%f r=%f b=%f]", left, top, right, bottom);
+ void roundOut() {
+ left = floorf(left);
+ top = floorf(top);
+ right = ceilf(right);
+ bottom = ceilf(bottom);
+ }
+
+ void expandToCoverVertex(float x, float y) {
+ left = fminf(left, x);
+ top = fminf(top, y);
+ right = fmaxf(right, x);
+ bottom = fmaxf(bottom, y);
+ }
+
+ void dump(const char* label = NULL) const {
+ ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom);
}
private:
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
new file mode 100644
index 0000000..0db6198
--- /dev/null
+++ b/libs/hwui/RenderNode.cpp
@@ -0,0 +1,909 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_VIEW
+#define LOG_TAG "OpenGLRenderer"
+
+#include "RenderNode.h"
+
+#include <algorithm>
+#include <string>
+
+#include <SkCanvas.h>
+#include <algorithm>
+
+#include <utils/Trace.h>
+
+#include "DamageAccumulator.h"
+#include "Debug.h"
+#include "DisplayListOp.h"
+#include "DisplayListLogBuffer.h"
+#include "LayerRenderer.h"
+#include "OpenGLRenderer.h"
+#include "utils/MathUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+void RenderNode::outputLogBuffer(int fd) {
+ DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
+ if (logBuffer.isEmpty()) {
+ return;
+ }
+
+ FILE *file = fdopen(fd, "a");
+
+ fprintf(file, "\nRecent DisplayList operations\n");
+ logBuffer.outputCommands(file);
+
+ String8 cachesLog;
+ Caches::getInstance().dumpMemoryUsage(cachesLog);
+ fprintf(file, "\nCaches:\n%s", cachesLog.string());
+ fprintf(file, "\n");
+
+ fflush(file);
+}
+
+RenderNode::RenderNode()
+ : mDirtyPropertyFields(0)
+ , mNeedsDisplayListDataSync(false)
+ , mDisplayListData(0)
+ , mStagingDisplayListData(0)
+ , mAnimatorManager(*this)
+ , mLayer(0)
+ , mParentCount(0) {
+}
+
+RenderNode::~RenderNode() {
+ deleteDisplayListData();
+ delete mStagingDisplayListData;
+ LayerRenderer::destroyLayerDeferred(mLayer);
+}
+
+void RenderNode::setStagingDisplayList(DisplayListData* data) {
+ mNeedsDisplayListDataSync = true;
+ delete mStagingDisplayListData;
+ mStagingDisplayListData = data;
+ if (mStagingDisplayListData) {
+ Caches::getInstance().registerFunctors(mStagingDisplayListData->functors.size());
+ }
+}
+
+/**
+ * This function is a simplified version of replay(), where we simply retrieve and log the
+ * display list. This function should remain in sync with the replay() function.
+ */
+void RenderNode::output(uint32_t level) {
+ ALOGD("%*sStart display list (%p, %s, render=%d)", (level - 1) * 2, "", this,
+ getName(), isRenderable());
+ ALOGD("%*s%s %d", level * 2, "", "Save",
+ SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+
+ properties().debugOutputProperties(level);
+ int flags = DisplayListOp::kOpLogFlag_Recurse;
+ if (mDisplayListData) {
+ for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) {
+ mDisplayListData->displayListOps[i]->output(level, flags);
+ }
+ }
+
+ ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, getName());
+}
+
+int RenderNode::getDebugSize() {
+ int size = sizeof(RenderNode);
+ if (mStagingDisplayListData) {
+ size += mStagingDisplayListData->allocator.usedSize();
+ }
+ if (mDisplayListData && mDisplayListData != mStagingDisplayListData) {
+ size += mDisplayListData->allocator.usedSize();
+ }
+ return size;
+}
+
+void RenderNode::prepareTree(TreeInfo& info) {
+ ATRACE_CALL();
+ LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing");
+
+ prepareTreeImpl(info);
+}
+
+void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
+ mAnimatorManager.addAnimator(animator);
+}
+
+void RenderNode::damageSelf(TreeInfo& info) {
+ if (isRenderable()) {
+ if (properties().getClipDamageToBounds()) {
+ info.damageAccumulator->dirty(0, 0, properties().getWidth(), properties().getHeight());
+ } else {
+ // Hope this is big enough?
+ // TODO: Get this from the display list ops or something
+ info.damageAccumulator->dirty(INT_MIN, INT_MIN, INT_MAX, INT_MAX);
+ }
+ }
+}
+
+void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) {
+ LayerType layerType = properties().layerProperties().type();
+ if (CC_UNLIKELY(layerType == kLayerTypeRenderLayer)) {
+ // Damage applied so far needs to affect our parent, but does not require
+ // the layer to be updated. So we pop/push here to clear out the current
+ // damage and get a clean state for display list or children updates to
+ // affect, which will require the layer to be updated
+ info.damageAccumulator->popTransform();
+ info.damageAccumulator->pushTransform(this);
+ if (dirtyMask & DISPLAY_LIST) {
+ damageSelf(info);
+ }
+ }
+}
+
+void RenderNode::pushLayerUpdate(TreeInfo& info) {
+ LayerType layerType = properties().layerProperties().type();
+ // If we are not a layer OR we cannot be rendered (eg, view was detached)
+ // we need to destroy any Layers we may have had previously
+ if (CC_LIKELY(layerType != kLayerTypeRenderLayer) || CC_UNLIKELY(!isRenderable())) {
+ if (CC_UNLIKELY(mLayer)) {
+ LayerRenderer::destroyLayer(mLayer);
+ mLayer = NULL;
+ }
+ return;
+ }
+
+ bool transformUpdateNeeded = false;
+ if (!mLayer) {
+ mLayer = LayerRenderer::createRenderLayer(info.renderState, getWidth(), getHeight());
+ applyLayerPropertiesToLayer(info);
+ damageSelf(info);
+ transformUpdateNeeded = true;
+ } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) {
+ if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) {
+ LayerRenderer::destroyLayer(mLayer);
+ mLayer = 0;
+ }
+ damageSelf(info);
+ transformUpdateNeeded = true;
+ }
+
+ if (transformUpdateNeeded) {
+ // update the transform in window of the layer to reset its origin wrt light source position
+ Matrix4 windowTransform;
+ info.damageAccumulator->computeCurrentTransform(&windowTransform);
+ mLayer->setWindowTransform(windowTransform);
+ }
+
+ SkRect dirty;
+ info.damageAccumulator->peekAtDirty(&dirty);
+
+ if (!mLayer) {
+ if (info.errorHandler) {
+ std::string msg = "Unable to create layer for ";
+ msg += getName();
+ info.errorHandler->onError(msg);
+ }
+ return;
+ }
+
+
+ if (dirty.intersect(0, 0, getWidth(), getHeight())) {
+ dirty.roundOut();
+ mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom);
+ }
+ // This is not inside the above if because we may have called
+ // updateDeferred on a previous prepare pass that didn't have a renderer
+ if (info.renderer && mLayer->deferredUpdateScheduled) {
+ info.renderer->pushLayerUpdate(mLayer);
+ }
+}
+
+void RenderNode::prepareTreeImpl(TreeInfo& info) {
+ info.damageAccumulator->pushTransform(this);
+
+ if (info.mode == TreeInfo::MODE_FULL) {
+ pushStagingPropertiesChanges(info);
+ }
+ uint32_t animatorDirtyMask = mAnimatorManager.animate(info);
+ prepareLayer(info, animatorDirtyMask);
+ if (info.mode == TreeInfo::MODE_FULL) {
+ pushStagingDisplayListChanges(info);
+ }
+ prepareSubTree(info, mDisplayListData);
+ pushLayerUpdate(info);
+
+ info.damageAccumulator->popTransform();
+}
+
+void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) {
+ // Push the animators first so that setupStartValueIfNecessary() is called
+ // before properties() is trampled by stagingProperties(), as they are
+ // required by some animators.
+ mAnimatorManager.pushStaging(info);
+ if (mDirtyPropertyFields) {
+ mDirtyPropertyFields = 0;
+ damageSelf(info);
+ info.damageAccumulator->popTransform();
+ mProperties = mStagingProperties;
+ applyLayerPropertiesToLayer(info);
+ // We could try to be clever and only re-damage if the matrix changed.
+ // However, we don't need to worry about that. The cost of over-damaging
+ // here is only going to be a single additional map rect of this node
+ // plus a rect join(). The parent's transform (and up) will only be
+ // performed once.
+ info.damageAccumulator->pushTransform(this);
+ damageSelf(info);
+ }
+}
+
+void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) {
+ if (CC_LIKELY(!mLayer)) return;
+
+ const LayerProperties& props = properties().layerProperties();
+ mLayer->setAlpha(props.alpha(), props.xferMode());
+ mLayer->setColorFilter(props.colorFilter());
+ mLayer->setBlend(props.needsBlending());
+}
+
+void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) {
+ if (mNeedsDisplayListDataSync) {
+ mNeedsDisplayListDataSync = false;
+ // Make sure we inc first so that we don't fluctuate between 0 and 1,
+ // which would thrash the layer cache
+ if (mStagingDisplayListData) {
+ for (size_t i = 0; i < mStagingDisplayListData->children().size(); i++) {
+ mStagingDisplayListData->children()[i]->mRenderNode->incParentRefCount();
+ }
+ }
+ deleteDisplayListData();
+ mDisplayListData = mStagingDisplayListData;
+ mStagingDisplayListData = NULL;
+ if (mDisplayListData) {
+ for (size_t i = 0; i < mDisplayListData->functors.size(); i++) {
+ (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, NULL);
+ }
+ }
+ damageSelf(info);
+ }
+}
+
+void RenderNode::deleteDisplayListData() {
+ if (mDisplayListData) {
+ for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
+ mDisplayListData->children()[i]->mRenderNode->decParentRefCount();
+ }
+ }
+ delete mDisplayListData;
+ mDisplayListData = NULL;
+}
+
+void RenderNode::prepareSubTree(TreeInfo& info, DisplayListData* subtree) {
+ if (subtree) {
+ TextureCache& cache = Caches::getInstance().textureCache;
+ info.out.hasFunctors |= subtree->functors.size();
+ // TODO: Fix ownedBitmapResources to not require disabling prepareTextures
+ // and thus falling out of async drawing path.
+ if (subtree->ownedBitmapResources.size()) {
+ info.prepareTextures = false;
+ }
+ for (size_t i = 0; info.prepareTextures && i < subtree->bitmapResources.size(); i++) {
+ info.prepareTextures = cache.prefetchAndMarkInUse(subtree->bitmapResources[i]);
+ }
+ for (size_t i = 0; i < subtree->children().size(); i++) {
+ DrawRenderNodeOp* op = subtree->children()[i];
+ RenderNode* childNode = op->mRenderNode;
+ info.damageAccumulator->pushTransform(&op->mTransformFromParent);
+ childNode->prepareTreeImpl(info);
+ info.damageAccumulator->popTransform();
+ }
+ }
+}
+
+void RenderNode::destroyHardwareResources() {
+ if (mLayer) {
+ LayerRenderer::destroyLayer(mLayer);
+ mLayer = NULL;
+ }
+ if (mDisplayListData) {
+ for (size_t i = 0; i < mDisplayListData->children().size(); i++) {
+ mDisplayListData->children()[i]->mRenderNode->destroyHardwareResources();
+ }
+ if (mNeedsDisplayListDataSync) {
+ // Next prepare tree we are going to push a new display list, so we can
+ // drop our current one now
+ deleteDisplayListData();
+ }
+ }
+}
+
+void RenderNode::decParentRefCount() {
+ LOG_ALWAYS_FATAL_IF(!mParentCount, "already 0!");
+ mParentCount--;
+ if (!mParentCount) {
+ // If a child of ours is being attached to our parent then this will incorrectly
+ // destroy its hardware resources. However, this situation is highly unlikely
+ // and the failure is "just" that the layer is re-created, so this should
+ // be safe enough
+ destroyHardwareResources();
+ }
+}
+
+/*
+ * For property operations, we pass a savecount of 0, since the operations aren't part of the
+ * displaylist, and thus don't have to compensate for the record-time/playback-time discrepancy in
+ * base saveCount (i.e., how RestoreToCount uses saveCount + properties().getCount())
+ */
+#define PROPERTY_SAVECOUNT 0
+
+template <class T>
+void RenderNode::setViewProperties(OpenGLRenderer& renderer, T& handler) {
+#if DEBUG_DISPLAY_LIST
+ properties().debugOutputProperties(handler.level() + 1);
+#endif
+ if (properties().getLeft() != 0 || properties().getTop() != 0) {
+ renderer.translate(properties().getLeft(), properties().getTop());
+ }
+ if (properties().getStaticMatrix()) {
+ renderer.concatMatrix(*properties().getStaticMatrix());
+ } else if (properties().getAnimationMatrix()) {
+ renderer.concatMatrix(*properties().getAnimationMatrix());
+ }
+ if (properties().hasTransformMatrix()) {
+ if (properties().isTransformTranslateOnly()) {
+ renderer.translate(properties().getTranslationX(), properties().getTranslationY());
+ } else {
+ renderer.concatMatrix(*properties().getTransformMatrix());
+ }
+ }
+ const bool isLayer = properties().layerProperties().type() != kLayerTypeNone;
+ int clipFlags = properties().getClippingFlags();
+ if (properties().getAlpha() < 1) {
+ if (isLayer) {
+ clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
+
+ renderer.setOverrideLayerAlpha(properties().getAlpha());
+ } else if (!properties().getHasOverlappingRendering()) {
+ renderer.scaleAlpha(properties().getAlpha());
+ } else {
+ Rect layerBounds(0, 0, getWidth(), getHeight());
+ int saveFlags = SkCanvas::kHasAlphaLayer_SaveFlag;
+ if (clipFlags) {
+ saveFlags |= SkCanvas::kClipToLayer_SaveFlag;
+ properties().getClippingRectForFlags(clipFlags, &layerBounds);
+ clipFlags = 0; // all clipping done by saveLayer
+ }
+
+ SaveLayerOp* op = new (handler.allocator()) SaveLayerOp(
+ layerBounds.left, layerBounds.top, layerBounds.right, layerBounds.bottom,
+ properties().getAlpha() * 255, saveFlags);
+ handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
+ }
+ }
+ if (clipFlags) {
+ Rect clipRect;
+ properties().getClippingRectForFlags(clipFlags, &clipRect);
+ ClipRectOp* op = new (handler.allocator()) ClipRectOp(
+ clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
+ SkRegion::kIntersect_Op);
+ handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
+ }
+
+ // TODO: support both reveal clip and outline clip simultaneously
+ if (mProperties.getRevealClip().willClip()) {
+ Rect bounds;
+ mProperties.getRevealClip().getBounds(&bounds);
+ renderer.setClippingRoundRect(handler.allocator(), bounds, mProperties.getRevealClip().getRadius());
+ } else if (mProperties.getOutline().willClip()) {
+ renderer.setClippingOutline(handler.allocator(), &(mProperties.getOutline()));
+ }
+
+}
+
+/**
+ * Apply property-based transformations to input matrix
+ *
+ * If true3dTransform is set to true, the transform applied to the input matrix will use true 4x4
+ * matrix computation instead of the Skia 3x3 matrix + camera hackery.
+ */
+void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform) const {
+ if (properties().getLeft() != 0 || properties().getTop() != 0) {
+ matrix.translate(properties().getLeft(), properties().getTop());
+ }
+ if (properties().getStaticMatrix()) {
+ mat4 stat(*properties().getStaticMatrix());
+ matrix.multiply(stat);
+ } else if (properties().getAnimationMatrix()) {
+ mat4 anim(*properties().getAnimationMatrix());
+ matrix.multiply(anim);
+ }
+
+ bool applyTranslationZ = true3dTransform && !MathUtils::isZero(properties().getZ());
+ if (properties().hasTransformMatrix() || applyTranslationZ) {
+ if (properties().isTransformTranslateOnly()) {
+ matrix.translate(properties().getTranslationX(), properties().getTranslationY(),
+ true3dTransform ? properties().getZ() : 0.0f);
+ } else {
+ if (!true3dTransform) {
+ matrix.multiply(*properties().getTransformMatrix());
+ } else {
+ mat4 true3dMat;
+ true3dMat.loadTranslate(
+ properties().getPivotX() + properties().getTranslationX(),
+ properties().getPivotY() + properties().getTranslationY(),
+ properties().getZ());
+ true3dMat.rotate(properties().getRotationX(), 1, 0, 0);
+ true3dMat.rotate(properties().getRotationY(), 0, 1, 0);
+ true3dMat.rotate(properties().getRotation(), 0, 0, 1);
+ true3dMat.scale(properties().getScaleX(), properties().getScaleY(), 1);
+ true3dMat.translate(-properties().getPivotX(), -properties().getPivotY());
+
+ matrix.multiply(true3dMat);
+ }
+ }
+ }
+}
+
+/**
+ * Organizes the DisplayList hierarchy to prepare for background projection reordering.
+ *
+ * This should be called before a call to defer() or drawDisplayList()
+ *
+ * Each DisplayList that serves as a 3d root builds its list of composited children,
+ * which are flagged to not draw in the standard draw loop.
+ */
+void RenderNode::computeOrdering() {
+ ATRACE_CALL();
+ mProjectedNodes.clear();
+
+ // TODO: create temporary DDLOp and call computeOrderingImpl on top DisplayList so that
+ // transform properties are applied correctly to top level children
+ if (mDisplayListData == NULL) return;
+ for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
+ DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
+ childOp->mRenderNode->computeOrderingImpl(childOp,
+ properties().getOutline().getPath(), &mProjectedNodes, &mat4::identity());
+ }
+}
+
+void RenderNode::computeOrderingImpl(
+ DrawRenderNodeOp* opState,
+ const SkPath* outlineOfProjectionSurface,
+ Vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
+ const mat4* transformFromProjectionSurface) {
+ mProjectedNodes.clear();
+ if (mDisplayListData == NULL || mDisplayListData->isEmpty()) return;
+
+ // TODO: should avoid this calculation in most cases
+ // TODO: just calculate single matrix, down to all leaf composited elements
+ Matrix4 localTransformFromProjectionSurface(*transformFromProjectionSurface);
+ localTransformFromProjectionSurface.multiply(opState->mTransformFromParent);
+
+ if (properties().getProjectBackwards()) {
+ // composited projectee, flag for out of order draw, save matrix, and store in proj surface
+ opState->mSkipInOrderDraw = true;
+ opState->mTransformFromCompositingAncestor.load(localTransformFromProjectionSurface);
+ compositedChildrenOfProjectionSurface->add(opState);
+ } else {
+ // standard in order draw
+ opState->mSkipInOrderDraw = false;
+ }
+
+ if (mDisplayListData->children().size() > 0) {
+ const bool isProjectionReceiver = mDisplayListData->projectionReceiveIndex >= 0;
+ bool haveAppliedPropertiesToProjection = false;
+ for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
+ DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
+ RenderNode* child = childOp->mRenderNode;
+
+ const SkPath* projectionOutline = NULL;
+ Vector<DrawRenderNodeOp*>* projectionChildren = NULL;
+ const mat4* projectionTransform = NULL;
+ if (isProjectionReceiver && !child->properties().getProjectBackwards()) {
+ // if receiving projections, collect projecting descendent
+
+ // Note that if a direct descendent is projecting backwards, we pass it's
+ // grandparent projection collection, since it shouldn't project onto it's
+ // parent, where it will already be drawing.
+ projectionOutline = properties().getOutline().getPath();
+ projectionChildren = &mProjectedNodes;
+ projectionTransform = &mat4::identity();
+ } else {
+ if (!haveAppliedPropertiesToProjection) {
+ applyViewPropertyTransforms(localTransformFromProjectionSurface);
+ haveAppliedPropertiesToProjection = true;
+ }
+ projectionOutline = outlineOfProjectionSurface;
+ projectionChildren = compositedChildrenOfProjectionSurface;
+ projectionTransform = &localTransformFromProjectionSurface;
+ }
+ child->computeOrderingImpl(childOp,
+ projectionOutline, projectionChildren, projectionTransform);
+ }
+ }
+}
+
+class DeferOperationHandler {
+public:
+ DeferOperationHandler(DeferStateStruct& deferStruct, int level)
+ : mDeferStruct(deferStruct), mLevel(level) {}
+ inline void operator()(DisplayListOp* operation, int saveCount, bool clipToBounds) {
+ operation->defer(mDeferStruct, saveCount, mLevel, clipToBounds);
+ }
+ inline LinearAllocator& allocator() { return *(mDeferStruct.mAllocator); }
+ inline void startMark(const char* name) {} // do nothing
+ inline void endMark() {}
+ inline int level() { return mLevel; }
+ inline int replayFlags() { return mDeferStruct.mReplayFlags; }
+ inline SkPath* allocPathForFrame() { return mDeferStruct.allocPathForFrame(); }
+
+private:
+ DeferStateStruct& mDeferStruct;
+ const int mLevel;
+};
+
+void RenderNode::defer(DeferStateStruct& deferStruct, const int level) {
+ DeferOperationHandler handler(deferStruct, level);
+ issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler);
+}
+
+class ReplayOperationHandler {
+public:
+ ReplayOperationHandler(ReplayStateStruct& replayStruct, int level)
+ : mReplayStruct(replayStruct), mLevel(level) {}
+ inline void operator()(DisplayListOp* operation, int saveCount, bool clipToBounds) {
+#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS
+ mReplayStruct.mRenderer.eventMark(operation->name());
+#endif
+ operation->replay(mReplayStruct, saveCount, mLevel, clipToBounds);
+ }
+ inline LinearAllocator& allocator() { return *(mReplayStruct.mAllocator); }
+ inline void startMark(const char* name) {
+ mReplayStruct.mRenderer.startMark(name);
+ }
+ inline void endMark() {
+ mReplayStruct.mRenderer.endMark();
+ }
+ inline int level() { return mLevel; }
+ inline int replayFlags() { return mReplayStruct.mReplayFlags; }
+ inline SkPath* allocPathForFrame() { return mReplayStruct.allocPathForFrame(); }
+
+private:
+ ReplayStateStruct& mReplayStruct;
+ const int mLevel;
+};
+
+void RenderNode::replay(ReplayStateStruct& replayStruct, const int level) {
+ ReplayOperationHandler handler(replayStruct, level);
+ issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler);
+}
+
+void RenderNode::buildZSortedChildList(Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) {
+ if (mDisplayListData == NULL || mDisplayListData->children().size() == 0) return;
+
+ for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) {
+ DrawRenderNodeOp* childOp = mDisplayListData->children()[i];
+ RenderNode* child = childOp->mRenderNode;
+ float childZ = child->properties().getZ();
+
+ if (!MathUtils::isZero(childZ)) {
+ zTranslatedNodes.add(ZDrawRenderNodeOpPair(childZ, childOp));
+ childOp->mSkipInOrderDraw = true;
+ } else if (!child->properties().getProjectBackwards()) {
+ // regular, in order drawing DisplayList
+ childOp->mSkipInOrderDraw = false;
+ }
+ }
+
+ // Z sort 3d children (stable-ness makes z compare fall back to standard drawing order)
+ std::stable_sort(zTranslatedNodes.begin(), zTranslatedNodes.end());
+}
+
+template <class T>
+void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler) {
+ if (properties().getAlpha() <= 0.0f
+ || properties().getOutline().getAlpha() <= 0.0f
+ || !properties().getOutline().getPath()) {
+ // no shadow to draw
+ return;
+ }
+
+ mat4 shadowMatrixXY(transformFromParent);
+ applyViewPropertyTransforms(shadowMatrixXY);
+
+ // Z matrix needs actual 3d transformation, so mapped z values will be correct
+ mat4 shadowMatrixZ(transformFromParent);
+ applyViewPropertyTransforms(shadowMatrixZ, true);
+
+ const SkPath* casterOutlinePath = properties().getOutline().getPath();
+ const SkPath* revealClipPath = properties().getRevealClip().getPath();
+ if (revealClipPath && revealClipPath->isEmpty()) return;
+
+ float casterAlpha = properties().getAlpha() * properties().getOutline().getAlpha();
+
+ const SkPath* outlinePath = casterOutlinePath;
+ if (revealClipPath) {
+ // if we can't simply use the caster's path directly, create a temporary one
+ SkPath* frameAllocatedPath = handler.allocPathForFrame();
+
+ // intersect the outline with the convex reveal clip
+ Op(*casterOutlinePath, *revealClipPath, kIntersect_PathOp, frameAllocatedPath);
+ outlinePath = frameAllocatedPath;
+ }
+
+ DisplayListOp* shadowOp = new (handler.allocator()) DrawShadowOp(
+ shadowMatrixXY, shadowMatrixZ, casterAlpha, outlinePath);
+ handler(shadowOp, PROPERTY_SAVECOUNT, properties().getClipToBounds());
+}
+
+template <class T>
+int RenderNode::issueOperationsOfNegZChildren(
+ const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+ OpenGLRenderer& renderer, T& handler) {
+ if (zTranslatedNodes.isEmpty()) return -1;
+
+ // create a save around the body of the ViewGroup's draw method, so that
+ // matrix/clip methods don't affect composited children
+ int shadowSaveCount = renderer.getSaveCount();
+ handler(new (handler.allocator()) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
+ PROPERTY_SAVECOUNT, properties().getClipToBounds());
+
+ issueOperationsOf3dChildren(zTranslatedNodes, kNegativeZChildren, renderer, handler);
+ return shadowSaveCount;
+}
+
+template <class T>
+void RenderNode::issueOperationsOfPosZChildren(int shadowRestoreTo,
+ const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+ OpenGLRenderer& renderer, T& handler) {
+ if (zTranslatedNodes.isEmpty()) return;
+
+ LOG_ALWAYS_FATAL_IF(shadowRestoreTo < 0, "invalid save to restore to");
+ handler(new (handler.allocator()) RestoreToCountOp(shadowRestoreTo),
+ PROPERTY_SAVECOUNT, properties().getClipToBounds());
+ renderer.setOverrideLayerAlpha(1.0f);
+
+ issueOperationsOf3dChildren(zTranslatedNodes, kPositiveZChildren, renderer, handler);
+}
+
+#define SHADOW_DELTA 0.1f
+
+template <class T>
+void RenderNode::issueOperationsOf3dChildren(const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+ ChildrenSelectMode mode, OpenGLRenderer& renderer, T& handler) {
+ const int size = zTranslatedNodes.size();
+ if (size == 0
+ || (mode == kNegativeZChildren && zTranslatedNodes[0].key > 0.0f)
+ || (mode == kPositiveZChildren && zTranslatedNodes[size - 1].key < 0.0f)) {
+ // no 3d children to draw
+ return;
+ }
+
+ /**
+ * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters
+ * with very similar Z heights to draw together.
+ *
+ * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are
+ * underneath both, and neither's shadow is drawn on top of the other.
+ */
+ const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes);
+ size_t drawIndex, shadowIndex, endIndex;
+ if (mode == kNegativeZChildren) {
+ drawIndex = 0;
+ endIndex = nonNegativeIndex;
+ shadowIndex = endIndex; // draw no shadows
+ } else {
+ drawIndex = nonNegativeIndex;
+ endIndex = size;
+ shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
+ }
+
+ DISPLAY_LIST_LOGD("%*s%d %s 3d children:", (handler.level() + 1) * 2, "",
+ endIndex - drawIndex, mode == kNegativeZChildren ? "negative" : "positive");
+
+ float lastCasterZ = 0.0f;
+ while (shadowIndex < endIndex || drawIndex < endIndex) {
+ if (shadowIndex < endIndex) {
+ DrawRenderNodeOp* casterOp = zTranslatedNodes[shadowIndex].value;
+ RenderNode* caster = casterOp->mRenderNode;
+ const float casterZ = zTranslatedNodes[shadowIndex].key;
+ // attempt to render the shadow if the caster about to be drawn is its caster,
+ // OR if its caster's Z value is similar to the previous potential caster
+ if (shadowIndex == drawIndex || casterZ - lastCasterZ < SHADOW_DELTA) {
+ caster->issueDrawShadowOperation(casterOp->mTransformFromParent, handler);
+
+ lastCasterZ = casterZ; // must do this even if current caster not casting a shadow
+ shadowIndex++;
+ continue;
+ }
+ }
+
+ // only the actual child DL draw needs to be in save/restore,
+ // since it modifies the renderer's matrix
+ int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
+
+ DrawRenderNodeOp* childOp = zTranslatedNodes[drawIndex].value;
+ RenderNode* child = childOp->mRenderNode;
+
+ renderer.concatMatrix(childOp->mTransformFromParent);
+ childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
+ handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
+ childOp->mSkipInOrderDraw = true;
+
+ renderer.restoreToCount(restoreTo);
+ drawIndex++;
+ }
+}
+
+template <class T>
+void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& handler) {
+ DISPLAY_LIST_LOGD("%*s%d projected children:", (handler.level() + 1) * 2, "", mProjectedNodes.size());
+ const SkPath* projectionReceiverOutline = properties().getOutline().getPath();
+ int restoreTo = renderer.getSaveCount();
+
+ LinearAllocator& alloc = handler.allocator();
+ handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
+ PROPERTY_SAVECOUNT, properties().getClipToBounds());
+
+ // Transform renderer to match background we're projecting onto
+ // (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
+ const DisplayListOp* op =
+ (mDisplayListData->displayListOps[mDisplayListData->projectionReceiveIndex]);
+ const DrawRenderNodeOp* backgroundOp = reinterpret_cast<const DrawRenderNodeOp*>(op);
+ const RenderProperties& backgroundProps = backgroundOp->mRenderNode->properties();
+ renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
+
+ // If the projection reciever has an outline, we mask each of the projected rendernodes to it
+ // Either with clipRect, or special saveLayer masking
+ if (projectionReceiverOutline != NULL) {
+ const SkRect& outlineBounds = projectionReceiverOutline->getBounds();
+ if (projectionReceiverOutline->isRect(NULL)) {
+ // mask to the rect outline simply with clipRect
+ ClipRectOp* clipOp = new (alloc) ClipRectOp(
+ outlineBounds.left(), outlineBounds.top(),
+ outlineBounds.right(), outlineBounds.bottom(), SkRegion::kIntersect_Op);
+ handler(clipOp, PROPERTY_SAVECOUNT, properties().getClipToBounds());
+ } else {
+ // wrap the projected RenderNodes with a SaveLayer that will mask to the outline
+ SaveLayerOp* op = new (alloc) SaveLayerOp(
+ outlineBounds.left(), outlineBounds.top(),
+ outlineBounds.right(), outlineBounds.bottom(),
+ 255, SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag | SkCanvas::kARGB_ClipLayer_SaveFlag);
+ op->setMask(projectionReceiverOutline);
+ handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
+
+ /* TODO: add optimizations here to take advantage of placement/size of projected
+ * children (which may shrink saveLayer area significantly). This is dependent on
+ * passing actual drawing/dirtying bounds of projected content down to native.
+ */
+ }
+ }
+
+ // draw projected nodes
+ for (size_t i = 0; i < mProjectedNodes.size(); i++) {
+ DrawRenderNodeOp* childOp = mProjectedNodes[i];
+
+ // matrix save, concat, and restore can be done safely without allocating operations
+ int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag);
+ renderer.concatMatrix(childOp->mTransformFromCompositingAncestor);
+ childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone
+ handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds());
+ childOp->mSkipInOrderDraw = true;
+ renderer.restoreToCount(restoreTo);
+ }
+
+ if (projectionReceiverOutline != NULL) {
+ handler(new (alloc) RestoreToCountOp(restoreTo),
+ PROPERTY_SAVECOUNT, properties().getClipToBounds());
+ }
+}
+
+/**
+ * This function serves both defer and replay modes, and will organize the displayList's component
+ * operations for a single frame:
+ *
+ * Every 'simple' state operation that affects just the matrix and alpha (or other factors of
+ * DeferredDisplayState) may be issued directly to the renderer, but complex operations (with custom
+ * defer logic) and operations in displayListOps are issued through the 'handler' which handles the
+ * defer vs replay logic, per operation
+ */
+template <class T>
+void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) {
+ const int level = handler.level();
+ if (mDisplayListData->isEmpty()) {
+ DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, getName());
+ return;
+ }
+
+ const bool drawLayer = (mLayer && (&renderer != mLayer->renderer));
+ // If we are updating the contents of mLayer, we don't want to apply any of
+ // the RenderNode's properties to this issueOperations pass. Those will all
+ // be applied when the layer is drawn, aka when this is true.
+ const bool useViewProperties = (!mLayer || drawLayer);
+ if (useViewProperties) {
+ const Outline& outline = properties().getOutline();
+ if (properties().getAlpha() <= 0 || (outline.getShouldClip() && outline.isEmpty())) {
+ DISPLAY_LIST_LOGD("%*sRejected display list (%p, %s)", level * 2, "", this, getName());
+ return;
+ }
+ }
+
+ handler.startMark(getName());
+
+#if DEBUG_DISPLAY_LIST
+ const Rect& clipRect = renderer.getLocalClipBounds();
+ DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), localClipBounds: %.0f, %.0f, %.0f, %.0f",
+ level * 2, "", this, getName(),
+ clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
+#endif
+
+ LinearAllocator& alloc = handler.allocator();
+ int restoreTo = renderer.getSaveCount();
+ handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag),
+ PROPERTY_SAVECOUNT, properties().getClipToBounds());
+
+ DISPLAY_LIST_LOGD("%*sSave %d %d", (level + 1) * 2, "",
+ SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag, restoreTo);
+
+ if (useViewProperties) {
+ setViewProperties<T>(renderer, handler);
+ }
+
+ bool quickRejected = properties().getClipToBounds()
+ && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight());
+ if (!quickRejected) {
+ if (drawLayer) {
+ handler(new (alloc) DrawLayerOp(mLayer, 0, 0),
+ renderer.getSaveCount() - 1, properties().getClipToBounds());
+ } else {
+ Vector<ZDrawRenderNodeOpPair> zTranslatedNodes;
+ buildZSortedChildList(zTranslatedNodes);
+
+ // for 3d root, draw children with negative z values
+ int shadowRestoreTo = issueOperationsOfNegZChildren(zTranslatedNodes, renderer, handler);
+
+ DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance();
+ const int saveCountOffset = renderer.getSaveCount() - 1;
+ const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex;
+ const int size = static_cast<int>(mDisplayListData->displayListOps.size());
+ for (int i = 0; i < size; i++) {
+ DisplayListOp *op = mDisplayListData->displayListOps[i];
+
+#if DEBUG_DISPLAY_LIST
+ op->output(level + 1);
+#endif
+ logBuffer.writeCommand(level, op->name());
+ handler(op, saveCountOffset, properties().getClipToBounds());
+
+ if (CC_UNLIKELY(i == projectionReceiveIndex && mProjectedNodes.size() > 0)) {
+ issueOperationsOfProjectedChildren(renderer, handler);
+ }
+ }
+
+ // for 3d root, draw children with positive z values
+ issueOperationsOfPosZChildren(shadowRestoreTo, zTranslatedNodes, renderer, handler);
+ }
+ }
+
+ DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (level + 1) * 2, "", restoreTo);
+ handler(new (alloc) RestoreToCountOp(restoreTo),
+ PROPERTY_SAVECOUNT, properties().getClipToBounds());
+ renderer.setOverrideLayerAlpha(1.0f);
+
+ DISPLAY_LIST_LOGD("%*sDone (%p, %s)", level * 2, "", this, getName());
+ handler.endMark();
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
new file mode 100644
index 0000000..afa17d5
--- /dev/null
+++ b/libs/hwui/RenderNode.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef RENDERNODE_H
+#define RENDERNODE_H
+
+#ifndef LOG_TAG
+ #define LOG_TAG "OpenGLRenderer"
+#endif
+
+#include <SkCamera.h>
+#include <SkMatrix.h>
+
+#include <utils/LinearAllocator.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <cutils/compiler.h>
+
+#include <androidfw/ResourceTypes.h>
+
+#include "AnimatorManager.h"
+#include "DamageAccumulator.h"
+#include "Debug.h"
+#include "Matrix.h"
+#include "DeferredDisplayList.h"
+#include "DisplayList.h"
+#include "RenderProperties.h"
+#include "TreeInfo.h"
+
+class SkBitmap;
+class SkPaint;
+class SkPath;
+class SkRegion;
+
+namespace android {
+namespace uirenderer {
+
+class DisplayListOp;
+class DisplayListRenderer;
+class OpenGLRenderer;
+class Rect;
+class Layer;
+class SkiaShader;
+
+class ClipRectOp;
+class SaveLayerOp;
+class SaveOp;
+class RestoreToCountOp;
+class DrawRenderNodeOp;
+
+/**
+ * Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties.
+ *
+ * Recording of canvas commands is somewhat similar to SkPicture, except the canvas-recording
+ * functionality is split between DisplayListRenderer (which manages the recording), DisplayListData
+ * (which holds the actual data), and DisplayList (which holds properties and performs playback onto
+ * a renderer).
+ *
+ * Note that DisplayListData is swapped out from beneath an individual DisplayList when a view's
+ * recorded stream of canvas operations is refreshed. The DisplayList (and its properties) stay
+ * attached.
+ */
+class RenderNode : public VirtualLightRefBase {
+public:
+ enum DirtyPropertyMask {
+ GENERIC = 1 << 1,
+ TRANSLATION_X = 1 << 2,
+ TRANSLATION_Y = 1 << 3,
+ TRANSLATION_Z = 1 << 4,
+ SCALE_X = 1 << 5,
+ SCALE_Y = 1 << 6,
+ ROTATION = 1 << 7,
+ ROTATION_X = 1 << 8,
+ ROTATION_Y = 1 << 9,
+ X = 1 << 10,
+ Y = 1 << 11,
+ Z = 1 << 12,
+ ALPHA = 1 << 13,
+ DISPLAY_LIST = 1 << 14,
+ };
+
+ ANDROID_API RenderNode();
+ ANDROID_API virtual ~RenderNode();
+
+ // See flags defined in DisplayList.java
+ enum ReplayFlag {
+ kReplayFlag_ClipChildren = 0x1
+ };
+
+ ANDROID_API static void outputLogBuffer(int fd);
+
+ ANDROID_API void setStagingDisplayList(DisplayListData* newData);
+
+ void computeOrdering();
+
+ void defer(DeferStateStruct& deferStruct, const int level);
+ void replay(ReplayStateStruct& replayStruct, const int level);
+
+ ANDROID_API void output(uint32_t level = 1);
+ ANDROID_API int getDebugSize();
+
+ bool isRenderable() const {
+ return mDisplayListData && mDisplayListData->hasDrawOps;
+ }
+
+ bool hasProjectionReceiver() const {
+ return mDisplayListData && mDisplayListData->projectionReceiveIndex >= 0;
+ }
+
+ const char* getName() const {
+ return mName.string();
+ }
+
+ void setName(const char* name) {
+ if (name) {
+ char* lastPeriod = strrchr(name, '.');
+ if (lastPeriod) {
+ mName.setTo(lastPeriod + 1);
+ } else {
+ mName.setTo(name);
+ }
+ }
+ }
+
+ bool isPropertyFieldDirty(DirtyPropertyMask field) const {
+ return mDirtyPropertyFields & field;
+ }
+
+ void setPropertyFieldsDirty(uint32_t fields) {
+ mDirtyPropertyFields |= fields;
+ }
+
+ const RenderProperties& properties() const {
+ return mProperties;
+ }
+
+ RenderProperties& animatorProperties() {
+ return mProperties;
+ }
+
+ const RenderProperties& stagingProperties() {
+ return mStagingProperties;
+ }
+
+ RenderProperties& mutateStagingProperties() {
+ return mStagingProperties;
+ }
+
+ int getWidth() {
+ return properties().getWidth();
+ }
+
+ int getHeight() {
+ return properties().getHeight();
+ }
+
+ ANDROID_API virtual void prepareTree(TreeInfo& info);
+ void destroyHardwareResources();
+
+ // UI thread only!
+ ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator);
+
+ void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const;
+
+private:
+ typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair;
+
+ static size_t findNonNegativeIndex(const Vector<ZDrawRenderNodeOpPair>& nodes) {
+ for (size_t i = 0; i < nodes.size(); i++) {
+ if (nodes[i].key >= 0.0f) return i;
+ }
+ return nodes.size();
+ }
+
+ enum ChildrenSelectMode {
+ kNegativeZChildren,
+ kPositiveZChildren
+ };
+
+ void computeOrderingImpl(DrawRenderNodeOp* opState,
+ const SkPath* outlineOfProjectionSurface,
+ Vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface,
+ const mat4* transformFromProjectionSurface);
+
+ template <class T>
+ inline void setViewProperties(OpenGLRenderer& renderer, T& handler);
+
+ void buildZSortedChildList(Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes);
+
+ template<class T>
+ inline void issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler);
+
+ template <class T>
+ inline int issueOperationsOfNegZChildren(
+ const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+ OpenGLRenderer& renderer, T& handler);
+ template <class T>
+ inline void issueOperationsOfPosZChildren(int shadowRestoreTo,
+ const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+ OpenGLRenderer& renderer, T& handler);
+ template <class T>
+ inline void issueOperationsOf3dChildren(const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes,
+ ChildrenSelectMode mode, OpenGLRenderer& renderer, T& handler);
+
+ template <class T>
+ inline void issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& handler);
+
+ /**
+ * Issue the RenderNode's operations into a handler, recursing for subtrees through
+ * DrawRenderNodeOp's defer() or replay() methods
+ */
+ template <class T>
+ inline void issueOperations(OpenGLRenderer& renderer, T& handler);
+
+ class TextContainer {
+ public:
+ size_t length() const {
+ return mByteLength;
+ }
+
+ const char* text() const {
+ return (const char*) mText;
+ }
+
+ size_t mByteLength;
+ const char* mText;
+ };
+
+ void prepareTreeImpl(TreeInfo& info);
+ void pushStagingPropertiesChanges(TreeInfo& info);
+ void pushStagingDisplayListChanges(TreeInfo& info);
+ void prepareSubTree(TreeInfo& info, DisplayListData* subtree);
+ void applyLayerPropertiesToLayer(TreeInfo& info);
+ void prepareLayer(TreeInfo& info, uint32_t dirtyMask);
+ void pushLayerUpdate(TreeInfo& info);
+ void deleteDisplayListData();
+ void damageSelf(TreeInfo& info);
+
+ void incParentRefCount() { mParentCount++; }
+ void decParentRefCount();
+
+ String8 mName;
+
+ uint32_t mDirtyPropertyFields;
+ RenderProperties mProperties;
+ RenderProperties mStagingProperties;
+
+ bool mNeedsDisplayListDataSync;
+ // WARNING: Do not delete this directly, you must go through deleteDisplayListData()!
+ DisplayListData* mDisplayListData;
+ DisplayListData* mStagingDisplayListData;
+
+ friend class AnimatorManager;
+ AnimatorManager mAnimatorManager;
+
+ // Owned by RT. Lifecycle is managed by prepareTree(), with the exception
+ // being in ~RenderNode() which may happen on any thread.
+ Layer* mLayer;
+
+ /**
+ * Draw time state - these properties are only set and used during rendering
+ */
+
+ // for projection surfaces, contains a list of all children items
+ Vector<DrawRenderNodeOp*> mProjectedNodes;
+
+ // How many references our parent(s) have to us. Typically this should alternate
+ // between 2 and 1 (when a staging push happens we inc first then dec)
+ // When this hits 0 we are no longer in the tree, so any hardware resources
+ // (specifically Layers) should be released.
+ // This is *NOT* thread-safe, and should therefore only be tracking
+ // mDisplayListData, not mStagingDisplayListData.
+ uint32_t mParentCount;
+}; // class RenderNode
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* RENDERNODE_H */
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
new file mode 100644
index 0000000..250cadc
--- /dev/null
+++ b/libs/hwui/RenderProperties.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you mPrimitiveFields.may not use this file except in compliance with the License.
+ * You mPrimitiveFields.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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "RenderProperties.h"
+
+#include <utils/Trace.h>
+
+#include <SkCanvas.h>
+#include <SkColorFilter.h>
+#include <SkMatrix.h>
+#include <SkPath.h>
+#include <SkPathOps.h>
+
+#include "Matrix.h"
+#include "OpenGLRenderer.h"
+#include "utils/MathUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+LayerProperties::LayerProperties()
+ : mType(kLayerTypeNone)
+ , mColorFilter(NULL) {
+ reset();
+}
+
+LayerProperties::~LayerProperties() {
+ setType(kLayerTypeNone);
+}
+
+void LayerProperties::reset() {
+ mOpaque = false;
+ setFromPaint(NULL);
+}
+
+bool LayerProperties::setColorFilter(SkColorFilter* filter) {
+ if (mColorFilter == filter) return false;
+ SkRefCnt_SafeAssign(mColorFilter, filter);
+ return true;
+}
+
+bool LayerProperties::setFromPaint(const SkPaint* paint) {
+ bool changed = false;
+ SkXfermode::Mode mode;
+ int alpha;
+ OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode);
+ changed |= setAlpha(static_cast<uint8_t>(alpha));
+ changed |= setXferMode(mode);
+ changed |= setColorFilter(paint ? paint->getColorFilter() : NULL);
+ return changed;
+}
+
+LayerProperties& LayerProperties::operator=(const LayerProperties& other) {
+ setType(other.type());
+ setOpaque(other.opaque());
+ setAlpha(other.alpha());
+ setXferMode(other.xferMode());
+ setColorFilter(other.colorFilter());
+ return *this;
+}
+
+RenderProperties::PrimitiveFields::PrimitiveFields()
+ : mClippingFlags(CLIP_TO_BOUNDS)
+ , mProjectBackwards(false)
+ , mProjectionReceiver(false)
+ , mAlpha(1)
+ , mHasOverlappingRendering(true)
+ , mElevation(0)
+ , mTranslationX(0), mTranslationY(0), mTranslationZ(0)
+ , mRotation(0), mRotationX(0), mRotationY(0)
+ , mScaleX(1), mScaleY(1)
+ , mPivotX(0), mPivotY(0)
+ , mLeft(0), mTop(0), mRight(0), mBottom(0)
+ , mWidth(0), mHeight(0)
+ , mPivotExplicitlySet(false)
+ , mMatrixOrPivotDirty(false) {
+}
+
+RenderProperties::ComputedFields::ComputedFields()
+ : mTransformMatrix(NULL) {
+}
+
+RenderProperties::ComputedFields::~ComputedFields() {
+ delete mTransformMatrix;
+}
+
+RenderProperties::RenderProperties()
+ : mStaticMatrix(NULL)
+ , mAnimationMatrix(NULL) {
+}
+
+RenderProperties::~RenderProperties() {
+ delete mStaticMatrix;
+ delete mAnimationMatrix;
+}
+
+RenderProperties& RenderProperties::operator=(const RenderProperties& other) {
+ if (this != &other) {
+ mPrimitiveFields = other.mPrimitiveFields;
+ setStaticMatrix(other.getStaticMatrix());
+ setAnimationMatrix(other.getAnimationMatrix());
+ setCameraDistance(other.getCameraDistance());
+ mLayerProperties = other.layerProperties();
+
+ // Force recalculation of the matrix, since other's dirty bit may be clear
+ mPrimitiveFields.mMatrixOrPivotDirty = true;
+ updateMatrix();
+ }
+ return *this;
+}
+
+void RenderProperties::debugOutputProperties(const int level) const {
+ if (mPrimitiveFields.mLeft != 0 || mPrimitiveFields.mTop != 0) {
+ ALOGD("%*sTranslate (left, top) %d, %d", level * 2, "", mPrimitiveFields.mLeft, mPrimitiveFields.mTop);
+ }
+ if (mStaticMatrix) {
+ ALOGD("%*sConcatMatrix (static) %p: " SK_MATRIX_STRING,
+ level * 2, "", mStaticMatrix, SK_MATRIX_ARGS(mStaticMatrix));
+ }
+ if (mAnimationMatrix) {
+ ALOGD("%*sConcatMatrix (animation) %p: " SK_MATRIX_STRING,
+ level * 2, "", mAnimationMatrix, SK_MATRIX_ARGS(mAnimationMatrix));
+ }
+ if (hasTransformMatrix()) {
+ if (isTransformTranslateOnly()) {
+ ALOGD("%*sTranslate %.2f, %.2f, %.2f",
+ level * 2, "", getTranslationX(), getTranslationY(), getZ());
+ } else {
+ ALOGD("%*sConcatMatrix %p: " SK_MATRIX_STRING,
+ level * 2, "", mComputedFields.mTransformMatrix, SK_MATRIX_ARGS(mComputedFields.mTransformMatrix));
+ }
+ }
+
+ const bool isLayer = layerProperties().type() != kLayerTypeNone;
+ int clipFlags = getClippingFlags();
+ if (mPrimitiveFields.mAlpha < 1) {
+ if (isLayer) {
+ clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer
+
+ ALOGD("%*sSetOverrideLayerAlpha %.2f", level * 2, "", mPrimitiveFields.mAlpha);
+ } else if (!mPrimitiveFields.mHasOverlappingRendering) {
+ ALOGD("%*sScaleAlpha %.2f", level * 2, "", mPrimitiveFields.mAlpha);
+ } else {
+ Rect layerBounds(0, 0, getWidth(), getHeight());
+ int saveFlags = SkCanvas::kHasAlphaLayer_SaveFlag;
+ if (clipFlags) {
+ saveFlags |= SkCanvas::kClipToLayer_SaveFlag;
+ getClippingRectForFlags(clipFlags, &layerBounds);
+ clipFlags = 0; // all clipping done by saveLayer
+ }
+
+ ALOGD("%*sSaveLayerAlpha %d, %d, %d, %d, %d, 0x%x", level * 2, "",
+ (int)layerBounds.left, (int)layerBounds.top, (int)layerBounds.right, (int)layerBounds.bottom,
+ (int)(mPrimitiveFields.mAlpha * 255), saveFlags);
+ }
+ }
+ if (clipFlags) {
+ Rect clipRect;
+ getClippingRectForFlags(clipFlags, &clipRect);
+ ALOGD("%*sClipRect %d, %d, %d, %d", level * 2, "",
+ (int)clipRect.left, (int)clipRect.top, (int)clipRect.right, (int)clipRect.bottom);
+ }
+}
+
+void RenderProperties::updateMatrix() {
+ if (mPrimitiveFields.mMatrixOrPivotDirty) {
+ if (!mComputedFields.mTransformMatrix) {
+ // only allocate a mPrimitiveFields.matrix if we have a complex transform
+ mComputedFields.mTransformMatrix = new SkMatrix();
+ }
+ if (!mPrimitiveFields.mPivotExplicitlySet) {
+ mPrimitiveFields.mPivotX = mPrimitiveFields.mWidth / 2.0f;
+ mPrimitiveFields.mPivotY = mPrimitiveFields.mHeight / 2.0f;
+ }
+ SkMatrix* transform = mComputedFields.mTransformMatrix;
+ transform->reset();
+ if (MathUtils::isZero(getRotationX()) && MathUtils::isZero(getRotationY())) {
+ transform->setTranslate(getTranslationX(), getTranslationY());
+ transform->preRotate(getRotation(), getPivotX(), getPivotY());
+ transform->preScale(getScaleX(), getScaleY(), getPivotX(), getPivotY());
+ } else {
+ SkMatrix transform3D;
+ mComputedFields.mTransformCamera.save();
+ transform->preScale(getScaleX(), getScaleY(), getPivotX(), getPivotY());
+ mComputedFields.mTransformCamera.rotateX(mPrimitiveFields.mRotationX);
+ mComputedFields.mTransformCamera.rotateY(mPrimitiveFields.mRotationY);
+ mComputedFields.mTransformCamera.rotateZ(-mPrimitiveFields.mRotation);
+ mComputedFields.mTransformCamera.getMatrix(&transform3D);
+ transform3D.preTranslate(-getPivotX(), -getPivotY());
+ transform3D.postTranslate(getPivotX() + getTranslationX(),
+ getPivotY() + getTranslationY());
+ transform->postConcat(transform3D);
+ mComputedFields.mTransformCamera.restore();
+ }
+ mPrimitiveFields.mMatrixOrPivotDirty = false;
+ }
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
new file mode 100644
index 0000000..0c8d07f
--- /dev/null
+++ b/libs/hwui/RenderProperties.h
@@ -0,0 +1,623 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef RENDERNODEPROPERTIES_H
+#define RENDERNODEPROPERTIES_H
+
+#include <algorithm>
+#include <stddef.h>
+#include <vector>
+#include <cutils/compiler.h>
+#include <androidfw/ResourceTypes.h>
+#include <utils/Log.h>
+
+#include <SkCamera.h>
+#include <SkMatrix.h>
+#include <SkRegion.h>
+
+#include "Animator.h"
+#include "Rect.h"
+#include "RevealClip.h"
+#include "Outline.h"
+#include "utils/MathUtils.h"
+
+class SkBitmap;
+class SkColorFilter;
+class SkPaint;
+
+namespace android {
+namespace uirenderer {
+
+class Matrix4;
+class RenderNode;
+class RenderProperties;
+
+// The __VA_ARGS__ will be executed if a & b are not equal
+#define RP_SET(a, b, ...) (a != b ? (a = b, ##__VA_ARGS__, true) : false)
+#define RP_SET_AND_DIRTY(a, b) RP_SET(a, b, mPrimitiveFields.mMatrixOrPivotDirty = true)
+
+// Keep in sync with View.java:LAYER_TYPE_*
+enum LayerType {
+ kLayerTypeNone = 0,
+ // Although we cannot build the software layer directly (must be done at
+ // record time), this information is used when applying alpha.
+ kLayerTypeSoftware = 1,
+ kLayerTypeRenderLayer = 2,
+ // TODO: LayerTypeSurfaceTexture? Maybe?
+};
+
+enum ClippingFlags {
+ CLIP_TO_BOUNDS = 0x1 << 0,
+ CLIP_TO_CLIP_BOUNDS = 0x1 << 1,
+};
+
+class ANDROID_API LayerProperties {
+public:
+ bool setType(LayerType type) {
+ if (RP_SET(mType, type)) {
+ reset();
+ return true;
+ }
+ return false;
+ }
+
+ LayerType type() const {
+ return mType;
+ }
+
+ bool setOpaque(bool opaque) {
+ return RP_SET(mOpaque, opaque);
+ }
+
+ bool opaque() const {
+ return mOpaque;
+ }
+
+ bool setAlpha(uint8_t alpha) {
+ return RP_SET(mAlpha, alpha);
+ }
+
+ uint8_t alpha() const {
+ return mAlpha;
+ }
+
+ bool setXferMode(SkXfermode::Mode mode) {
+ return RP_SET(mMode, mode);
+ }
+
+ SkXfermode::Mode xferMode() const {
+ return mMode;
+ }
+
+ bool setColorFilter(SkColorFilter* filter);
+
+ SkColorFilter* colorFilter() const {
+ return mColorFilter;
+ }
+
+ // Sets alpha, xfermode, and colorfilter from an SkPaint
+ // paint may be NULL, in which case defaults will be set
+ bool setFromPaint(const SkPaint* paint);
+
+ bool needsBlending() const {
+ return !opaque() || alpha() < 255;
+ }
+
+ LayerProperties& operator=(const LayerProperties& other);
+
+private:
+ LayerProperties();
+ ~LayerProperties();
+ void reset();
+
+ friend class RenderProperties;
+
+ LayerType mType;
+ // Whether or not that Layer's content is opaque, doesn't include alpha
+ bool mOpaque;
+ uint8_t mAlpha;
+ SkXfermode::Mode mMode;
+ SkColorFilter* mColorFilter;
+};
+
+/*
+ * Data structure that holds the properties for a RenderNode
+ */
+class ANDROID_API RenderProperties {
+public:
+ RenderProperties();
+ virtual ~RenderProperties();
+
+ static bool setFlag(int flag, bool newValue, int* outFlags) {
+ if (newValue) {
+ if (!(flag & *outFlags)) {
+ *outFlags |= flag;
+ return true;
+ }
+ return false;
+ } else {
+ if (flag & *outFlags) {
+ *outFlags &= ~flag;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ RenderProperties& operator=(const RenderProperties& other);
+
+ bool setClipToBounds(bool clipToBounds) {
+ return setFlag(CLIP_TO_BOUNDS, clipToBounds, &mPrimitiveFields.mClippingFlags);
+ }
+
+ bool setClipBounds(const Rect& clipBounds) {
+ bool ret = setFlag(CLIP_TO_CLIP_BOUNDS, true, &mPrimitiveFields.mClippingFlags);
+ return RP_SET(mPrimitiveFields.mClipBounds, clipBounds) || ret;
+ }
+
+ bool setClipBoundsEmpty() {
+ return setFlag(CLIP_TO_CLIP_BOUNDS, false, &mPrimitiveFields.mClippingFlags);
+ }
+
+ bool setProjectBackwards(bool shouldProject) {
+ return RP_SET(mPrimitiveFields.mProjectBackwards, shouldProject);
+ }
+
+ bool setProjectionReceiver(bool shouldRecieve) {
+ return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldRecieve);
+ }
+
+ bool isProjectionReceiver() const {
+ return mPrimitiveFields.mProjectionReceiver;
+ }
+
+ bool setStaticMatrix(const SkMatrix* matrix) {
+ delete mStaticMatrix;
+ if (matrix) {
+ mStaticMatrix = new SkMatrix(*matrix);
+ } else {
+ mStaticMatrix = NULL;
+ }
+ return true;
+ }
+
+ // Can return NULL
+ const SkMatrix* getStaticMatrix() const {
+ return mStaticMatrix;
+ }
+
+ bool setAnimationMatrix(const SkMatrix* matrix) {
+ delete mAnimationMatrix;
+ if (matrix) {
+ mAnimationMatrix = new SkMatrix(*matrix);
+ } else {
+ mAnimationMatrix = NULL;
+ }
+ return true;
+ }
+
+ bool setAlpha(float alpha) {
+ alpha = MathUtils::clampAlpha(alpha);
+ return RP_SET(mPrimitiveFields.mAlpha, alpha);
+ }
+
+ float getAlpha() const {
+ return mPrimitiveFields.mAlpha;
+ }
+
+ bool setHasOverlappingRendering(bool hasOverlappingRendering) {
+ return RP_SET(mPrimitiveFields.mHasOverlappingRendering, hasOverlappingRendering);
+ }
+
+ bool hasOverlappingRendering() const {
+ return mPrimitiveFields.mHasOverlappingRendering;
+ }
+
+ bool setElevation(float elevation) {
+ return RP_SET(mPrimitiveFields.mElevation, elevation);
+ // Don't dirty matrix/pivot, since they don't respect Z
+ }
+
+ float getElevation() const {
+ return mPrimitiveFields.mElevation;
+ }
+
+ bool setTranslationX(float translationX) {
+ return RP_SET_AND_DIRTY(mPrimitiveFields.mTranslationX, translationX);
+ }
+
+ float getTranslationX() const {
+ return mPrimitiveFields.mTranslationX;
+ }
+
+ bool setTranslationY(float translationY) {
+ return RP_SET_AND_DIRTY(mPrimitiveFields.mTranslationY, translationY);
+ }
+
+ float getTranslationY() const {
+ return mPrimitiveFields.mTranslationY;
+ }
+
+ bool setTranslationZ(float translationZ) {
+ return RP_SET(mPrimitiveFields.mTranslationZ, translationZ);
+ // mMatrixOrPivotDirty not set, since matrix doesn't respect Z
+ }
+
+ float getTranslationZ() const {
+ return mPrimitiveFields.mTranslationZ;
+ }
+
+ // Animation helper
+ bool setX(float value) {
+ return setTranslationX(value - getLeft());
+ }
+
+ // Animation helper
+ float getX() const {
+ return getLeft() + getTranslationX();
+ }
+
+ // Animation helper
+ bool setY(float value) {
+ return setTranslationY(value - getTop());
+ }
+
+ // Animation helper
+ float getY() const {
+ return getTop() + getTranslationY();
+ }
+
+ // Animation helper
+ bool setZ(float value) {
+ return setTranslationZ(value - getElevation());
+ }
+
+ float getZ() const {
+ return getElevation() + getTranslationZ();
+ }
+
+ bool setRotation(float rotation) {
+ return RP_SET_AND_DIRTY(mPrimitiveFields.mRotation, rotation);
+ }
+
+ float getRotation() const {
+ return mPrimitiveFields.mRotation;
+ }
+
+ bool setRotationX(float rotationX) {
+ return RP_SET_AND_DIRTY(mPrimitiveFields.mRotationX, rotationX);
+ }
+
+ float getRotationX() const {
+ return mPrimitiveFields.mRotationX;
+ }
+
+ bool setRotationY(float rotationY) {
+ return RP_SET_AND_DIRTY(mPrimitiveFields.mRotationY, rotationY);
+ }
+
+ float getRotationY() const {
+ return mPrimitiveFields.mRotationY;
+ }
+
+ bool setScaleX(float scaleX) {
+ return RP_SET_AND_DIRTY(mPrimitiveFields.mScaleX, scaleX);
+ }
+
+ float getScaleX() const {
+ return mPrimitiveFields.mScaleX;
+ }
+
+ bool setScaleY(float scaleY) {
+ return RP_SET_AND_DIRTY(mPrimitiveFields.mScaleY, scaleY);
+ }
+
+ float getScaleY() const {
+ return mPrimitiveFields.mScaleY;
+ }
+
+ bool setPivotX(float pivotX) {
+ if (RP_SET(mPrimitiveFields.mPivotX, pivotX)
+ || !mPrimitiveFields.mPivotExplicitlySet) {
+ mPrimitiveFields.mMatrixOrPivotDirty = true;
+ mPrimitiveFields.mPivotExplicitlySet = true;
+ return true;
+ }
+ return false;
+ }
+
+ /* Note that getPivotX and getPivotY are adjusted by updateMatrix(),
+ * so the value returned may be stale if the RenderProperties has been
+ * modified since the last call to updateMatrix()
+ */
+ float getPivotX() const {
+ return mPrimitiveFields.mPivotX;
+ }
+
+ bool setPivotY(float pivotY) {
+ if (RP_SET(mPrimitiveFields.mPivotY, pivotY)
+ || !mPrimitiveFields.mPivotExplicitlySet) {
+ mPrimitiveFields.mMatrixOrPivotDirty = true;
+ mPrimitiveFields.mPivotExplicitlySet = true;
+ return true;
+ }
+ return false;
+ }
+
+ float getPivotY() const {
+ return mPrimitiveFields.mPivotY;
+ }
+
+ bool isPivotExplicitlySet() const {
+ return mPrimitiveFields.mPivotExplicitlySet;
+ }
+
+ bool setCameraDistance(float distance) {
+ if (distance != getCameraDistance()) {
+ mPrimitiveFields.mMatrixOrPivotDirty = true;
+ mComputedFields.mTransformCamera.setCameraLocation(0, 0, distance);
+ return true;
+ }
+ return false;
+ }
+
+ float getCameraDistance() const {
+ // TODO: update getCameraLocationZ() to be const
+ return const_cast<Sk3DView*>(&mComputedFields.mTransformCamera)->getCameraLocationZ();
+ }
+
+ bool setLeft(int left) {
+ if (RP_SET(mPrimitiveFields.mLeft, left)) {
+ mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft;
+ if (!mPrimitiveFields.mPivotExplicitlySet) {
+ mPrimitiveFields.mMatrixOrPivotDirty = true;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ float getLeft() const {
+ return mPrimitiveFields.mLeft;
+ }
+
+ bool setTop(int top) {
+ if (RP_SET(mPrimitiveFields.mTop, top)) {
+ mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop;
+ if (!mPrimitiveFields.mPivotExplicitlySet) {
+ mPrimitiveFields.mMatrixOrPivotDirty = true;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ float getTop() const {
+ return mPrimitiveFields.mTop;
+ }
+
+ bool setRight(int right) {
+ if (RP_SET(mPrimitiveFields.mRight, right)) {
+ mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft;
+ if (!mPrimitiveFields.mPivotExplicitlySet) {
+ mPrimitiveFields.mMatrixOrPivotDirty = true;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ float getRight() const {
+ return mPrimitiveFields.mRight;
+ }
+
+ bool setBottom(int bottom) {
+ if (RP_SET(mPrimitiveFields.mBottom, bottom)) {
+ mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop;
+ if (!mPrimitiveFields.mPivotExplicitlySet) {
+ mPrimitiveFields.mMatrixOrPivotDirty = true;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ float getBottom() const {
+ return mPrimitiveFields.mBottom;
+ }
+
+ bool setLeftTop(int left, int top) {
+ bool leftResult = setLeft(left);
+ bool topResult = setTop(top);
+ return leftResult || topResult;
+ }
+
+ bool setLeftTopRightBottom(int left, int top, int right, int bottom) {
+ if (left != mPrimitiveFields.mLeft || top != mPrimitiveFields.mTop
+ || right != mPrimitiveFields.mRight || bottom != mPrimitiveFields.mBottom) {
+ mPrimitiveFields.mLeft = left;
+ mPrimitiveFields.mTop = top;
+ mPrimitiveFields.mRight = right;
+ mPrimitiveFields.mBottom = bottom;
+ mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft;
+ mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop;
+ if (!mPrimitiveFields.mPivotExplicitlySet) {
+ mPrimitiveFields.mMatrixOrPivotDirty = true;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ bool offsetLeftRight(int offset) {
+ if (offset != 0) {
+ mPrimitiveFields.mLeft += offset;
+ mPrimitiveFields.mRight += offset;
+ return true;
+ }
+ return false;
+ }
+
+ bool offsetTopBottom(int offset) {
+ if (offset != 0) {
+ mPrimitiveFields.mTop += offset;
+ mPrimitiveFields.mBottom += offset;
+ return true;
+ }
+ return false;
+ }
+
+ int getWidth() const {
+ return mPrimitiveFields.mWidth;
+ }
+
+ int getHeight() const {
+ return mPrimitiveFields.mHeight;
+ }
+
+ const SkMatrix* getAnimationMatrix() const {
+ return mAnimationMatrix;
+ }
+
+ bool hasTransformMatrix() const {
+ return getTransformMatrix() && !getTransformMatrix()->isIdentity();
+ }
+
+ // May only call this if hasTransformMatrix() is true
+ bool isTransformTranslateOnly() const {
+ return getTransformMatrix()->getType() == SkMatrix::kTranslate_Mask;
+ }
+
+ const SkMatrix* getTransformMatrix() const {
+ LOG_ALWAYS_FATAL_IF(mPrimitiveFields.mMatrixOrPivotDirty, "Cannot get a dirty matrix!");
+ return mComputedFields.mTransformMatrix;
+ }
+
+ int getClippingFlags() const {
+ return mPrimitiveFields.mClippingFlags;
+ }
+
+ bool getClipToBounds() const {
+ return mPrimitiveFields.mClippingFlags & CLIP_TO_BOUNDS;
+ }
+
+ void getClippingRectForFlags(uint32_t flags, Rect* outRect) const {
+ if (flags & CLIP_TO_BOUNDS) {
+ outRect->set(0, 0, getWidth(), getHeight());
+ if (flags & CLIP_TO_CLIP_BOUNDS) {
+ outRect->intersect(mPrimitiveFields.mClipBounds);
+ }
+ } else {
+ outRect->set(mPrimitiveFields.mClipBounds);
+ }
+ }
+
+ bool getHasOverlappingRendering() const {
+ return mPrimitiveFields.mHasOverlappingRendering;
+ }
+
+ const Outline& getOutline() const {
+ return mPrimitiveFields.mOutline;
+ }
+
+ const RevealClip& getRevealClip() const {
+ return mPrimitiveFields.mRevealClip;
+ }
+
+ bool getProjectBackwards() const {
+ return mPrimitiveFields.mProjectBackwards;
+ }
+
+ void debugOutputProperties(const int level) const;
+
+ void updateMatrix();
+
+ Outline& mutableOutline() {
+ return mPrimitiveFields.mOutline;
+ }
+
+ RevealClip& mutableRevealClip() {
+ return mPrimitiveFields.mRevealClip;
+ }
+
+ const LayerProperties& layerProperties() const {
+ return mLayerProperties;
+ }
+
+ LayerProperties& mutateLayerProperties() {
+ return mLayerProperties;
+ }
+
+ // Returns true if damage calculations should be clipped to bounds
+ // TODO: Figure out something better for getZ(), as children should still be
+ // clipped to this RP's bounds. But as we will damage -INT_MAX to INT_MAX
+ // for this RP's getZ() anyway, this can be optimized when we have a
+ // Z damage estimate instead of INT_MAX
+ bool getClipDamageToBounds() const {
+ return getClipToBounds() && (getZ() <= 0 || getOutline().isEmpty());
+ }
+
+private:
+ // Rendering properties
+ struct PrimitiveFields {
+ PrimitiveFields();
+
+ Outline mOutline;
+ RevealClip mRevealClip;
+ int mClippingFlags;
+ bool mProjectBackwards;
+ bool mProjectionReceiver;
+ float mAlpha;
+ bool mHasOverlappingRendering;
+ float mElevation;
+ float mTranslationX, mTranslationY, mTranslationZ;
+ float mRotation, mRotationX, mRotationY;
+ float mScaleX, mScaleY;
+ float mPivotX, mPivotY;
+ int mLeft, mTop, mRight, mBottom;
+ int mWidth, mHeight;
+ bool mPivotExplicitlySet;
+ bool mMatrixOrPivotDirty;
+ Rect mClipBounds;
+ } mPrimitiveFields;
+
+ SkMatrix* mStaticMatrix;
+ SkMatrix* mAnimationMatrix;
+ LayerProperties mLayerProperties;
+
+ /**
+ * These fields are all generated from other properties and are not set directly.
+ */
+ struct ComputedFields {
+ ComputedFields();
+ ~ComputedFields();
+
+ /**
+ * Stores the total transformation of the DisplayList based upon its scalar
+ * translate/rotate/scale properties.
+ *
+ * In the common translation-only case, the matrix isn't necessarily allocated,
+ * and the mTranslation properties are used directly.
+ */
+ SkMatrix* mTransformMatrix;
+
+ Sk3DView mTransformCamera;
+ } mComputedFields;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* RENDERNODEPROPERTIES_H */
diff --git a/libs/hwui/RenderState.cpp b/libs/hwui/RenderState.cpp
new file mode 100644
index 0000000..97f379d
--- /dev/null
+++ b/libs/hwui/RenderState.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 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 "RenderState.h"
+
+namespace android {
+namespace uirenderer {
+
+RenderState::RenderState()
+ : mCaches(NULL)
+ , mViewportWidth(0)
+ , mViewportHeight(0)
+ , mFramebuffer(0) {
+}
+
+RenderState::~RenderState() {
+}
+
+void RenderState::onGLContextCreated() {
+ // This is delayed because the first access of Caches makes GL calls
+ mCaches = &Caches::getInstance();
+ mCaches->init();
+}
+
+void RenderState::setViewport(GLsizei width, GLsizei height) {
+ mViewportWidth = width;
+ mViewportHeight = height;
+ glViewport(0, 0, mViewportWidth, mViewportHeight);
+}
+
+
+void RenderState::getViewport(GLsizei* outWidth, GLsizei* outHeight) {
+ *outWidth = mViewportWidth;
+ *outHeight = mViewportHeight;
+}
+
+void RenderState::bindFramebuffer(GLuint fbo) {
+ if (mFramebuffer != fbo) {
+ mFramebuffer = fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
+ }
+}
+
+void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) {
+ interruptForFunctorInvoke();
+ (*functor)(mode, info);
+ resumeFromFunctorInvoke();
+}
+
+void RenderState::interruptForFunctorInvoke() {
+ if (mCaches->currentProgram) {
+ if (mCaches->currentProgram->isInUse()) {
+ mCaches->currentProgram->remove();
+ mCaches->currentProgram = NULL;
+ }
+ }
+ mCaches->resetActiveTexture();
+ mCaches->unbindMeshBuffer();
+ mCaches->unbindIndicesBuffer();
+ mCaches->resetVertexPointers();
+ mCaches->disableTexCoordsVertexArray();
+ debugOverdraw(false, false);
+}
+
+void RenderState::resumeFromFunctorInvoke() {
+ glViewport(0, 0, mViewportWidth, mViewportHeight);
+ glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
+ debugOverdraw(false, false);
+
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+
+ mCaches->scissorEnabled = glIsEnabled(GL_SCISSOR_TEST);
+ mCaches->enableScissor();
+ mCaches->resetScissor();
+
+ mCaches->activeTexture(0);
+ mCaches->resetBoundTextures();
+
+ mCaches->blend = true;
+ glEnable(GL_BLEND);
+ glBlendFunc(mCaches->lastSrcMode, mCaches->lastDstMode);
+ glBlendEquation(GL_FUNC_ADD);
+}
+
+void RenderState::debugOverdraw(bool enable, bool clear) {
+ if (mCaches->debugOverdraw && mFramebuffer == 0) {
+ if (clear) {
+ mCaches->disableScissor();
+ mCaches->stencil.clear();
+ }
+ if (enable) {
+ mCaches->stencil.enableDebugWrite();
+ } else {
+ mCaches->stencil.disable();
+ }
+ }
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/RenderState.h b/libs/hwui/RenderState.h
new file mode 100644
index 0000000..f7116e2
--- /dev/null
+++ b/libs/hwui/RenderState.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef RENDERSTATE_H
+#define RENDERSTATE_H
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <private/hwui/DrawGlInfo.h>
+
+#include "Caches.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+namespace renderthread {
+class RenderThread;
+}
+
+// TODO: Replace Cache's GL state tracking with this. For now it's more a thin
+// wrapper of Caches for users to migrate to.
+class RenderState {
+ PREVENT_COPY_AND_ASSIGN(RenderState);
+public:
+ void onGLContextCreated();
+
+ void setViewport(GLsizei width, GLsizei height);
+ void getViewport(GLsizei* outWidth, GLsizei* outHeight);
+
+ void bindFramebuffer(GLuint fbo);
+ GLint getFramebuffer() { return mFramebuffer; }
+
+ void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info);
+
+ void debugOverdraw(bool enable, bool clear);
+
+private:
+ friend class renderthread::RenderThread;
+
+ void interruptForFunctorInvoke();
+ void resumeFromFunctorInvoke();
+
+ RenderState();
+ ~RenderState();
+
+ Caches* mCaches;
+
+ GLsizei mViewportWidth;
+ GLsizei mViewportHeight;
+ GLuint mFramebuffer;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* RENDERSTATE_H */
diff --git a/libs/hwui/Renderer.h b/libs/hwui/Renderer.h
new file mode 100644
index 0000000..f5cd266
--- /dev/null
+++ b/libs/hwui/Renderer.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_RENDERER_H
+#define ANDROID_HWUI_RENDERER_H
+
+#include <SkRegion.h>
+
+#include <utils/String8.h>
+
+#include "AssetAtlas.h"
+#include "SkPaint.h"
+
+namespace android {
+
+class Functor;
+struct Res_png_9patch;
+
+namespace uirenderer {
+
+class RenderNode;
+class Layer;
+class Matrix4;
+class SkiaColorFilter;
+class Patch;
+
+enum DrawOpMode {
+ kDrawOpMode_Immediate,
+ kDrawOpMode_Defer,
+ kDrawOpMode_Flush
+};
+
+/**
+ * Hwui's abstract version of Canvas.
+ *
+ * Provides methods for frame state operations, as well as the SkCanvas style transform/clip state,
+ * and varied drawing operations.
+ *
+ * Should at some point interact with native SkCanvas.
+ */
+class ANDROID_API Renderer {
+public:
+ virtual ~Renderer() {}
+
+ /**
+ * Safely retrieves the mode from the specified xfermode. If the specified
+ * xfermode is null, the mode is assumed to be SkXfermode::kSrcOver_Mode.
+ */
+ static inline SkXfermode::Mode getXfermode(SkXfermode* mode) {
+ SkXfermode::Mode resultMode;
+ if (!SkXfermode::AsMode(mode, &resultMode)) {
+ resultMode = SkXfermode::kSrcOver_Mode;
+ }
+ return resultMode;
+ }
+
+// ----------------------------------------------------------------------------
+// Frame state operations
+// ----------------------------------------------------------------------------
+ /**
+ * Sets the dimension of the underlying drawing surface. This method must
+ * be called at least once every time the drawing surface changes size.
+ *
+ * @param width The width in pixels of the underlysing surface
+ * @param height The height in pixels of the underlysing surface
+ */
+ virtual void setViewport(int width, int height) = 0;
+
+ /**
+ * Prepares the renderer to draw a frame. This method must be invoked
+ * at the beginning of each frame. When this method is invoked, the
+ * entire drawing surface is assumed to be redrawn.
+ *
+ * @param opaque If true, the target surface is considered opaque
+ * and will not be cleared. If false, the target surface
+ * will be cleared
+ */
+ virtual status_t prepare(bool opaque) = 0;
+
+ /**
+ * Prepares the renderer to draw a frame. This method must be invoked
+ * at the beginning of each frame. Only the specified rectangle of the
+ * frame is assumed to be dirty. A clip will automatically be set to
+ * the specified rectangle.
+ *
+ * @param left The left coordinate of the dirty rectangle
+ * @param top The top coordinate of the dirty rectangle
+ * @param right The right coordinate of the dirty rectangle
+ * @param bottom The bottom coordinate of the dirty rectangle
+ * @param opaque If true, the target surface is considered opaque
+ * and will not be cleared. If false, the target surface
+ * will be cleared in the specified dirty rectangle
+ */
+ virtual status_t prepareDirty(float left, float top, float right, float bottom,
+ bool opaque) = 0;
+
+ /**
+ * Indicates the end of a frame. This method must be invoked whenever
+ * the caller is done rendering a frame.
+ */
+ virtual void finish() = 0;
+
+// ----------------------------------------------------------------------------
+// Canvas state operations
+// ----------------------------------------------------------------------------
+ // Save (layer)
+ virtual int getSaveCount() const = 0;
+ virtual int save(int flags) = 0;
+ virtual void restore() = 0;
+ virtual void restoreToCount(int saveCount) = 0;
+
+ virtual int saveLayer(float left, float top, float right, float bottom,
+ const SkPaint* paint, int flags) = 0;
+
+ int saveLayerAlpha(float left, float top, float right, float bottom,
+ int alpha, int flags) {
+ SkPaint paint;
+ paint.setAlpha(alpha);
+ return saveLayer(left, top, right, bottom, &paint, flags);
+ }
+
+ // Matrix
+ virtual void getMatrix(SkMatrix* outMatrix) const = 0;
+ virtual void translate(float dx, float dy, float dz = 0.0f) = 0;
+ virtual void rotate(float degrees) = 0;
+ virtual void scale(float sx, float sy) = 0;
+ virtual void skew(float sx, float sy) = 0;
+
+ virtual void setMatrix(const SkMatrix& matrix) = 0;
+ virtual void concatMatrix(const SkMatrix& matrix) = 0;
+
+ // clip
+ virtual const Rect& getLocalClipBounds() const = 0;
+ virtual bool quickRejectConservative(float left, float top,
+ float right, float bottom) const = 0;
+ virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op) = 0;
+ virtual bool clipPath(const SkPath* path, SkRegion::Op op) = 0;
+ virtual bool clipRegion(const SkRegion* region, SkRegion::Op op) = 0;
+
+ // Misc - should be implemented with SkPaint inspection
+ virtual void resetPaintFilter() = 0;
+ virtual void setupPaintFilter(int clearBits, int setBits) = 0;
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations
+// ----------------------------------------------------------------------------
+ virtual status_t drawColor(int color, SkXfermode::Mode mode) = 0;
+
+ // Bitmap-based
+ virtual status_t drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) = 0;
+ virtual status_t drawBitmap(const SkBitmap* bitmap, float srcLeft, float srcTop,
+ float srcRight, float srcBottom, float dstLeft, float dstTop,
+ float dstRight, float dstBottom, const SkPaint* paint) = 0;
+ virtual status_t drawBitmapData(const SkBitmap* bitmap, const SkPaint* paint) = 0;
+ virtual status_t drawBitmapMesh(const SkBitmap* bitmap, int meshWidth, int meshHeight,
+ const float* vertices, const int* colors, const SkPaint* paint) = 0;
+ virtual status_t drawPatch(const SkBitmap* bitmap, const Res_png_9patch* patch,
+ float left, float top, float right, float bottom, const SkPaint* paint) = 0;
+
+ // Shapes
+ virtual status_t drawRect(float left, float top, float right, float bottom,
+ const SkPaint* paint) = 0;
+ virtual status_t drawRects(const float* rects, int count, const SkPaint* paint) = 0;
+ virtual status_t drawRoundRect(float left, float top, float right, float bottom,
+ float rx, float ry, const SkPaint* paint) = 0;
+ virtual status_t drawCircle(float x, float y, float radius, const SkPaint* paint) = 0;
+ virtual status_t drawOval(float left, float top, float right, float bottom,
+ const SkPaint* paint) = 0;
+ virtual status_t drawArc(float left, float top, float right, float bottom,
+ float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) = 0;
+ virtual status_t drawPath(const SkPath* path, const SkPaint* paint) = 0;
+ virtual status_t drawLines(const float* points, int count, const SkPaint* paint) = 0;
+ virtual status_t drawPoints(const float* points, int count, const SkPaint* paint) = 0;
+
+ // Text
+ virtual status_t drawText(const char* text, int bytesCount, int count, float x, float y,
+ const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds,
+ DrawOpMode drawOpMode = kDrawOpMode_Immediate) = 0;
+ virtual status_t drawTextOnPath(const char* text, int bytesCount, int count, const SkPath* path,
+ float hOffset, float vOffset, const SkPaint* paint) = 0;
+ virtual status_t drawPosText(const char* text, int bytesCount, int count,
+ const float* positions, const SkPaint* paint) = 0;
+
+// ----------------------------------------------------------------------------
+// Canvas draw operations - special
+// ----------------------------------------------------------------------------
+ virtual status_t drawLayer(Layer* layer, float x, float y) = 0;
+ virtual status_t drawRenderNode(RenderNode* renderNode, Rect& dirty,
+ int32_t replayFlags) = 0;
+
+ // TODO: rename for consistency
+ virtual status_t callDrawGLFunction(Functor* functor, Rect& dirty) = 0;
+}; // class Renderer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_RENDERER_H
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
index 77292bf..8b553d1 100644
--- a/libs/hwui/ResourceCache.cpp
+++ b/libs/hwui/ResourceCache.cpp
@@ -40,7 +40,7 @@ void ResourceCache::logCache() {
ResourceCache::ResourceCache() {
Mutex::Autolock _l(mLock);
- mCache = new KeyedVector<void*, ResourceReference*>();
+ mCache = new KeyedVector<const void*, ResourceReference*>();
}
ResourceCache::~ResourceCache() {
@@ -61,27 +61,17 @@ void ResourceCache::incrementRefcount(void* resource, ResourceType resourceType)
incrementRefcountLocked(resource, resourceType);
}
-void ResourceCache::incrementRefcount(SkBitmap* bitmapResource) {
+void ResourceCache::incrementRefcount(const SkBitmap* bitmapResource) {
bitmapResource->pixelRef()->globalRef();
SkSafeRef(bitmapResource->getColorTable());
incrementRefcount((void*) bitmapResource, kBitmap);
}
-void ResourceCache::incrementRefcount(SkPath* pathResource) {
+void ResourceCache::incrementRefcount(const SkPath* pathResource) {
incrementRefcount((void*) pathResource, kPath);
}
-void ResourceCache::incrementRefcount(SkiaShader* shaderResource) {
- SkSafeRef(shaderResource->getSkShader());
- incrementRefcount((void*) shaderResource, kShader);
-}
-
-void ResourceCache::incrementRefcount(SkiaColorFilter* filterResource) {
- SkSafeRef(filterResource->getSkColorFilter());
- incrementRefcount((void*) filterResource, kColorFilter);
-}
-
-void ResourceCache::incrementRefcount(Res_png_9patch* patchResource) {
+void ResourceCache::incrementRefcount(const Res_png_9patch* patchResource) {
incrementRefcount((void*) patchResource, kNinePatch);
}
@@ -99,27 +89,17 @@ void ResourceCache::incrementRefcountLocked(void* resource, ResourceType resourc
ref->refCount++;
}
-void ResourceCache::incrementRefcountLocked(SkBitmap* bitmapResource) {
+void ResourceCache::incrementRefcountLocked(const SkBitmap* bitmapResource) {
bitmapResource->pixelRef()->globalRef();
SkSafeRef(bitmapResource->getColorTable());
incrementRefcountLocked((void*) bitmapResource, kBitmap);
}
-void ResourceCache::incrementRefcountLocked(SkPath* pathResource) {
+void ResourceCache::incrementRefcountLocked(const SkPath* pathResource) {
incrementRefcountLocked((void*) pathResource, kPath);
}
-void ResourceCache::incrementRefcountLocked(SkiaShader* shaderResource) {
- SkSafeRef(shaderResource->getSkShader());
- incrementRefcountLocked((void*) shaderResource, kShader);
-}
-
-void ResourceCache::incrementRefcountLocked(SkiaColorFilter* filterResource) {
- SkSafeRef(filterResource->getSkColorFilter());
- incrementRefcountLocked((void*) filterResource, kColorFilter);
-}
-
-void ResourceCache::incrementRefcountLocked(Res_png_9patch* patchResource) {
+void ResourceCache::incrementRefcountLocked(const Res_png_9patch* patchResource) {
incrementRefcountLocked((void*) patchResource, kNinePatch);
}
@@ -132,27 +112,17 @@ void ResourceCache::decrementRefcount(void* resource) {
decrementRefcountLocked(resource);
}
-void ResourceCache::decrementRefcount(SkBitmap* bitmapResource) {
+void ResourceCache::decrementRefcount(const SkBitmap* bitmapResource) {
bitmapResource->pixelRef()->globalUnref();
SkSafeUnref(bitmapResource->getColorTable());
decrementRefcount((void*) bitmapResource);
}
-void ResourceCache::decrementRefcount(SkPath* pathResource) {
+void ResourceCache::decrementRefcount(const SkPath* pathResource) {
decrementRefcount((void*) pathResource);
}
-void ResourceCache::decrementRefcount(SkiaShader* shaderResource) {
- SkSafeUnref(shaderResource->getSkShader());
- decrementRefcount((void*) shaderResource);
-}
-
-void ResourceCache::decrementRefcount(SkiaColorFilter* filterResource) {
- SkSafeUnref(filterResource->getSkColorFilter());
- decrementRefcount((void*) filterResource);
-}
-
-void ResourceCache::decrementRefcount(Res_png_9patch* patchResource) {
+void ResourceCache::decrementRefcount(const Res_png_9patch* patchResource) {
decrementRefcount((void*) patchResource);
}
@@ -173,27 +143,17 @@ void ResourceCache::decrementRefcountLocked(void* resource) {
}
}
-void ResourceCache::decrementRefcountLocked(SkBitmap* bitmapResource) {
+void ResourceCache::decrementRefcountLocked(const SkBitmap* bitmapResource) {
bitmapResource->pixelRef()->globalUnref();
SkSafeUnref(bitmapResource->getColorTable());
decrementRefcountLocked((void*) bitmapResource);
}
-void ResourceCache::decrementRefcountLocked(SkPath* pathResource) {
+void ResourceCache::decrementRefcountLocked(const SkPath* pathResource) {
decrementRefcountLocked((void*) pathResource);
}
-void ResourceCache::decrementRefcountLocked(SkiaShader* shaderResource) {
- SkSafeUnref(shaderResource->getSkShader());
- decrementRefcountLocked((void*) shaderResource);
-}
-
-void ResourceCache::decrementRefcountLocked(SkiaColorFilter* filterResource) {
- SkSafeUnref(filterResource->getSkColorFilter());
- decrementRefcountLocked((void*) filterResource);
-}
-
-void ResourceCache::decrementRefcountLocked(Res_png_9patch* patchResource) {
+void ResourceCache::decrementRefcountLocked(const Res_png_9patch* patchResource) {
decrementRefcountLocked((void*) patchResource);
}
@@ -224,12 +184,12 @@ void ResourceCache::destructorLocked(SkPath* resource) {
}
}
-void ResourceCache::destructor(SkBitmap* resource) {
+void ResourceCache::destructor(const SkBitmap* resource) {
Mutex::Autolock _l(mLock);
destructorLocked(resource);
}
-void ResourceCache::destructorLocked(SkBitmap* resource) {
+void ResourceCache::destructorLocked(const SkBitmap* resource) {
ssize_t index = mCache->indexOfKey(resource);
ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
if (ref == NULL) {
@@ -247,44 +207,6 @@ void ResourceCache::destructorLocked(SkBitmap* resource) {
}
}
-void ResourceCache::destructor(SkiaShader* resource) {
- Mutex::Autolock _l(mLock);
- destructorLocked(resource);
-}
-
-void ResourceCache::destructorLocked(SkiaShader* resource) {
- ssize_t index = mCache->indexOfKey(resource);
- ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
- if (ref == NULL) {
- // If we're not tracking this resource, just delete it
- delete resource;
- return;
- }
- ref->destroyed = true;
- if (ref->refCount == 0) {
- deleteResourceReferenceLocked(resource, ref);
- }
-}
-
-void ResourceCache::destructor(SkiaColorFilter* resource) {
- Mutex::Autolock _l(mLock);
- destructorLocked(resource);
-}
-
-void ResourceCache::destructorLocked(SkiaColorFilter* resource) {
- ssize_t index = mCache->indexOfKey(resource);
- ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
- if (ref == NULL) {
- // If we're not tracking this resource, just delete it
- delete resource;
- return;
- }
- ref->destroyed = true;
- if (ref->refCount == 0) {
- deleteResourceReferenceLocked(resource, ref);
- }
-}
-
void ResourceCache::destructor(Res_png_9patch* resource) {
Mutex::Autolock _l(mLock);
destructorLocked(resource);
@@ -348,7 +270,7 @@ bool ResourceCache::recycleLocked(SkBitmap* resource) {
* This method should only be called while the mLock mutex is held (that mutex is grabbed
* by the various destructor() and recycle() methods which call this method).
*/
-void ResourceCache::deleteResourceReferenceLocked(void* resource, ResourceReference* ref) {
+void ResourceCache::deleteResourceReferenceLocked(const void* resource, ResourceReference* ref) {
if (ref->recycled && ref->resourceType == kBitmap) {
((SkBitmap*) resource)->setPixels(NULL, NULL);
}
@@ -372,16 +294,6 @@ void ResourceCache::deleteResourceReferenceLocked(void* resource, ResourceRefere
}
}
break;
- case kShader: {
- SkiaShader* shader = (SkiaShader*) resource;
- delete shader;
- }
- break;
- case kColorFilter: {
- SkiaColorFilter* filter = (SkiaColorFilter*) resource;
- delete filter;
- }
- break;
case kNinePatch: {
if (Caches::hasInstance()) {
Caches::getInstance().patchCache.removeDeferred((Res_png_9patch*) resource);
diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h
index ea0c1b5..3864d4b 100644
--- a/libs/hwui/ResourceCache.h
+++ b/libs/hwui/ResourceCache.h
@@ -20,8 +20,6 @@
#include <cutils/compiler.h>
#include <SkBitmap.h>
-#include <SkiaColorFilter.h>
-#include <SkiaShader.h>
#include <utils/KeyedVector.h>
@@ -37,8 +35,6 @@ namespace uirenderer {
*/
enum ResourceType {
kBitmap,
- kShader,
- kColorFilter,
kNinePatch,
kPath,
kLayer
@@ -70,51 +66,39 @@ public:
void lock();
void unlock();
- void incrementRefcount(SkPath* resource);
- void incrementRefcount(SkBitmap* resource);
- void incrementRefcount(SkiaShader* resource);
- void incrementRefcount(SkiaColorFilter* resource);
- void incrementRefcount(Res_png_9patch* resource);
+ void incrementRefcount(const SkPath* resource);
+ void incrementRefcount(const SkBitmap* resource);
+ void incrementRefcount(const Res_png_9patch* resource);
void incrementRefcount(Layer* resource);
- void incrementRefcountLocked(SkPath* resource);
- void incrementRefcountLocked(SkBitmap* resource);
- void incrementRefcountLocked(SkiaShader* resource);
- void incrementRefcountLocked(SkiaColorFilter* resource);
- void incrementRefcountLocked(Res_png_9patch* resource);
+ void incrementRefcountLocked(const SkPath* resource);
+ void incrementRefcountLocked(const SkBitmap* resource);
+ void incrementRefcountLocked(const Res_png_9patch* resource);
void incrementRefcountLocked(Layer* resource);
- void decrementRefcount(SkBitmap* resource);
- void decrementRefcount(SkPath* resource);
- void decrementRefcount(SkiaShader* resource);
- void decrementRefcount(SkiaColorFilter* resource);
- void decrementRefcount(Res_png_9patch* resource);
+ void decrementRefcount(const SkBitmap* resource);
+ void decrementRefcount(const SkPath* resource);
+ void decrementRefcount(const Res_png_9patch* resource);
void decrementRefcount(Layer* resource);
- void decrementRefcountLocked(SkBitmap* resource);
- void decrementRefcountLocked(SkPath* resource);
- void decrementRefcountLocked(SkiaShader* resource);
- void decrementRefcountLocked(SkiaColorFilter* resource);
- void decrementRefcountLocked(Res_png_9patch* resource);
+ void decrementRefcountLocked(const SkBitmap* resource);
+ void decrementRefcountLocked(const SkPath* resource);
+ void decrementRefcountLocked(const Res_png_9patch* resource);
void decrementRefcountLocked(Layer* resource);
void destructor(SkPath* resource);
- void destructor(SkBitmap* resource);
- void destructor(SkiaShader* resource);
- void destructor(SkiaColorFilter* resource);
+ void destructor(const SkBitmap* resource);
void destructor(Res_png_9patch* resource);
void destructorLocked(SkPath* resource);
- void destructorLocked(SkBitmap* resource);
- void destructorLocked(SkiaShader* resource);
- void destructorLocked(SkiaColorFilter* resource);
+ void destructorLocked(const SkBitmap* resource);
void destructorLocked(Res_png_9patch* resource);
bool recycle(SkBitmap* resource);
bool recycleLocked(SkBitmap* resource);
private:
- void deleteResourceReferenceLocked(void* resource, ResourceReference* ref);
+ void deleteResourceReferenceLocked(const void* resource, ResourceReference* ref);
void incrementRefcount(void* resource, ResourceType resourceType);
void incrementRefcountLocked(void* resource, ResourceType resourceType);
@@ -131,7 +115,7 @@ private:
*/
mutable Mutex mLock;
- KeyedVector<void*, ResourceReference*>* mCache;
+ KeyedVector<const void*, ResourceReference*>* mCache;
};
}; // namespace uirenderer
diff --git a/libs/hwui/RevealClip.h b/libs/hwui/RevealClip.h
new file mode 100644
index 0000000..a9600f1
--- /dev/null
+++ b/libs/hwui/RevealClip.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef REVEALCLIP_H
+#define REVEALCLIP_H
+
+#include <SkPath.h>
+
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+class RevealClip {
+public:
+ RevealClip()
+ : mShouldClip(false)
+ , mX(0)
+ , mY(0)
+ , mRadius(0) {}
+
+ void set(bool shouldClip, float x, float y, float radius) {
+ mShouldClip = shouldClip;
+ mX = x;
+ mY = y;
+ mRadius = radius;
+
+ mPath.rewind();
+ if (mShouldClip) {
+ mPath.addCircle(x, y, radius);
+ }
+ }
+
+ bool willClip() const {
+ return mShouldClip;
+ }
+
+ void getBounds(Rect* outBounds) const {
+ outBounds->set(mX - mRadius, mY - mRadius,
+ mX + mRadius, mY + mRadius);
+ }
+ float getRadius() const { return mRadius; }
+
+ const SkPath* getPath() const {
+ if (!mShouldClip) return NULL;
+
+ return &mPath;
+ }
+
+private:
+ bool mShouldClip;
+ float mX;
+ float mY;
+ float mRadius;
+ SkPath mPath;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* REVEALCLIP_H */
diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp
new file mode 100644
index 0000000..e71439d
--- /dev/null
+++ b/libs/hwui/ShadowTessellator.cpp
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include <math.h>
+#include <utils/Log.h>
+#include <utils/Trace.h>
+
+#include "AmbientShadow.h"
+#include "Caches.h"
+#include "ShadowTessellator.h"
+#include "SpotShadow.h"
+
+namespace android {
+namespace uirenderer {
+
+template<typename T>
+static inline T max(T a, T b) {
+ return a > b ? a : b;
+}
+
+void ShadowTessellator::tessellateAmbientShadow(bool isCasterOpaque,
+ const Vector3* casterPolygon, int casterVertexCount,
+ const Vector3& centroid3d, const Rect& casterBounds,
+ const Rect& localClip, float maxZ, VertexBuffer& shadowVertexBuffer) {
+ ATRACE_CALL();
+
+ // A bunch of parameters to tweak the shadow.
+ // TODO: Allow some of these changable by debug settings or APIs.
+ float heightFactor = 1.0f / 128;
+ const float geomFactor = 64;
+
+ Caches& caches = Caches::getInstance();
+ if (CC_UNLIKELY(caches.propertyAmbientRatio > 0.0f)) {
+ heightFactor *= caches.propertyAmbientRatio;
+ }
+
+ Rect ambientShadowBounds(casterBounds);
+ ambientShadowBounds.outset(maxZ * geomFactor * heightFactor);
+
+ if (!localClip.intersects(ambientShadowBounds)) {
+#if DEBUG_SHADOW
+ ALOGD("Ambient shadow is out of clip rect!");
+#endif
+ return;
+ }
+
+ AmbientShadow::createAmbientShadow(isCasterOpaque, casterPolygon,
+ casterVertexCount, centroid3d, heightFactor, geomFactor,
+ shadowVertexBuffer);
+}
+
+void ShadowTessellator::tessellateSpotShadow(bool isCasterOpaque,
+ const Vector3* casterPolygon, int casterVertexCount,
+ const mat4& receiverTransform, const Vector3& lightCenter, int lightRadius,
+ const Rect& casterBounds, const Rect& localClip, VertexBuffer& shadowVertexBuffer) {
+ ATRACE_CALL();
+
+ Caches& caches = Caches::getInstance();
+
+ Vector3 adjustedLightCenter(lightCenter);
+ if (CC_UNLIKELY(caches.propertyLightPosY > 0)) {
+ adjustedLightCenter.y = - caches.propertyLightPosY; // negated since this shifts up
+ }
+ if (CC_UNLIKELY(caches.propertyLightPosZ > 0)) {
+ adjustedLightCenter.z = caches.propertyLightPosZ;
+ }
+
+#if DEBUG_SHADOW
+ ALOGD("light center %f %f %f",
+ adjustedLightCenter.x, adjustedLightCenter.y, adjustedLightCenter.z);
+#endif
+
+ // light position (because it's in local space) needs to compensate for receiver transform
+ // TODO: should apply to light orientation, not just position
+ Matrix4 reverseReceiverTransform;
+ reverseReceiverTransform.loadInverse(receiverTransform);
+ reverseReceiverTransform.mapPoint3d(adjustedLightCenter);
+
+ const int lightVertexCount = 8;
+ if (CC_UNLIKELY(caches.propertyLightDiameter > 0)) {
+ lightRadius = caches.propertyLightDiameter;
+ }
+
+ // Now light and caster are both in local space, we will check whether
+ // the shadow is within the clip area.
+ Rect lightRect = Rect(adjustedLightCenter.x - lightRadius, adjustedLightCenter.y - lightRadius,
+ adjustedLightCenter.x + lightRadius, adjustedLightCenter.y + lightRadius);
+ lightRect.unionWith(localClip);
+ if (!lightRect.intersects(casterBounds)) {
+#if DEBUG_SHADOW
+ ALOGD("Spot shadow is out of clip rect!");
+#endif
+ return;
+ }
+
+ SpotShadow::createSpotShadow(isCasterOpaque,
+ casterPolygon, casterVertexCount, adjustedLightCenter, lightRadius,
+ lightVertexCount, shadowVertexBuffer);
+#if DEBUG_SHADOW
+ if(shadowVertexBuffer.getVertexCount() <= 0) {
+ ALOGD("Spot shadow generation failed %d", shadowVertexBuffer.getVertexCount());
+ }
+#endif
+}
+
+void ShadowTessellator::generateShadowIndices(uint16_t* shadowIndices) {
+ int currentIndex = 0;
+ const int rays = SHADOW_RAY_COUNT;
+ // For the penumbra area.
+ for (int layer = 0; layer < 2; layer ++) {
+ int baseIndex = layer * rays;
+ for (int i = 0; i < rays; i++) {
+ shadowIndices[currentIndex++] = i + baseIndex;
+ shadowIndices[currentIndex++] = rays + i + baseIndex;
+ }
+ // To close the loop, back to the ray 0.
+ shadowIndices[currentIndex++] = 0 + baseIndex;
+ // Note this is the same as the first index of next layer loop.
+ shadowIndices[currentIndex++] = rays + baseIndex;
+ }
+
+#if DEBUG_SHADOW
+ if (currentIndex != MAX_SHADOW_INDEX_COUNT) {
+ ALOGW("vertex index count is wrong. current %d, expected %d",
+ currentIndex, MAX_SHADOW_INDEX_COUNT);
+ }
+ for (int i = 0; i < MAX_SHADOW_INDEX_COUNT; i++) {
+ ALOGD("vertex index is (%d, %d)", i, shadowIndices[i]);
+ }
+#endif
+}
+
+/**
+ * Calculate the centroid of a 2d polygon.
+ *
+ * @param poly The polygon, which is represented in a Vector2 array.
+ * @param polyLength The length of the polygon in terms of number of vertices.
+ * @return the centroid of the polygon.
+ */
+Vector2 ShadowTessellator::centroid2d(const Vector2* poly, int polyLength) {
+ double sumx = 0;
+ double sumy = 0;
+ int p1 = polyLength - 1;
+ double area = 0;
+ for (int p2 = 0; p2 < polyLength; p2++) {
+ double x1 = poly[p1].x;
+ double y1 = poly[p1].y;
+ double x2 = poly[p2].x;
+ double y2 = poly[p2].y;
+ double a = (x1 * y2 - x2 * y1);
+ sumx += (x1 + x2) * a;
+ sumy += (y1 + y2) * a;
+ area += a;
+ p1 = p2;
+ }
+
+ Vector2 centroid = poly[0];
+ if (area != 0) {
+ centroid = (Vector2){static_cast<float>(sumx / (3 * area)),
+ static_cast<float>(sumy / (3 * area))};
+ } else {
+ ALOGW("Area is 0 while computing centroid!");
+ }
+ return centroid;
+}
+
+/**
+ * Test whether the polygon is order in clockwise.
+ *
+ * @param polygon the polygon as a Vector2 array
+ * @param len the number of points of the polygon
+ */
+bool ShadowTessellator::isClockwise(const Vector2* polygon, int len) {
+ if (len < 2 || polygon == NULL) {
+ return true;
+ }
+ double sum = 0;
+ double p1x = polygon[len - 1].x;
+ double p1y = polygon[len - 1].y;
+ for (int i = 0; i < len; i++) {
+
+ double p2x = polygon[i].x;
+ double p2y = polygon[i].y;
+ sum += p1x * p2y - p2x * p1y;
+ p1x = p2x;
+ p1y = p2y;
+ }
+ return sum < 0;
+}
+
+bool ShadowTessellator::isClockwisePath(const SkPath& path) {
+ SkPath::Iter iter(path, false);
+ SkPoint pts[4];
+ SkPath::Verb v;
+
+ Vector<Vector2> arrayForDirection;
+ while (SkPath::kDone_Verb != (v = iter.next(pts))) {
+ switch (v) {
+ case SkPath::kMove_Verb:
+ arrayForDirection.add((Vector2){pts[0].x(), pts[0].y()});
+ break;
+ case SkPath::kLine_Verb:
+ arrayForDirection.add((Vector2){pts[1].x(), pts[1].y()});
+ break;
+ case SkPath::kQuad_Verb:
+ arrayForDirection.add((Vector2){pts[1].x(), pts[1].y()});
+ arrayForDirection.add((Vector2){pts[2].x(), pts[2].y()});
+ break;
+ case SkPath::kCubic_Verb:
+ arrayForDirection.add((Vector2){pts[1].x(), pts[1].y()});
+ arrayForDirection.add((Vector2){pts[2].x(), pts[2].y()});
+ arrayForDirection.add((Vector2){pts[3].x(), pts[3].y()});
+ break;
+ default:
+ break;
+ }
+ }
+
+ return isClockwise(arrayForDirection.array(), arrayForDirection.size());
+}
+
+void ShadowTessellator::reverseVertexArray(Vertex* polygon, int len) {
+ int n = len / 2;
+ for (int i = 0; i < n; i++) {
+ Vertex tmp = polygon[i];
+ int k = len - 1 - i;
+ polygon[i] = polygon[k];
+ polygon[k] = tmp;
+ }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/ShadowTessellator.h b/libs/hwui/ShadowTessellator.h
new file mode 100644
index 0000000..cb65df5
--- /dev/null
+++ b/libs/hwui/ShadowTessellator.h
@@ -0,0 +1,108 @@
+
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_SHADOW_TESSELLATOR_H
+#define ANDROID_HWUI_SHADOW_TESSELLATOR_H
+
+#include "Debug.h"
+#include "Matrix.h"
+#include "OpenGLRenderer.h"
+#include "VertexBuffer.h"
+
+namespace android {
+namespace uirenderer {
+
+// All SHADOW_* are used to define all the geometry property of shadows.
+// Use a simplified example to illustrate the geometry setup here.
+// Assuming we use 6 rays and only 1 layer, Then we will have 2 hexagons, which
+// are 0 to 5 and 6 to 11. The area between them will be the penumbra area, and
+// the area inside the 2nd hexagon is the umbra.
+// Ambient shadow is using only 1 layer for opaque caster, otherwise, spot
+// shadow and ambient shadow are using 2 layers.
+// Triange strip indices for penumbra area: (0, 6, 1, 7, 2, 8, 3, 9, 4, 10, 5, 11, 0, 6)
+// 0
+//
+// 5 6 1
+// 11 7
+//
+// 10 8
+// 4 9 2
+//
+// 3
+
+// The total number of rays starting from the centroid of shadow area, in order
+// to generate the shadow geometry.
+#define SHADOW_RAY_COUNT 128
+
+// The total number of all the vertices representing the shadow.
+// For the case we only have 1 layer, then we will just fill only 2/3 of it.
+#define SHADOW_VERTEX_COUNT (3 * SHADOW_RAY_COUNT)
+
+// The total number of indices used for drawing the shadow geometry as triangle strips.
+// Depending on the mode we are drawing, we can have 1 layer or 2 layers.
+// Therefore, we only build the longer index buffer.
+#define TWO_POLY_RING_SHADOW_INDEX_COUNT (4 * (SHADOW_RAY_COUNT + 1))
+#define ONE_POLY_RING_SHADOW_INDEX_COUNT (2 * (SHADOW_RAY_COUNT + 1))
+
+#define MAX_SHADOW_INDEX_COUNT TWO_POLY_RING_SHADOW_INDEX_COUNT
+
+#define SHADOW_MIN_CASTER_Z 0.001f
+
+#define MINIMAL_DELTA_THETA (M_PI / 180 / 1000)
+
+class ShadowTessellator {
+public:
+ static void tessellateAmbientShadow(bool isCasterOpaque,
+ const Vector3* casterPolygon, int casterVertexCount,
+ const Vector3& centroid3d, const Rect& casterBounds,
+ const Rect& localClip, float maxZ, VertexBuffer& shadowVertexBuffer);
+
+ static void tessellateSpotShadow(bool isCasterOpaque,
+ const Vector3* casterPolygon, int casterVertexCount,
+ const mat4& receiverTransform, const Vector3& lightCenter, int lightRadius,
+ const Rect& casterBounds, const Rect& localClip, VertexBuffer& shadowVertexBuffer);
+
+ static void generateShadowIndices(uint16_t* shadowIndices);
+
+ static Vector2 centroid2d(const Vector2* poly, int polyLength);
+
+ static bool isClockwise(const Vector2* polygon, int len);
+
+ /**
+ * Determine whether the path is clockwise, using the control points.
+ *
+ * TODO: Given the skia is using inverted Y coordinate, shadow system needs
+ * to convert to the same coordinate to avoid the extra reverse.
+ *
+ * @param path The path to be examined.
+ */
+ static bool isClockwisePath(const SkPath &path);
+
+ /**
+ * Reverse the vertex array.
+ *
+ * @param polygon The vertex array to be reversed.
+ * @param len The length of the vertex array.
+ */
+ static void reverseVertexArray(Vertex* polygon, int len);
+
+}; // ShadowTessellator
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_SHADOW_TESSELLATOR_H
diff --git a/libs/hwui/SkiaColorFilter.cpp b/libs/hwui/SkiaColorFilter.cpp
deleted file mode 100644
index df918be..0000000
--- a/libs/hwui/SkiaColorFilter.cpp
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2010 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 "SkiaColorFilter.h"
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Base color filter
-///////////////////////////////////////////////////////////////////////////////
-
-SkiaColorFilter::SkiaColorFilter(SkColorFilter *skFilter, Type type, bool blend):
- mType(type), mBlend(blend), mSkFilter(skFilter) {
-}
-
-SkiaColorFilter::~SkiaColorFilter() {
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Color matrix filter
-///////////////////////////////////////////////////////////////////////////////
-
-SkiaColorMatrixFilter::SkiaColorMatrixFilter(SkColorFilter* skFilter, float* matrix, float* vector):
- SkiaColorFilter(skFilter, kColorMatrix, true), mMatrix(matrix), mVector(vector) {
- // Skia uses the range [0..255] for the addition vector, but we need
- // the [0..1] range to apply the vector in GLSL
- for (int i = 0; i < 4; i++) {
- mVector[i] /= 255.0f;
- }
-
- // TODO: We should be smarter about this
- mBlend = true;
-}
-
-SkiaColorMatrixFilter::~SkiaColorMatrixFilter() {
- delete[] mMatrix;
- delete[] mVector;
-}
-
-void SkiaColorMatrixFilter::describe(ProgramDescription& description,
- const Extensions& extensions) {
- description.colorOp = ProgramDescription::kColorMatrix;
-}
-
-void SkiaColorMatrixFilter::setupProgram(Program* program) {
- glUniformMatrix4fv(program->getUniform("colorMatrix"), 1, GL_FALSE, &mMatrix[0]);
- glUniform4fv(program->getUniform("colorMatrixVector"), 1, mVector);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Lighting color filter
-///////////////////////////////////////////////////////////////////////////////
-
-SkiaLightingFilter::SkiaLightingFilter(SkColorFilter* skFilter, int multiply, int add):
- SkiaColorFilter(skFilter, kLighting, true) {
- mMulR = ((multiply >> 16) & 0xFF) / 255.0f;
- mMulG = ((multiply >> 8) & 0xFF) / 255.0f;
- mMulB = ((multiply ) & 0xFF) / 255.0f;
-
- mAddR = ((add >> 16) & 0xFF) / 255.0f;
- mAddG = ((add >> 8) & 0xFF) / 255.0f;
- mAddB = ((add ) & 0xFF) / 255.0f;
-
- // A lighting filter always ignores alpha
- mBlend = false;
-}
-
-void SkiaLightingFilter::describe(ProgramDescription& description, const Extensions& extensions) {
- description.colorOp = ProgramDescription::kColorLighting;
-}
-
-void SkiaLightingFilter::setupProgram(Program* program) {
- glUniform4f(program->getUniform("lightingMul"), mMulR, mMulG, mMulB, 1.0f);
- glUniform4f(program->getUniform("lightingAdd"), mAddR, mAddG, mAddB, 0.0f);
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// Blend color filter
-///////////////////////////////////////////////////////////////////////////////
-
-SkiaBlendFilter::SkiaBlendFilter(SkColorFilter* skFilter, int color, SkXfermode::Mode mode):
- SkiaColorFilter(skFilter, kBlend, true), mMode(mode) {
- const int alpha = (color >> 24) & 0xFF;
- mA = alpha / 255.0f;
- mR = mA * ((color >> 16) & 0xFF) / 255.0f;
- mG = mA * ((color >> 8) & 0xFF) / 255.0f;
- mB = mA * ((color ) & 0xFF) / 255.0f;
-
- // TODO: We should do something smarter here
- mBlend = true;
-}
-
-void SkiaBlendFilter::describe(ProgramDescription& description, const Extensions& extensions) {
- description.colorOp = ProgramDescription::kColorBlend;
- description.colorMode = mMode;
-}
-
-void SkiaBlendFilter::setupProgram(Program* program) {
- glUniform4f(program->getUniform("colorBlend"), mR, mG, mB, mA);
-}
-
-}; // namespace uirenderer
-}; // namespace android
diff --git a/libs/hwui/SkiaColorFilter.h b/libs/hwui/SkiaColorFilter.h
deleted file mode 100644
index 2feb834..0000000
--- a/libs/hwui/SkiaColorFilter.h
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HWUI_SKIA_COLOR_FILTER_H
-#define ANDROID_HWUI_SKIA_COLOR_FILTER_H
-
-#include <GLES2/gl2.h>
-#include <SkColorFilter.h>
-
-#include <cutils/compiler.h>
-
-#include "ProgramCache.h"
-#include "Extensions.h"
-
-namespace android {
-namespace uirenderer {
-
-///////////////////////////////////////////////////////////////////////////////
-// Base color filter
-///////////////////////////////////////////////////////////////////////////////
-
-/**
- * Represents a Skia color filter. A color filter modifies a ProgramDescription
- * and sets uniforms on the resulting shaders.
- */
-struct SkiaColorFilter {
- /**
- * Type of Skia color filter in use.
- */
- enum Type {
- kNone,
- kColorMatrix,
- kLighting,
- kBlend,
- };
-
- ANDROID_API SkiaColorFilter(SkColorFilter *skFilter, Type type, bool blend);
- virtual ~SkiaColorFilter();
-
- virtual void describe(ProgramDescription& description, const Extensions& extensions) = 0;
- virtual void setupProgram(Program* program) = 0;
-
- inline bool blend() const {
- return mBlend;
- }
-
- Type type() const {
- return mType;
- }
-
- SkColorFilter* getSkColorFilter() {
- return mSkFilter;
- }
-
-protected:
- Type mType;
- bool mBlend;
-
-private:
- SkColorFilter *mSkFilter;
-}; // struct SkiaColorFilter
-
-///////////////////////////////////////////////////////////////////////////////
-// Implementations
-///////////////////////////////////////////////////////////////////////////////
-
-/**
- * A color filter that multiplies the source color with a matrix and adds a vector.
- */
-struct SkiaColorMatrixFilter: public SkiaColorFilter {
- ANDROID_API SkiaColorMatrixFilter(SkColorFilter *skFilter, float* matrix, float* vector);
- ~SkiaColorMatrixFilter();
-
- void describe(ProgramDescription& description, const Extensions& extensions);
- void setupProgram(Program* program);
-
-private:
- float* mMatrix;
- float* mVector;
-}; // struct SkiaColorMatrixFilter
-
-/**
- * A color filters that multiplies the source color with a fixed value and adds
- * another fixed value. Ignores the alpha channel of both arguments.
- */
-struct SkiaLightingFilter: public SkiaColorFilter {
- ANDROID_API SkiaLightingFilter(SkColorFilter *skFilter, int multiply, int add);
-
- void describe(ProgramDescription& description, const Extensions& extensions);
- void setupProgram(Program* program);
-
-private:
- GLfloat mMulR, mMulG, mMulB;
- GLfloat mAddR, mAddG, mAddB;
-}; // struct SkiaLightingFilter
-
-/**
- * A color filters that blends the source color with a specified destination color
- * and PorterDuff blending mode.
- */
-struct SkiaBlendFilter: public SkiaColorFilter {
- ANDROID_API SkiaBlendFilter(SkColorFilter *skFilter, int color, SkXfermode::Mode mode);
-
- void describe(ProgramDescription& description, const Extensions& extensions);
- void setupProgram(Program* program);
-
-private:
- SkXfermode::Mode mMode;
- GLfloat mR, mG, mB, mA;
-}; // struct SkiaBlendFilter
-
-}; // namespace uirenderer
-}; // namespace android
-
-#endif // ANDROID_HWUI_SKIA_COLOR_FILTER_H
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 797ed10..c672bc4 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -21,9 +21,10 @@
#include <SkMatrix.h>
#include "Caches.h"
+#include "Layer.h"
+#include "Matrix.h"
#include "SkiaShader.h"
#include "Texture.h"
-#include "Matrix.h"
namespace android {
namespace uirenderer {
@@ -54,117 +55,245 @@ static inline void bindUniformColor(int slot, uint32_t color) {
a);
}
-///////////////////////////////////////////////////////////////////////////////
-// Base shader
-///////////////////////////////////////////////////////////////////////////////
-
-void SkiaShader::copyFrom(const SkiaShader& shader) {
- mType = shader.mType;
- mKey = shader.mKey;
- mTileX = shader.mTileX;
- mTileY = shader.mTileY;
- mBlend = shader.mBlend;
- mUnitMatrix = shader.mUnitMatrix;
- mShaderMatrix = shader.mShaderMatrix;
- mGenerationId = shader.mGenerationId;
+static inline void bindTexture(Caches* caches, Texture* texture, GLenum wrapS, GLenum wrapT) {
+ caches->bindTexture(texture->id);
+ texture->setWrapST(wrapS, wrapT);
}
-SkiaShader::SkiaShader(): mCaches(NULL) {
-}
+/**
+ * Compute the matrix to transform to screen space.
+ * @param screenSpace Output param for the computed matrix.
+ * @param unitMatrix The unit matrix for gradient shaders, as returned by SkShader::asAGradient,
+ * or identity.
+ * @param localMatrix Local matrix, as returned by SkShader::getLocalMatrix().
+ * @param modelViewMatrix Model view matrix, as supplied by the OpenGLRenderer.
+ */
+static void computeScreenSpaceMatrix(mat4& screenSpace, const SkMatrix& unitMatrix,
+ const SkMatrix& localMatrix, const mat4& modelViewMatrix) {
+ mat4 shaderMatrix;
+ // uses implicit construction
+ shaderMatrix.loadInverse(localMatrix);
+ // again, uses implicit construction
+ screenSpace.loadMultiply(unitMatrix, shaderMatrix);
+ screenSpace.multiply(modelViewMatrix);
+}
+
+// Returns true if one is a bitmap and the other is a gradient
+static bool bitmapAndGradient(SkiaShaderType type1, SkiaShaderType type2) {
+ return (type1 == kBitmap_SkiaShaderType && type2 == kGradient_SkiaShaderType)
+ || (type2 == kBitmap_SkiaShaderType && type1 == kGradient_SkiaShaderType);
+}
+
+SkiaShaderType SkiaShader::getType(const SkShader& shader) {
+ // First check for a gradient shader.
+ switch (shader.asAGradient(NULL)) {
+ case SkShader::kNone_GradientType:
+ // Not a gradient shader. Fall through to check for other types.
+ break;
+ case SkShader::kLinear_GradientType:
+ case SkShader::kRadial_GradientType:
+ case SkShader::kSweep_GradientType:
+ return kGradient_SkiaShaderType;
+ default:
+ // This is a Skia gradient that has no SkiaShader equivalent. Return None to skip.
+ return kNone_SkiaShaderType;
+ }
-SkiaShader::SkiaShader(Type type, SkShader* key, SkShader::TileMode tileX,
- SkShader::TileMode tileY, SkMatrix* matrix, bool blend):
- mType(type), mKey(key), mTileX(tileX), mTileY(tileY), mBlend(blend),
- mCaches(NULL) {
- setMatrix(matrix);
- mGenerationId = 0;
-}
+ // The shader is not a gradient. Check for a bitmap shader.
+ if (shader.asABitmap(NULL, NULL, NULL) == SkShader::kDefault_BitmapType) {
+ return kBitmap_SkiaShaderType;
+ }
-SkiaShader::~SkiaShader() {
-}
+ // Check for a ComposeShader.
+ SkShader::ComposeRec rec;
+ if (shader.asACompose(&rec)) {
+ const SkiaShaderType shaderAType = getType(*rec.fShaderA);
+ const SkiaShaderType shaderBType = getType(*rec.fShaderB);
+
+ // Compose is only supported if one is a bitmap and the other is a
+ // gradient. Otherwise, return None to skip.
+ if (!bitmapAndGradient(shaderAType, shaderBType)) {
+ return kNone_SkiaShaderType;
+ }
+ return kCompose_SkiaShaderType;
+ }
-void SkiaShader::describe(ProgramDescription& description, const Extensions& extensions) {
-}
+ if (shader.asACustomShader(NULL)) {
+ return kLayer_SkiaShaderType;
+ }
-void SkiaShader::setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
- GLuint* textureUnit) {
+ return kNone_SkiaShaderType;
}
-void SkiaShader::bindTexture(Texture* texture, GLenum wrapS, GLenum wrapT) {
- mCaches->bindTexture(texture->id);
- texture->setWrapST(wrapS, wrapT);
+typedef void (*describeProc)(Caches* caches, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader);
+
+describeProc gDescribeProc[] = {
+ InvalidSkiaShader::describe,
+ SkiaBitmapShader::describe,
+ SkiaGradientShader::describe,
+ SkiaComposeShader::describe,
+ SkiaLayerShader::describe,
+};
+
+typedef void (*setupProgramProc)(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions& extensions, const SkShader& shader);
+
+setupProgramProc gSetupProgramProc[] = {
+ InvalidSkiaShader::setupProgram,
+ SkiaBitmapShader::setupProgram,
+ SkiaGradientShader::setupProgram,
+ SkiaComposeShader::setupProgram,
+ SkiaLayerShader::setupProgram,
+};
+
+void SkiaShader::describe(Caches* caches, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader) {
+ gDescribeProc[getType(shader)](caches, description, extensions, shader);
}
-void SkiaShader::computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView) {
- screenSpace.loadMultiply(mUnitMatrix, mShaderMatrix);
- screenSpace.multiply(modelView);
+void SkiaShader::setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions& extensions, const SkShader& shader) {
+
+ gSetupProgramProc[getType(shader)](caches, modelViewMatrix, textureUnit, extensions, shader);
}
///////////////////////////////////////////////////////////////////////////////
-// Bitmap shader
+// Layer shader
///////////////////////////////////////////////////////////////////////////////
-SkiaBitmapShader::SkiaBitmapShader(SkBitmap* bitmap, SkShader* key, SkShader::TileMode tileX,
- SkShader::TileMode tileY, SkMatrix* matrix, bool blend):
- SkiaShader(kBitmap, key, tileX, tileY, matrix, blend), mBitmap(bitmap), mTexture(NULL) {
- updateLocalMatrix(matrix);
+void SkiaLayerShader::describe(Caches*, ProgramDescription& description,
+ const Extensions&, const SkShader& shader) {
+ description.hasBitmap = true;
}
-SkiaShader* SkiaBitmapShader::copy() {
- SkiaBitmapShader* copy = new SkiaBitmapShader();
- copy->copyFrom(*this);
- copy->mBitmap = mBitmap;
- return copy;
+void SkiaLayerShader::setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions&, const SkShader& shader) {
+ Layer* layer;
+ if (!shader.asACustomShader(reinterpret_cast<void**>(&layer))) {
+ LOG_ALWAYS_FATAL("SkiaLayerShader::setupProgram called on the wrong type of shader!");
+ }
+
+ GLuint textureSlot = (*textureUnit)++;
+ caches->activeTexture(textureSlot);
+
+ const float width = layer->getWidth();
+ const float height = layer->getHeight();
+
+ mat4 textureTransform;
+ computeScreenSpaceMatrix(textureTransform, SkMatrix::I(), shader.getLocalMatrix(),
+ modelViewMatrix);
+
+
+ // Uniforms
+ layer->bindTexture();
+ layer->setWrap(GL_CLAMP_TO_EDGE);
+ layer->setFilter(GL_LINEAR);
+
+ Program* program = caches->currentProgram;
+ glUniform1i(program->getUniform("bitmapSampler"), textureSlot);
+ glUniformMatrix4fv(program->getUniform("textureTransform"), 1,
+ GL_FALSE, &textureTransform.data[0]);
+ glUniform2f(program->getUniform("textureDimension"), 1.0f / width, 1.0f / height);
}
-void SkiaBitmapShader::describe(ProgramDescription& description, const Extensions& extensions) {
- Texture* texture = mCaches->textureCache.get(mBitmap);
- if (!texture) return;
- mTexture = texture;
+///////////////////////////////////////////////////////////////////////////////
+// Bitmap shader
+///////////////////////////////////////////////////////////////////////////////
+
+struct BitmapShaderInfo {
+ float width;
+ float height;
+ GLenum wrapS;
+ GLenum wrapT;
+ Texture* texture;
+};
+
+static bool bitmapShaderHelper(Caches* caches, ProgramDescription* description,
+ BitmapShaderInfo* shaderInfo,
+ const Extensions& extensions,
+ const SkBitmap& bitmap, SkShader::TileMode tileModes[2]) {
+ Texture* texture = caches->textureCache.get(&bitmap);
+ if (!texture) return false;
const float width = texture->width;
const float height = texture->height;
+ GLenum wrapS, wrapT;
- description.hasBitmap = true;
+ if (description) {
+ description->hasBitmap = true;
+ }
// The driver does not support non-power of two mirrored/repeated
// textures, so do it ourselves
if (!extensions.hasNPot() && (!isPowerOfTwo(width) || !isPowerOfTwo(height)) &&
- (mTileX != SkShader::kClamp_TileMode || mTileY != SkShader::kClamp_TileMode)) {
- description.isBitmapNpot = true;
- description.bitmapWrapS = gTileModes[mTileX];
- description.bitmapWrapT = gTileModes[mTileY];
- mWrapS = GL_CLAMP_TO_EDGE;
- mWrapT = GL_CLAMP_TO_EDGE;
+ (tileModes[0] != SkShader::kClamp_TileMode ||
+ tileModes[1] != SkShader::kClamp_TileMode)) {
+ if (description) {
+ description->isBitmapNpot = true;
+ description->bitmapWrapS = gTileModes[tileModes[0]];
+ description->bitmapWrapT = gTileModes[tileModes[1]];
+ }
+ wrapS = GL_CLAMP_TO_EDGE;
+ wrapT = GL_CLAMP_TO_EDGE;
} else {
- mWrapS = gTileModes[mTileX];
- mWrapT = gTileModes[mTileY];
+ wrapS = gTileModes[tileModes[0]];
+ wrapT = gTileModes[tileModes[1]];
}
+
+ if (shaderInfo) {
+ shaderInfo->width = width;
+ shaderInfo->height = height;
+ shaderInfo->wrapS = wrapS;
+ shaderInfo->wrapT = wrapT;
+ shaderInfo->texture = texture;
+ }
+ return true;
+}
+
+void SkiaBitmapShader::describe(Caches* caches, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader) {
+ SkBitmap bitmap;
+ SkShader::TileMode xy[2];
+ if (shader.asABitmap(&bitmap, NULL, xy) != SkShader::kDefault_BitmapType) {
+ LOG_ALWAYS_FATAL("SkiaBitmapShader::describe called with a different kind of shader!");
+ }
+ bitmapShaderHelper(caches, &description, NULL, extensions, bitmap, xy);
}
-void SkiaBitmapShader::setupProgram(Program* program, const mat4& modelView,
- const Snapshot& snapshot, GLuint* textureUnit) {
+void SkiaBitmapShader::setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions& extensions, const SkShader& shader) {
+ SkBitmap bitmap;
+ SkShader::TileMode xy[2];
+ if (shader.asABitmap(&bitmap, NULL, xy) != SkShader::kDefault_BitmapType) {
+ LOG_ALWAYS_FATAL("SkiaBitmapShader::setupProgram called with a different kind of shader!");
+ }
+
GLuint textureSlot = (*textureUnit)++;
Caches::getInstance().activeTexture(textureSlot);
- Texture* texture = mTexture;
- mTexture = NULL;
- if (!texture) return;
- const AutoTexture autoCleanup(texture);
+ BitmapShaderInfo shaderInfo;
+ if (!bitmapShaderHelper(caches, NULL, &shaderInfo, extensions, bitmap, xy)) {
+ return;
+ }
- const float width = texture->width;
- const float height = texture->height;
+ Program* program = caches->currentProgram;
+ Texture* texture = shaderInfo.texture;
+
+ const AutoTexture autoCleanup(texture);
mat4 textureTransform;
- computeScreenSpaceMatrix(textureTransform, modelView);
+ computeScreenSpaceMatrix(textureTransform, SkMatrix::I(), shader.getLocalMatrix(),
+ modelViewMatrix);
// Uniforms
- bindTexture(texture, mWrapS, mWrapT);
+ bindTexture(caches, texture, shaderInfo.wrapS, shaderInfo.wrapT);
texture->setFilter(GL_LINEAR);
glUniform1i(program->getUniform("bitmapSampler"), textureSlot);
glUniformMatrix4fv(program->getUniform("textureTransform"), 1,
GL_FALSE, &textureTransform.data[0]);
- glUniform2f(program->getUniform("textureDimension"), 1.0f / width, 1.0f / height);
+ glUniform2f(program->getUniform("textureDimension"), 1.0f / shaderInfo.width,
+ 1.0f / shaderInfo.height);
}
///////////////////////////////////////////////////////////////////////////////
@@ -182,74 +311,6 @@ static void toUnitMatrix(const SkPoint pts[2], SkMatrix* matrix) {
matrix->postScale(inv, inv);
}
-SkiaLinearGradientShader::SkiaLinearGradientShader(float* bounds, uint32_t* colors,
- float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
- SkMatrix* matrix, bool blend):
- SkiaShader(kLinearGradient, key, tileMode, tileMode, matrix, blend),
- mBounds(bounds), mColors(colors), mPositions(positions), mCount(count) {
- SkPoint points[2];
- points[0].set(bounds[0], bounds[1]);
- points[1].set(bounds[2], bounds[3]);
-
- SkMatrix unitMatrix;
- toUnitMatrix(points, &unitMatrix);
- mUnitMatrix.load(unitMatrix);
-
- updateLocalMatrix(matrix);
-
- mIsSimple = count == 2 && tileMode == SkShader::kClamp_TileMode;
-}
-
-SkiaLinearGradientShader::~SkiaLinearGradientShader() {
- delete[] mBounds;
- delete[] mColors;
- delete[] mPositions;
-}
-
-SkiaShader* SkiaLinearGradientShader::copy() {
- SkiaLinearGradientShader* copy = new SkiaLinearGradientShader();
- copy->copyFrom(*this);
- copy->mBounds = new float[4];
- memcpy(copy->mBounds, mBounds, sizeof(float) * 4);
- copy->mColors = new uint32_t[mCount];
- memcpy(copy->mColors, mColors, sizeof(uint32_t) * mCount);
- copy->mPositions = new float[mCount];
- memcpy(copy->mPositions, mPositions, sizeof(float) * mCount);
- copy->mCount = mCount;
- copy->mIsSimple = mIsSimple;
- return copy;
-}
-
-void SkiaLinearGradientShader::describe(ProgramDescription& description,
- const Extensions& extensions) {
- description.hasGradient = true;
- description.gradientType = ProgramDescription::kGradientLinear;
- description.isSimpleGradient = mIsSimple;
-}
-
-void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelView,
- const Snapshot& snapshot, GLuint* textureUnit) {
- if (CC_UNLIKELY(!mIsSimple)) {
- GLuint textureSlot = (*textureUnit)++;
- Caches::getInstance().activeTexture(textureSlot);
-
- Texture* texture = mCaches->gradientCache.get(mColors, mPositions, mCount);
-
- // Uniforms
- bindTexture(texture, gTileModes[mTileX], gTileModes[mTileY]);
- glUniform1i(program->getUniform("gradientSampler"), textureSlot);
- } else {
- bindUniformColor(program->getUniform("startColor"), mColors[0]);
- bindUniformColor(program->getUniform("endColor"), mColors[1]);
- }
-
- Caches::getInstance().dither.setupProgram(program, textureUnit);
-
- mat4 screenSpace;
- computeScreenSpaceMatrix(screenSpace, modelView);
- glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
-}
-
///////////////////////////////////////////////////////////////////////////////
// Circular gradient shader
///////////////////////////////////////////////////////////////////////////////
@@ -261,37 +322,6 @@ static void toCircularUnitMatrix(const float x, const float y, const float radiu
matrix->postScale(inv, inv);
}
-SkiaCircularGradientShader::SkiaCircularGradientShader(float x, float y, float radius,
- uint32_t* colors, float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
- SkMatrix* matrix, bool blend):
- SkiaSweepGradientShader(kCircularGradient, x, y, colors, positions, count, key,
- tileMode, matrix, blend) {
- SkMatrix unitMatrix;
- toCircularUnitMatrix(x, y, radius, &unitMatrix);
- mUnitMatrix.load(unitMatrix);
-
- updateLocalMatrix(matrix);
-}
-
-SkiaShader* SkiaCircularGradientShader::copy() {
- SkiaCircularGradientShader* copy = new SkiaCircularGradientShader();
- copy->copyFrom(*this);
- copy->mColors = new uint32_t[mCount];
- memcpy(copy->mColors, mColors, sizeof(uint32_t) * mCount);
- copy->mPositions = new float[mCount];
- memcpy(copy->mPositions, mPositions, sizeof(float) * mCount);
- copy->mCount = mCount;
- copy->mIsSimple = mIsSimple;
- return copy;
-}
-
-void SkiaCircularGradientShader::describe(ProgramDescription& description,
- const Extensions& extensions) {
- description.hasGradient = true;
- description.gradientType = ProgramDescription::kGradientCircular;
- description.isSimpleGradient = mIsSimple;
-}
-
///////////////////////////////////////////////////////////////////////////////
// Sweep gradient shader
///////////////////////////////////////////////////////////////////////////////
@@ -300,73 +330,103 @@ static void toSweepUnitMatrix(const float x, const float y, SkMatrix* matrix) {
matrix->setTranslate(-x, -y);
}
-SkiaSweepGradientShader::SkiaSweepGradientShader(float x, float y, uint32_t* colors,
- float* positions, int count, SkShader* key, SkMatrix* matrix, bool blend):
- SkiaShader(kSweepGradient, key, SkShader::kClamp_TileMode,
- SkShader::kClamp_TileMode, matrix, blend),
- mColors(colors), mPositions(positions), mCount(count) {
- SkMatrix unitMatrix;
- toSweepUnitMatrix(x, y, &unitMatrix);
- mUnitMatrix.load(unitMatrix);
-
- updateLocalMatrix(matrix);
-
- mIsSimple = count == 2;
-}
-
-SkiaSweepGradientShader::SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors,
- float* positions, int count, SkShader* key, SkShader::TileMode tileMode,
- SkMatrix* matrix, bool blend):
- SkiaShader(type, key, tileMode, tileMode, matrix, blend),
- mColors(colors), mPositions(positions), mCount(count) {
-
- mIsSimple = count == 2 && tileMode == SkShader::kClamp_TileMode;
-}
-
-SkiaSweepGradientShader::~SkiaSweepGradientShader() {
- delete[] mColors;
- delete[] mPositions;
-}
-
-SkiaShader* SkiaSweepGradientShader::copy() {
- SkiaSweepGradientShader* copy = new SkiaSweepGradientShader();
- copy->copyFrom(*this);
- copy->mColors = new uint32_t[mCount];
- memcpy(copy->mColors, mColors, sizeof(uint32_t) * mCount);
- copy->mPositions = new float[mCount];
- memcpy(copy->mPositions, mPositions, sizeof(float) * mCount);
- copy->mCount = mCount;
- copy->mIsSimple = mIsSimple;
- return copy;
-}
+///////////////////////////////////////////////////////////////////////////////
+// Common gradient code
+///////////////////////////////////////////////////////////////////////////////
-void SkiaSweepGradientShader::describe(ProgramDescription& description,
- const Extensions& extensions) {
+static bool isSimpleGradient(const SkShader::GradientInfo& gradInfo) {
+ return gradInfo.fColorCount == 2 && gradInfo.fTileMode == SkShader::kClamp_TileMode;
+}
+
+void SkiaGradientShader::describe(Caches*, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader) {
+ SkShader::GradientInfo gradInfo;
+ gradInfo.fColorCount = 0;
+ gradInfo.fColors = NULL;
+ gradInfo.fColorOffsets = NULL;
+
+ switch (shader.asAGradient(&gradInfo)) {
+ case SkShader::kLinear_GradientType:
+ description.gradientType = ProgramDescription::kGradientLinear;
+ break;
+ case SkShader::kRadial_GradientType:
+ description.gradientType = ProgramDescription::kGradientCircular;
+ break;
+ case SkShader::kSweep_GradientType:
+ description.gradientType = ProgramDescription::kGradientSweep;
+ break;
+ default:
+ // Do nothing. This shader is unsupported.
+ return;
+ }
description.hasGradient = true;
- description.gradientType = ProgramDescription::kGradientSweep;
- description.isSimpleGradient = mIsSimple;
-}
-
-void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelView,
- const Snapshot& snapshot, GLuint* textureUnit) {
- if (CC_UNLIKELY(!mIsSimple)) {
+ description.isSimpleGradient = isSimpleGradient(gradInfo);
+}
+
+void SkiaGradientShader::setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions&, const SkShader& shader) {
+ // SkShader::GradientInfo.fColorCount is an in/out parameter. As input, it tells asAGradient
+ // how much space has been allocated for fColors and fColorOffsets. 10 was chosen
+ // arbitrarily, but should be >= 2.
+ // As output, it tells the number of actual colors/offsets in the gradient.
+ const int COLOR_COUNT = 10;
+ SkAutoSTMalloc<COLOR_COUNT, SkColor> colorStorage(COLOR_COUNT);
+ SkAutoSTMalloc<COLOR_COUNT, SkScalar> positionStorage(COLOR_COUNT);
+
+ SkShader::GradientInfo gradInfo;
+ gradInfo.fColorCount = COLOR_COUNT;
+ gradInfo.fColors = colorStorage.get();
+ gradInfo.fColorOffsets = positionStorage.get();
+
+ SkShader::GradientType gradType = shader.asAGradient(&gradInfo);
+
+ Program* program = caches->currentProgram;
+ if (CC_UNLIKELY(!isSimpleGradient(gradInfo))) {
+ if (gradInfo.fColorCount > COLOR_COUNT) {
+ // There was not enough room in our arrays for all the colors and offsets. Try again,
+ // now that we know the true number of colors.
+ gradInfo.fColors = colorStorage.reset(gradInfo.fColorCount);
+ gradInfo.fColorOffsets = positionStorage.reset(gradInfo.fColorCount);
+
+ shader.asAGradient(&gradInfo);
+ }
GLuint textureSlot = (*textureUnit)++;
- Caches::getInstance().activeTexture(textureSlot);
+ caches->activeTexture(textureSlot);
- Texture* texture = mCaches->gradientCache.get(mColors, mPositions, mCount);
+#ifndef SK_SCALAR_IS_FLOAT
+ #error Need to convert gradInfo.fColorOffsets to float!
+#endif
+ Texture* texture = caches->gradientCache.get(gradInfo.fColors, gradInfo.fColorOffsets,
+ gradInfo.fColorCount);
// Uniforms
- bindTexture(texture, gTileModes[mTileX], gTileModes[mTileY]);
+ bindTexture(caches, texture, gTileModes[gradInfo.fTileMode], gTileModes[gradInfo.fTileMode]);
glUniform1i(program->getUniform("gradientSampler"), textureSlot);
} else {
- bindUniformColor(program->getUniform("startColor"), mColors[0]);
- bindUniformColor(program->getUniform("endColor"), mColors[1]);
+ bindUniformColor(program->getUniform("startColor"), gradInfo.fColors[0]);
+ bindUniformColor(program->getUniform("endColor"), gradInfo.fColors[1]);
}
- mCaches->dither.setupProgram(program, textureUnit);
+ caches->dither.setupProgram(program, textureUnit);
+
+ SkMatrix unitMatrix;
+ switch (gradType) {
+ case SkShader::kLinear_GradientType:
+ toUnitMatrix(gradInfo.fPoint, &unitMatrix);
+ break;
+ case SkShader::kRadial_GradientType:
+ toCircularUnitMatrix(gradInfo.fPoint[0].fX, gradInfo.fPoint[0].fY,
+ gradInfo.fRadius[0], &unitMatrix);
+ break;
+ case SkShader::kSweep_GradientType:
+ toSweepUnitMatrix(gradInfo.fPoint[0].fX, gradInfo.fPoint[0].fY, &unitMatrix);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Invalid SkShader gradient type %d", gradType);
+ }
mat4 screenSpace;
- computeScreenSpaceMatrix(screenSpace, modelView);
+ computeScreenSpaceMatrix(screenSpace, unitMatrix, shader.getLocalMatrix(), modelViewMatrix);
glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
}
@@ -374,49 +434,39 @@ void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelVi
// Compose shader
///////////////////////////////////////////////////////////////////////////////
-SkiaComposeShader::SkiaComposeShader(SkiaShader* first, SkiaShader* second,
- SkXfermode::Mode mode, SkShader* key):
- SkiaShader(kCompose, key, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode,
- NULL, first->blend() || second->blend()),
- mFirst(first), mSecond(second), mMode(mode), mCleanup(false) {
-}
-
-SkiaComposeShader::~SkiaComposeShader() {
- if (mCleanup) {
- delete mFirst;
- delete mSecond;
+void SkiaComposeShader::describe(Caches* caches, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader) {
+ SkShader::ComposeRec rec;
+ if (!shader.asACompose(&rec)) {
+ LOG_ALWAYS_FATAL("SkiaComposeShader::describe called on the wrong shader type!");
}
-}
-
-SkiaShader* SkiaComposeShader::copy() {
- SkiaComposeShader* copy = new SkiaComposeShader();
- copy->copyFrom(*this);
- copy->mFirst = mFirst->copy();
- copy->mSecond = mSecond->copy();
- copy->mMode = mMode;
- copy->cleanup();
- return copy;
-}
-
-void SkiaComposeShader::describe(ProgramDescription& description, const Extensions& extensions) {
- mFirst->describe(description, extensions);
- mSecond->describe(description, extensions);
- if (mFirst->type() == kBitmap) {
+ SkiaShader::describe(caches, description, extensions, *rec.fShaderA);
+ SkiaShader::describe(caches, description, extensions, *rec.fShaderB);
+ if (SkiaShader::getType(*rec.fShaderA) == kBitmap_SkiaShaderType) {
description.isBitmapFirst = true;
}
- description.shadersMode = mMode;
+ if (!SkXfermode::AsMode(rec.fMode, &description.shadersMode)) {
+ // TODO: Support other modes.
+ description.shadersMode = SkXfermode::kSrcOver_Mode;
+ }
}
-void SkiaComposeShader::setupProgram(Program* program, const mat4& modelView,
- const Snapshot& snapshot, GLuint* textureUnit) {
+void SkiaComposeShader::setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions& extensions, const SkShader& shader) {
+ SkShader::ComposeRec rec;
+ if (!shader.asACompose(&rec)) {
+ LOG_ALWAYS_FATAL("SkiaComposeShader::setupProgram called on the wrong shader type!");
+ }
+
// Apply this compose shader's local transform and pass it down to
// the child shaders. They will in turn apply their local transform
// to this matrix.
mat4 transform;
- computeScreenSpaceMatrix(transform, modelView);
+ computeScreenSpaceMatrix(transform, SkMatrix::I(), shader.getLocalMatrix(),
+ modelViewMatrix);
- mFirst->setupProgram(program, transform, snapshot, textureUnit);
- mSecond->setupProgram(program, transform, snapshot, textureUnit);
+ SkiaShader::setupProgram(caches, transform, textureUnit, extensions, *rec.fShaderA);
+ SkiaShader::setupProgram(caches, transform, textureUnit, extensions, *rec.fShaderB);
}
}; // namespace uirenderer
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index cc56c50..034c3f6 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -28,230 +28,90 @@
#include "ProgramCache.h"
#include "TextureCache.h"
#include "GradientCache.h"
-#include "Snapshot.h"
namespace android {
namespace uirenderer {
class Caches;
-
-///////////////////////////////////////////////////////////////////////////////
-// Base shader
-///////////////////////////////////////////////////////////////////////////////
+class Layer;
/**
- * Represents a Skia shader. A shader will modify the GL context and active
- * program to recreate the original effect.
+ * Type of Skia shader in use.
*/
-struct SkiaShader {
- /**
- * Type of Skia shader in use.
- */
- enum Type {
- kNone,
- kBitmap,
- kLinearGradient,
- kCircularGradient,
- kSweepGradient,
- kCompose
- };
-
- ANDROID_API SkiaShader(Type type, SkShader* key, SkShader::TileMode tileX,
- SkShader::TileMode tileY, SkMatrix* matrix, bool blend);
- virtual ~SkiaShader();
-
- virtual SkiaShader* copy() = 0;
- void copyFrom(const SkiaShader& shader);
-
- virtual void describe(ProgramDescription& description, const Extensions& extensions);
- virtual void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
- GLuint* textureUnit);
-
- inline SkShader* getSkShader() {
- return mKey;
- }
-
- inline bool blend() const {
- return mBlend;
- }
-
- Type type() const {
- return mType;
- }
-
- virtual void setCaches(Caches& caches) {
- mCaches = &caches;
- }
-
- uint32_t getGenerationId() {
- return mGenerationId;
+enum SkiaShaderType {
+ kNone_SkiaShaderType,
+ kBitmap_SkiaShaderType,
+ kGradient_SkiaShaderType,
+ kCompose_SkiaShaderType,
+ kLayer_SkiaShaderType
+};
+
+class SkiaShader {
+public:
+ static SkiaShaderType getType(const SkShader& shader);
+ static void describe(Caches* caches, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader);
+ static void setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions& extensions, const SkShader& shader);
+};
+
+class InvalidSkiaShader {
+public:
+ static void describe(Caches* caches, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader) {
+ // This shader is unsupported. Skip it.
}
-
- void setMatrix(SkMatrix* matrix) {
- updateLocalMatrix(matrix);
- mGenerationId++;
- }
-
- void updateLocalMatrix(const SkMatrix* matrix) {
- if (matrix) {
- mat4 localMatrix(*matrix);
- mShaderMatrix.loadInverse(localMatrix);
- } else {
- mShaderMatrix.loadIdentity();
- }
- }
-
- void computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView);
-
-protected:
- SkiaShader();
-
- /**
- * The appropriate texture unit must have been activated prior to invoking
- * this method.
- */
- inline void bindTexture(Texture* texture, GLenum wrapS, GLenum wrapT);
-
- Type mType;
- SkShader* mKey;
- SkShader::TileMode mTileX;
- SkShader::TileMode mTileY;
- bool mBlend;
-
- Caches* mCaches;
-
- mat4 mUnitMatrix;
- mat4 mShaderMatrix;
-
-private:
- uint32_t mGenerationId;
-}; // struct SkiaShader
-
-
-///////////////////////////////////////////////////////////////////////////////
-// Implementations
-///////////////////////////////////////////////////////////////////////////////
-
-/**
- * A shader that draws a bitmap.
- */
-struct SkiaBitmapShader: public SkiaShader {
- ANDROID_API SkiaBitmapShader(SkBitmap* bitmap, SkShader* key, SkShader::TileMode tileX,
- SkShader::TileMode tileY, SkMatrix* matrix, bool blend);
- SkiaShader* copy();
-
- void describe(ProgramDescription& description, const Extensions& extensions);
- void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
- GLuint* textureUnit);
-
-private:
- SkiaBitmapShader() : mBitmap(NULL), mTexture(NULL) {
+ static void setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions& extensions, const SkShader& shader) {
+ // This shader is unsupported. Skip it.
}
- SkBitmap* mBitmap;
- Texture* mTexture;
- GLenum mWrapS;
- GLenum mWrapT;
-}; // struct SkiaBitmapShader
-
+};
/**
- * A shader that draws a linear gradient.
+ * A shader that draws a layer.
*/
-struct SkiaLinearGradientShader: public SkiaShader {
- ANDROID_API SkiaLinearGradientShader(float* bounds, uint32_t* colors, float* positions,
- int count, SkShader* key, SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
- ~SkiaLinearGradientShader();
- SkiaShader* copy();
-
- void describe(ProgramDescription& description, const Extensions& extensions);
- void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
- GLuint* textureUnit);
-
-private:
- SkiaLinearGradientShader() {
- }
-
- bool mIsSimple;
- float* mBounds;
- uint32_t* mColors;
- float* mPositions;
- int mCount;
-}; // struct SkiaLinearGradientShader
+class SkiaLayerShader {
+public:
+ static void describe(Caches* caches, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader);
+ static void setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions& extensions, const SkShader& shader);
+}; // class SkiaLayerShader
/**
- * A shader that draws a sweep gradient.
+ * A shader that draws a bitmap.
*/
-struct SkiaSweepGradientShader: public SkiaShader {
- ANDROID_API SkiaSweepGradientShader(float x, float y, uint32_t* colors, float* positions,
- int count, SkShader* key, SkMatrix* matrix, bool blend);
- ~SkiaSweepGradientShader();
- SkiaShader* copy();
-
- virtual void describe(ProgramDescription& description, const Extensions& extensions);
- void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
- GLuint* textureUnit);
+class SkiaBitmapShader {
+public:
+ static void describe(Caches* caches, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader);
+ static void setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions& extensions, const SkShader& shader);
-protected:
- SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors, float* positions,
- int count, SkShader* key, SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
- SkiaSweepGradientShader() {
- }
- bool mIsSimple;
- uint32_t* mColors;
- float* mPositions;
- int mCount;
-}; // struct SkiaSweepGradientShader
+}; // class SkiaBitmapShader
/**
- * A shader that draws a circular gradient.
+ * A shader that draws one of three types of gradient, depending on shader param.
*/
-struct SkiaCircularGradientShader: public SkiaSweepGradientShader {
- ANDROID_API SkiaCircularGradientShader(float x, float y, float radius, uint32_t* colors,
- float* positions, int count, SkShader* key,SkShader::TileMode tileMode,
- SkMatrix* matrix, bool blend);
- SkiaShader* copy();
-
- void describe(ProgramDescription& description, const Extensions& extensions);
-
-private:
- SkiaCircularGradientShader() {
- }
-}; // struct SkiaCircularGradientShader
+class SkiaGradientShader {
+public:
+ static void describe(Caches* caches, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader);
+ static void setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions& extensions, const SkShader& shader);
+};
/**
* A shader that draws two shaders, composited with an xfermode.
*/
-struct SkiaComposeShader: public SkiaShader {
- ANDROID_API SkiaComposeShader(SkiaShader* first, SkiaShader* second, SkXfermode::Mode mode,
- SkShader* key);
- ~SkiaComposeShader();
- SkiaShader* copy();
-
- void setCaches(Caches& caches) {
- SkiaShader::setCaches(caches);
- mFirst->setCaches(caches);
- mSecond->setCaches(caches);
- }
-
- void describe(ProgramDescription& description, const Extensions& extensions);
- void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
- GLuint* textureUnit);
-
-private:
- SkiaComposeShader(): mCleanup(false) {
- }
-
- void cleanup() {
- mCleanup = true;
- }
-
- SkiaShader* mFirst;
- SkiaShader* mSecond;
- SkXfermode::Mode mMode;
-
- bool mCleanup;
-}; // struct SkiaComposeShader
+class SkiaComposeShader {
+public:
+ static void describe(Caches* caches, ProgramDescription& description,
+ const Extensions& extensions, const SkShader& shader);
+ static void setupProgram(Caches* caches, const mat4& modelViewMatrix,
+ GLuint* textureUnit, const Extensions& extensions, const SkShader& shader);
+}; // class SkiaComposeShader
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index d26ee38..ecc47d2 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -27,9 +27,15 @@ namespace uirenderer {
// Constructors
///////////////////////////////////////////////////////////////////////////////
-Snapshot::Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0),
- invisible(false), empty(false), alpha(1.0f) {
-
+Snapshot::Snapshot()
+ : flags(0)
+ , previous(NULL)
+ , layer(NULL)
+ , fbo(0)
+ , invisible(false)
+ , empty(false)
+ , alpha(1.0f)
+ , roundRectClipState(NULL) {
transform = &mTransformRoot;
clipRect = &mClipRectRoot;
region = NULL;
@@ -40,11 +46,17 @@ Snapshot::Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0),
* Copies the specified snapshot/ The specified snapshot is stored as
* the previous snapshot.
*/
-Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags):
- flags(0), previous(s), layer(s->layer), fbo(s->fbo),
- invisible(s->invisible), empty(false),
- viewport(s->viewport), height(s->height), alpha(s->alpha) {
-
+Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags)
+ : flags(0)
+ , previous(s)
+ , layer(s->layer)
+ , fbo(s->fbo)
+ , invisible(s->invisible)
+ , empty(false)
+ , alpha(s->alpha)
+ , roundRectClipState(s->roundRectClipState)
+ , mViewportData(s->mViewportData)
+ , mRelativeLightCenter(s->mRelativeLightCenter) {
if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
mTransformRoot.load(*s->transform);
transform = &mTransformRoot;
@@ -189,11 +201,60 @@ void Snapshot::resetClip(float left, float top, float right, float bottom) {
///////////////////////////////////////////////////////////////////////////////
void Snapshot::resetTransform(float x, float y, float z) {
+ // before resetting, map current light pos with inverse of current transform
+ Vector3 center = mRelativeLightCenter;
+ mat4 inverse;
+ inverse.loadInverse(*transform);
+ inverse.mapPoint3d(center);
+ mRelativeLightCenter = center;
+
transform = &mTransformRoot;
transform->loadTranslate(x, y, z);
}
///////////////////////////////////////////////////////////////////////////////
+// Clipping round rect
+///////////////////////////////////////////////////////////////////////////////
+
+void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& bounds, float radius) {
+ if (bounds.isEmpty()) {
+ clipRect->setEmpty();
+ return;
+ }
+
+ RoundRectClipState* state = new (allocator) RoundRectClipState;
+
+ // store the inverse drawing matrix
+ Matrix4 roundRectDrawingMatrix;
+ roundRectDrawingMatrix.load(getOrthoMatrix());
+ roundRectDrawingMatrix.multiply(*transform);
+ state->matrix.loadInverse(roundRectDrawingMatrix);
+
+ // compute area under rounded corners - only draws overlapping these rects need to be clipped
+ for (int i = 0 ; i < 4; i++) {
+ state->dangerRects[i] = bounds;
+ }
+ state->dangerRects[0].bottom = state->dangerRects[1].bottom = bounds.top + radius;
+ state->dangerRects[0].right = state->dangerRects[2].right = bounds.left + radius;
+ state->dangerRects[1].left = state->dangerRects[3].left = bounds.right - radius;
+ state->dangerRects[2].top = state->dangerRects[3].top = bounds.bottom - radius;
+ for (int i = 0; i < 4; i++) {
+ transform->mapRect(state->dangerRects[i]);
+
+ // round danger rects out as though they are AA geometry (since they essentially are)
+ state->dangerRects[i].snapGeometryToPixelBoundaries(true);
+ }
+
+ // store RR area
+ state->innerRect = bounds;
+ state->innerRect.inset(radius);
+ state->radius = radius;
+
+ // store as immutable so, for this frame, pointer uniquely identifies this bundle of shader info
+ roundRectClipState = state;
+}
+
+///////////////////////////////////////////////////////////////////////////////
// Queries
///////////////////////////////////////////////////////////////////////////////
@@ -203,7 +264,7 @@ bool Snapshot::isIgnored() const {
void Snapshot::dump() const {
ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d",
- this, flags, previous.get(), height, isIgnored(), clipRegion && !clipRegion->isEmpty());
+ this, flags, previous.get(), getViewportHeight(), isIgnored(), clipRegion && !clipRegion->isEmpty());
ALOGD(" ClipRect (at %p) %.1f %.1f %.1f %.1f",
clipRect, clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
ALOGD(" Transform (at %p):", transform);
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 5bdb18a..ad4ee9d 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -20,6 +20,7 @@
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
+#include <utils/LinearAllocator.h>
#include <utils/RefBase.h>
#include <ui/Region.h>
@@ -27,12 +28,40 @@
#include "Layer.h"
#include "Matrix.h"
+#include "Outline.h"
#include "Rect.h"
+#include "utils/Macros.h"
namespace android {
namespace uirenderer {
/**
+ * Temporary structure holding information for a single outline clip.
+ *
+ * These structures are treated as immutable once created, and only exist for a single frame, which
+ * is why they may only be allocated with a LinearAllocator.
+ */
+class RoundRectClipState {
+public:
+ /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/
+ static void* operator new(size_t size, LinearAllocator& allocator) {
+ return allocator.alloc(size);
+ }
+
+ bool areaRequiresRoundRectClip(const Rect& rect) const {
+ return rect.intersects(dangerRects[0])
+ || rect.intersects(dangerRects[1])
+ || rect.intersects(dangerRects[2])
+ || rect.intersects(dangerRects[3]);
+ }
+
+ Matrix4 matrix;
+ Rect dangerRects[4];
+ Rect innerRect;
+ float radius;
+};
+
+/**
* A snapshot holds information about the current state of the rendering
* surface. A snapshot is usually created whenever the user calls save()
* and discarded when the user calls restore(). Once a snapshot is created,
@@ -65,17 +94,16 @@ public:
* Indicates that this snapshot is a special type of layer
* backed by an FBO. This flag only makes sense when the
* flag kFlagIsLayer is also set.
+ *
+ * Viewport has been modified to fit the new Fbo, and must be
+ * restored when this snapshot is restored.
*/
kFlagIsFboLayer = 0x4,
/**
- * Indicates that this snapshot has changed the ortho matrix.
- */
- kFlagDirtyOrtho = 0x8,
- /**
* Indicates that this snapshot or an ancestor snapshot is
* an FBO layer.
*/
- kFlagFboTarget = 0x10
+ kFlagFboTarget = 0x8,
};
/**
@@ -111,6 +139,11 @@ public:
ANDROID_API const Rect& getLocalClip();
/**
+ * Returns the current clip in render target coordinates.
+ */
+ const Rect& getRenderTargetClip() { return *clipRect; }
+
+ /**
* Resets the clip to the specified rect.
*/
void resetClip(float left, float top, float right, float bottom);
@@ -120,6 +153,22 @@ public:
*/
void resetTransform(float x, float y, float z);
+ void initializeViewport(int width, int height) {
+ mViewportData.initialize(width, height);
+ }
+
+ int getViewportWidth() const { return mViewportData.mWidth; }
+ int getViewportHeight() const { return mViewportData.mHeight; }
+ const Matrix4& getOrthoMatrix() const { return mViewportData.mOrthoMatrix; }
+
+ const Vector3& getRelativeLightCenter() const { return mRelativeLightCenter; }
+ void setRelativeLightCenter(const Vector3& lightCenter) { mRelativeLightCenter = lightCenter; }
+
+ /**
+ * Sets (and replaces) the current clipping outline
+ */
+ void setClippingRoundRect(LinearAllocator& allocator, const Rect& bounds, float radius);
+
/**
* Indicates whether this snapshot should be ignored. A snapshot
* is typicalled ignored if its layer is invisible or empty.
@@ -168,21 +217,6 @@ public:
bool empty;
/**
- * Current viewport.
- */
- Rect viewport;
-
- /**
- * Height of the framebuffer the snapshot is rendering into.
- */
- int height;
-
- /**
- * Contains the previous ortho matrix.
- */
- mat4 orthoMatrix;
-
- /**
* Local transformation. Holds the current translation, scale and
* rotation values.
*
@@ -228,9 +262,38 @@ public:
*/
float alpha;
+ /**
+ * Current clipping round rect.
+ *
+ * Points to data not owned by the snapshot, and may only be replaced by subsequent RR clips,
+ * never modified.
+ */
+ const RoundRectClipState* roundRectClipState;
+
void dump() const;
private:
+ struct ViewportData {
+ ViewportData() : mWidth(0), mHeight(0) {}
+ void initialize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1);
+ }
+
+ /*
+ * Width and height of current viewport.
+ *
+ * The viewport is always defined to be (0, 0, width, height).
+ */
+ int mWidth;
+ int mHeight;
+ /**
+ * Contains the current orthographic, projection matrix.
+ */
+ mat4 mOrthoMatrix;
+ };
+
void ensureClipRegion();
void copyClipRectFromRegion();
@@ -238,9 +301,11 @@ private:
mat4 mTransformRoot;
Rect mClipRectRoot;
- Rect mLocalClip;
+ Rect mLocalClip; // don't use directly, call getLocalClip() which initializes this
SkRegion mClipRegionRoot;
+ ViewportData mViewportData;
+ Vector3 mRelativeLightCenter;
}; // class Snapshot
diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp
new file mode 100644
index 0000000..d726538
--- /dev/null
+++ b/libs/hwui/SpotShadow.cpp
@@ -0,0 +1,939 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#define SHADOW_SHRINK_SCALE 0.1f
+
+#include <math.h>
+#include <stdlib.h>
+#include <utils/Log.h>
+
+#include "ShadowTessellator.h"
+#include "SpotShadow.h"
+#include "Vertex.h"
+
+namespace android {
+namespace uirenderer {
+
+static const double EPSILON = 1e-7;
+
+/**
+ * Calculate the angle between and x and a y coordinate.
+ * The atan2 range from -PI to PI.
+ */
+static float angle(const Vector2& point, const Vector2& center) {
+ return atan2(point.y - center.y, point.x - center.x);
+}
+
+/**
+ * Calculate the intersection of a ray with the line segment defined by two points.
+ *
+ * Returns a negative value in error conditions.
+
+ * @param rayOrigin The start of the ray
+ * @param dx The x vector of the ray
+ * @param dy The y vector of the ray
+ * @param p1 The first point defining the line segment
+ * @param p2 The second point defining the line segment
+ * @return The distance along the ray if it intersects with the line segment, negative if otherwise
+ */
+static float rayIntersectPoints(const Vector2& rayOrigin, float dx, float dy,
+ const Vector2& p1, const Vector2& p2) {
+ // The math below is derived from solving this formula, basically the
+ // intersection point should stay on both the ray and the edge of (p1, p2).
+ // solve([p1x+t*(p2x-p1x)=dx*t2+px,p1y+t*(p2y-p1y)=dy*t2+py],[t,t2]);
+
+ double divisor = (dx * (p1.y - p2.y) + dy * p2.x - dy * p1.x);
+ if (divisor == 0) return -1.0f; // error, invalid divisor
+
+#if DEBUG_SHADOW
+ double interpVal = (dx * (p1.y - rayOrigin.y) + dy * rayOrigin.x - dy * p1.x) / divisor;
+ if (interpVal < 0 || interpVal > 1) {
+ ALOGW("rayIntersectPoints is hitting outside the segment %f", interpVal);
+ }
+#endif
+
+ double distance = (p1.x * (rayOrigin.y - p2.y) + p2.x * (p1.y - rayOrigin.y) +
+ rayOrigin.x * (p2.y - p1.y)) / divisor;
+
+ return distance; // may be negative in error cases
+}
+
+/**
+ * Sort points by their X coordinates
+ *
+ * @param points the points as a Vector2 array.
+ * @param pointsLength the number of vertices of the polygon.
+ */
+void SpotShadow::xsort(Vector2* points, int pointsLength) {
+ quicksortX(points, 0, pointsLength - 1);
+}
+
+/**
+ * compute the convex hull of a collection of Points
+ *
+ * @param points the points as a Vector2 array.
+ * @param pointsLength the number of vertices of the polygon.
+ * @param retPoly pre allocated array of floats to put the vertices
+ * @return the number of points in the polygon 0 if no intersection
+ */
+int SpotShadow::hull(Vector2* points, int pointsLength, Vector2* retPoly) {
+ xsort(points, pointsLength);
+ int n = pointsLength;
+ Vector2 lUpper[n];
+ lUpper[0] = points[0];
+ lUpper[1] = points[1];
+
+ int lUpperSize = 2;
+
+ for (int i = 2; i < n; i++) {
+ lUpper[lUpperSize] = points[i];
+ lUpperSize++;
+
+ while (lUpperSize > 2 && !ccw(
+ lUpper[lUpperSize - 3].x, lUpper[lUpperSize - 3].y,
+ lUpper[lUpperSize - 2].x, lUpper[lUpperSize - 2].y,
+ lUpper[lUpperSize - 1].x, lUpper[lUpperSize - 1].y)) {
+ // Remove the middle point of the three last
+ lUpper[lUpperSize - 2].x = lUpper[lUpperSize - 1].x;
+ lUpper[lUpperSize - 2].y = lUpper[lUpperSize - 1].y;
+ lUpperSize--;
+ }
+ }
+
+ Vector2 lLower[n];
+ lLower[0] = points[n - 1];
+ lLower[1] = points[n - 2];
+
+ int lLowerSize = 2;
+
+ for (int i = n - 3; i >= 0; i--) {
+ lLower[lLowerSize] = points[i];
+ lLowerSize++;
+
+ while (lLowerSize > 2 && !ccw(
+ lLower[lLowerSize - 3].x, lLower[lLowerSize - 3].y,
+ lLower[lLowerSize - 2].x, lLower[lLowerSize - 2].y,
+ lLower[lLowerSize - 1].x, lLower[lLowerSize - 1].y)) {
+ // Remove the middle point of the three last
+ lLower[lLowerSize - 2] = lLower[lLowerSize - 1];
+ lLowerSize--;
+ }
+ }
+
+ // output points in CW ordering
+ const int total = lUpperSize + lLowerSize - 2;
+ int outIndex = total - 1;
+ for (int i = 0; i < lUpperSize; i++) {
+ retPoly[outIndex] = lUpper[i];
+ outIndex--;
+ }
+
+ for (int i = 1; i < lLowerSize - 1; i++) {
+ retPoly[outIndex] = lLower[i];
+ outIndex--;
+ }
+ // TODO: Add test harness which verify that all the points are inside the hull.
+ return total;
+}
+
+/**
+ * Test whether the 3 points form a counter clockwise turn.
+ *
+ * @return true if a right hand turn
+ */
+bool SpotShadow::ccw(double ax, double ay, double bx, double by,
+ double cx, double cy) {
+ return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax) > EPSILON;
+}
+
+/**
+ * Calculates the intersection of poly1 with poly2 and put in poly2.
+ * Note that both poly1 and poly2 must be in CW order already!
+ *
+ * @param poly1 The 1st polygon, as a Vector2 array.
+ * @param poly1Length The number of vertices of 1st polygon.
+ * @param poly2 The 2nd and output polygon, as a Vector2 array.
+ * @param poly2Length The number of vertices of 2nd polygon.
+ * @return number of vertices in output polygon as poly2.
+ */
+int SpotShadow::intersection(const Vector2* poly1, int poly1Length,
+ Vector2* poly2, int poly2Length) {
+#if DEBUG_SHADOW
+ if (!ShadowTessellator::isClockwise(poly1, poly1Length)) {
+ ALOGW("Poly1 is not clockwise! Intersection is wrong!");
+ }
+ if (!ShadowTessellator::isClockwise(poly2, poly2Length)) {
+ ALOGW("Poly2 is not clockwise! Intersection is wrong!");
+ }
+#endif
+ Vector2 poly[poly1Length * poly2Length + 2];
+ int count = 0;
+ int pcount = 0;
+
+ // If one vertex from one polygon sits inside another polygon, add it and
+ // count them.
+ for (int i = 0; i < poly1Length; i++) {
+ if (testPointInsidePolygon(poly1[i], poly2, poly2Length)) {
+ poly[count] = poly1[i];
+ count++;
+ pcount++;
+
+ }
+ }
+
+ int insidePoly2 = pcount;
+ for (int i = 0; i < poly2Length; i++) {
+ if (testPointInsidePolygon(poly2[i], poly1, poly1Length)) {
+ poly[count] = poly2[i];
+ count++;
+ }
+ }
+
+ int insidePoly1 = count - insidePoly2;
+ // If all vertices from poly1 are inside poly2, then just return poly1.
+ if (insidePoly2 == poly1Length) {
+ memcpy(poly2, poly1, poly1Length * sizeof(Vector2));
+ return poly1Length;
+ }
+
+ // If all vertices from poly2 are inside poly1, then just return poly2.
+ if (insidePoly1 == poly2Length) {
+ return poly2Length;
+ }
+
+ // Since neither polygon fully contain the other one, we need to add all the
+ // intersection points.
+ Vector2 intersection = {0, 0};
+ for (int i = 0; i < poly2Length; i++) {
+ for (int j = 0; j < poly1Length; j++) {
+ int poly2LineStart = i;
+ int poly2LineEnd = ((i + 1) % poly2Length);
+ int poly1LineStart = j;
+ int poly1LineEnd = ((j + 1) % poly1Length);
+ bool found = lineIntersection(
+ poly2[poly2LineStart].x, poly2[poly2LineStart].y,
+ poly2[poly2LineEnd].x, poly2[poly2LineEnd].y,
+ poly1[poly1LineStart].x, poly1[poly1LineStart].y,
+ poly1[poly1LineEnd].x, poly1[poly1LineEnd].y,
+ intersection);
+ if (found) {
+ poly[count].x = intersection.x;
+ poly[count].y = intersection.y;
+ count++;
+ } else {
+ Vector2 delta = poly2[i] - poly1[j];
+ if (delta.lengthSquared() < EPSILON) {
+ poly[count] = poly2[i];
+ count++;
+ }
+ }
+ }
+ }
+
+ if (count == 0) {
+ return 0;
+ }
+
+ // Sort the result polygon around the center.
+ Vector2 center = {0.0f, 0.0f};
+ for (int i = 0; i < count; i++) {
+ center += poly[i];
+ }
+ center /= count;
+ sort(poly, count, center);
+
+#if DEBUG_SHADOW
+ // Since poly2 is overwritten as the result, we need to save a copy to do
+ // our verification.
+ Vector2 oldPoly2[poly2Length];
+ int oldPoly2Length = poly2Length;
+ memcpy(oldPoly2, poly2, sizeof(Vector2) * poly2Length);
+#endif
+
+ // Filter the result out from poly and put it into poly2.
+ poly2[0] = poly[0];
+ int lastOutputIndex = 0;
+ for (int i = 1; i < count; i++) {
+ Vector2 delta = poly[i] - poly2[lastOutputIndex];
+ if (delta.lengthSquared() >= EPSILON) {
+ poly2[++lastOutputIndex] = poly[i];
+ } else {
+ // If the vertices are too close, pick the inner one, because the
+ // inner one is more likely to be an intersection point.
+ Vector2 delta1 = poly[i] - center;
+ Vector2 delta2 = poly2[lastOutputIndex] - center;
+ if (delta1.lengthSquared() < delta2.lengthSquared()) {
+ poly2[lastOutputIndex] = poly[i];
+ }
+ }
+ }
+ int resultLength = lastOutputIndex + 1;
+
+#if DEBUG_SHADOW
+ testConvex(poly2, resultLength, "intersection");
+ testConvex(poly1, poly1Length, "input poly1");
+ testConvex(oldPoly2, oldPoly2Length, "input poly2");
+
+ testIntersection(poly1, poly1Length, oldPoly2, oldPoly2Length, poly2, resultLength);
+#endif
+
+ return resultLength;
+}
+
+/**
+ * Sort points about a center point
+ *
+ * @param poly The in and out polyogon as a Vector2 array.
+ * @param polyLength The number of vertices of the polygon.
+ * @param center the center ctr[0] = x , ctr[1] = y to sort around.
+ */
+void SpotShadow::sort(Vector2* poly, int polyLength, const Vector2& center) {
+ quicksortCirc(poly, 0, polyLength - 1, center);
+}
+
+/**
+ * Swap points pointed to by i and j
+ */
+void SpotShadow::swap(Vector2* points, int i, int j) {
+ Vector2 temp = points[i];
+ points[i] = points[j];
+ points[j] = temp;
+}
+
+/**
+ * quick sort implementation about the center.
+ */
+void SpotShadow::quicksortCirc(Vector2* points, int low, int high,
+ const Vector2& center) {
+ int i = low, j = high;
+ int p = low + (high - low) / 2;
+ float pivot = angle(points[p], center);
+ while (i <= j) {
+ while (angle(points[i], center) > pivot) {
+ i++;
+ }
+ while (angle(points[j], center) < pivot) {
+ j--;
+ }
+
+ if (i <= j) {
+ swap(points, i, j);
+ i++;
+ j--;
+ }
+ }
+ if (low < j) quicksortCirc(points, low, j, center);
+ if (i < high) quicksortCirc(points, i, high, center);
+}
+
+/**
+ * Sort points by x axis
+ *
+ * @param points points to sort
+ * @param low start index
+ * @param high end index
+ */
+void SpotShadow::quicksortX(Vector2* points, int low, int high) {
+ int i = low, j = high;
+ int p = low + (high - low) / 2;
+ float pivot = points[p].x;
+ while (i <= j) {
+ while (points[i].x < pivot) {
+ i++;
+ }
+ while (points[j].x > pivot) {
+ j--;
+ }
+
+ if (i <= j) {
+ swap(points, i, j);
+ i++;
+ j--;
+ }
+ }
+ if (low < j) quicksortX(points, low, j);
+ if (i < high) quicksortX(points, i, high);
+}
+
+/**
+ * Test whether a point is inside the polygon.
+ *
+ * @param testPoint the point to test
+ * @param poly the polygon
+ * @return true if the testPoint is inside the poly.
+ */
+bool SpotShadow::testPointInsidePolygon(const Vector2 testPoint,
+ const Vector2* poly, int len) {
+ bool c = false;
+ double testx = testPoint.x;
+ double testy = testPoint.y;
+ for (int i = 0, j = len - 1; i < len; j = i++) {
+ double startX = poly[j].x;
+ double startY = poly[j].y;
+ double endX = poly[i].x;
+ double endY = poly[i].y;
+
+ if (((endY > testy) != (startY > testy)) &&
+ (testx < (startX - endX) * (testy - endY)
+ / (startY - endY) + endX)) {
+ c = !c;
+ }
+ }
+ return c;
+}
+
+/**
+ * Make the polygon turn clockwise.
+ *
+ * @param polygon the polygon as a Vector2 array.
+ * @param len the number of points of the polygon
+ */
+void SpotShadow::makeClockwise(Vector2* polygon, int len) {
+ if (polygon == 0 || len == 0) {
+ return;
+ }
+ if (!ShadowTessellator::isClockwise(polygon, len)) {
+ reverse(polygon, len);
+ }
+}
+
+/**
+ * Reverse the polygon
+ *
+ * @param polygon the polygon as a Vector2 array
+ * @param len the number of points of the polygon
+ */
+void SpotShadow::reverse(Vector2* polygon, int len) {
+ int n = len / 2;
+ for (int i = 0; i < n; i++) {
+ Vector2 tmp = polygon[i];
+ int k = len - 1 - i;
+ polygon[i] = polygon[k];
+ polygon[k] = tmp;
+ }
+}
+
+/**
+ * Intersects two lines in parametric form. This function is called in a tight
+ * loop, and we need double precision to get things right.
+ *
+ * @param x1 the x coordinate point 1 of line 1
+ * @param y1 the y coordinate point 1 of line 1
+ * @param x2 the x coordinate point 2 of line 1
+ * @param y2 the y coordinate point 2 of line 1
+ * @param x3 the x coordinate point 1 of line 2
+ * @param y3 the y coordinate point 1 of line 2
+ * @param x4 the x coordinate point 2 of line 2
+ * @param y4 the y coordinate point 2 of line 2
+ * @param ret the x,y location of the intersection
+ * @return true if it found an intersection
+ */
+inline bool SpotShadow::lineIntersection(double x1, double y1, double x2, double y2,
+ double x3, double y3, double x4, double y4, Vector2& ret) {
+ double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+ if (d == 0.0) return false;
+
+ double dx = (x1 * y2 - y1 * x2);
+ double dy = (x3 * y4 - y3 * x4);
+ double x = (dx * (x3 - x4) - (x1 - x2) * dy) / d;
+ double y = (dx * (y3 - y4) - (y1 - y2) * dy) / d;
+
+ // The intersection should be in the middle of the point 1 and point 2,
+ // likewise point 3 and point 4.
+ if (((x - x1) * (x - x2) > EPSILON)
+ || ((x - x3) * (x - x4) > EPSILON)
+ || ((y - y1) * (y - y2) > EPSILON)
+ || ((y - y3) * (y - y4) > EPSILON)) {
+ // Not interesected
+ return false;
+ }
+ ret.x = x;
+ ret.y = y;
+ return true;
+
+}
+
+/**
+ * Compute a horizontal circular polygon about point (x , y , height) of radius
+ * (size)
+ *
+ * @param points number of the points of the output polygon.
+ * @param lightCenter the center of the light.
+ * @param size the light size.
+ * @param ret result polygon.
+ */
+void SpotShadow::computeLightPolygon(int points, const Vector3& lightCenter,
+ float size, Vector3* ret) {
+ // TODO: Caching all the sin / cos values and store them in a look up table.
+ for (int i = 0; i < points; i++) {
+ double angle = 2 * i * M_PI / points;
+ ret[i].x = cosf(angle) * size + lightCenter.x;
+ ret[i].y = sinf(angle) * size + lightCenter.y;
+ ret[i].z = lightCenter.z;
+ }
+}
+
+/**
+* Generate the shadow from a spot light.
+*
+* @param poly x,y,z vertexes of a convex polygon that occludes the light source
+* @param polyLength number of vertexes of the occluding polygon
+* @param lightCenter the center of the light
+* @param lightSize the radius of the light source
+* @param lightVertexCount the vertex counter for the light polygon
+* @param shadowTriangleStrip return an (x,y,alpha) triangle strip representing the shadow. Return
+* empty strip if error.
+*
+*/
+void SpotShadow::createSpotShadow(bool isCasterOpaque, const Vector3* poly,
+ int polyLength, const Vector3& lightCenter, float lightSize,
+ int lightVertexCount, VertexBuffer& retStrips) {
+ Vector3 light[lightVertexCount * 3];
+ computeLightPolygon(lightVertexCount, lightCenter, lightSize, light);
+ computeSpotShadow(isCasterOpaque, light, lightVertexCount, lightCenter, poly,
+ polyLength, retStrips);
+}
+
+/**
+ * Generate the shadow spot light of shape lightPoly and a object poly
+ *
+ * @param lightPoly x,y,z vertex of a convex polygon that is the light source
+ * @param lightPolyLength number of vertexes of the light source polygon
+ * @param poly x,y,z vertexes of a convex polygon that occludes the light source
+ * @param polyLength number of vertexes of the occluding polygon
+ * @param shadowTriangleStrip return an (x,y,alpha) triangle strip representing the shadow. Return
+ * empty strip if error.
+ */
+void SpotShadow::computeSpotShadow(bool isCasterOpaque, const Vector3* lightPoly,
+ int lightPolyLength, const Vector3& lightCenter, const Vector3* poly,
+ int polyLength, VertexBuffer& shadowTriangleStrip) {
+ // Point clouds for all the shadowed vertices
+ Vector2 shadowRegion[lightPolyLength * polyLength];
+ // Shadow polygon from one point light.
+ Vector2 outline[polyLength];
+ Vector2 umbraMem[polyLength * lightPolyLength];
+ Vector2* umbra = umbraMem;
+
+ int umbraLength = 0;
+
+ // Validate input, receiver is always at z = 0 plane.
+ bool inputPolyPositionValid = true;
+ for (int i = 0; i < polyLength; i++) {
+ if (poly[i].z >= lightPoly[0].z) {
+ inputPolyPositionValid = false;
+ ALOGW("polygon above the light");
+ break;
+ }
+ }
+
+ // If the caster's position is invalid, don't draw anything.
+ if (!inputPolyPositionValid) {
+ return;
+ }
+
+ // Calculate the umbra polygon based on intersections of all outlines
+ int k = 0;
+ for (int j = 0; j < lightPolyLength; j++) {
+ int m = 0;
+ for (int i = 0; i < polyLength; i++) {
+ // After validating the input, deltaZ is guaranteed to be positive.
+ float deltaZ = lightPoly[j].z - poly[i].z;
+ float ratioZ = lightPoly[j].z / deltaZ;
+ float x = lightPoly[j].x - ratioZ * (lightPoly[j].x - poly[i].x);
+ float y = lightPoly[j].y - ratioZ * (lightPoly[j].y - poly[i].y);
+
+ Vector2 newPoint = {x, y};
+ shadowRegion[k] = newPoint;
+ outline[m] = newPoint;
+
+ k++;
+ m++;
+ }
+
+ // For the first light polygon's vertex, use the outline as the umbra.
+ // Later on, use the intersection of the outline and existing umbra.
+ if (umbraLength == 0) {
+ for (int i = 0; i < polyLength; i++) {
+ umbra[i] = outline[i];
+ }
+ umbraLength = polyLength;
+ } else {
+ int col = ((j * 255) / lightPolyLength);
+ umbraLength = intersection(outline, polyLength, umbra, umbraLength);
+ if (umbraLength == 0) {
+ break;
+ }
+ }
+ }
+
+ // Generate the penumbra area using the hull of all shadow regions.
+ int shadowRegionLength = k;
+ Vector2 penumbra[k];
+ int penumbraLength = hull(shadowRegion, shadowRegionLength, penumbra);
+
+ Vector2 fakeUmbra[polyLength];
+ if (umbraLength < 3) {
+ // If there is no real umbra, make a fake one.
+ for (int i = 0; i < polyLength; i++) {
+ float deltaZ = lightCenter.z - poly[i].z;
+ float ratioZ = lightCenter.z / deltaZ;
+ float x = lightCenter.x - ratioZ * (lightCenter.x - poly[i].x);
+ float y = lightCenter.y - ratioZ * (lightCenter.y - poly[i].y);
+
+ fakeUmbra[i].x = x;
+ fakeUmbra[i].y = y;
+ }
+
+ // Shrink the centroid's shadow by 10%.
+ // TODO: Study the magic number of 10%.
+ Vector2 shadowCentroid =
+ ShadowTessellator::centroid2d(fakeUmbra, polyLength);
+ for (int i = 0; i < polyLength; i++) {
+ fakeUmbra[i] = shadowCentroid * (1.0f - SHADOW_SHRINK_SCALE) +
+ fakeUmbra[i] * SHADOW_SHRINK_SCALE;
+ }
+#if DEBUG_SHADOW
+ ALOGD("No real umbra make a fake one, centroid2d = %f , %f",
+ shadowCentroid.x, shadowCentroid.y);
+#endif
+ // Set the fake umbra, whose size is the same as the original polygon.
+ umbra = fakeUmbra;
+ umbraLength = polyLength;
+ }
+
+ generateTriangleStrip(isCasterOpaque, penumbra, penumbraLength, umbra,
+ umbraLength, poly, polyLength, shadowTriangleStrip);
+}
+
+/**
+ * Converts a polygon specified with CW vertices into an array of distance-from-centroid values.
+ *
+ * Returns false in error conditions
+ *
+ * @param poly Array of vertices. Note that these *must* be CW.
+ * @param polyLength The number of vertices in the polygon.
+ * @param polyCentroid The centroid of the polygon, from which rays will be cast
+ * @param rayDist The output array for the calculated distances, must be SHADOW_RAY_COUNT in size
+ */
+bool convertPolyToRayDist(const Vector2* poly, int polyLength, const Vector2& polyCentroid,
+ float* rayDist) {
+ const int rays = SHADOW_RAY_COUNT;
+ const float step = M_PI * 2 / rays;
+
+ const Vector2* lastVertex = &(poly[polyLength - 1]);
+ float startAngle = angle(*lastVertex, polyCentroid);
+
+ // Start with the ray that's closest to and less than startAngle
+ int rayIndex = floor((startAngle - EPSILON) / step);
+ rayIndex = (rayIndex + rays) % rays; // ensure positive
+
+ for (int polyIndex = 0; polyIndex < polyLength; polyIndex++) {
+ /*
+ * For a given pair of vertices on the polygon, poly[i-1] and poly[i], the rays that
+ * intersect these will be those that are between the two angles from the centroid that the
+ * vertices define.
+ *
+ * Because the polygon vertices are stored clockwise, the closest ray with an angle
+ * *smaller* than that defined by angle(poly[i], centroid) will be the first ray that does
+ * not intersect with poly[i-1], poly[i].
+ */
+ float currentAngle = angle(poly[polyIndex], polyCentroid);
+
+ // find first ray that will not intersect the line segment poly[i-1] & poly[i]
+ int firstRayIndexOnNextSegment = floor((currentAngle - EPSILON) / step);
+ firstRayIndexOnNextSegment = (firstRayIndexOnNextSegment + rays) % rays; // ensure positive
+
+ // Iterate through all rays that intersect with poly[i-1], poly[i] line segment.
+ // This may be 0 rays.
+ while (rayIndex != firstRayIndexOnNextSegment) {
+ float distanceToIntersect = rayIntersectPoints(polyCentroid,
+ cos(rayIndex * step),
+ sin(rayIndex * step),
+ *lastVertex, poly[polyIndex]);
+ if (distanceToIntersect < 0) {
+#if DEBUG_SHADOW
+ ALOGW("ERROR: convertPolyToRayDist failed");
+#endif
+ return false; // error case, abort
+ }
+
+ rayDist[rayIndex] = distanceToIntersect;
+
+ rayIndex = (rayIndex - 1 + rays) % rays;
+ }
+ lastVertex = &poly[polyIndex];
+ }
+
+ return true;
+}
+
+int SpotShadow::calculateOccludedUmbra(const Vector2* umbra, int umbraLength,
+ const Vector3* poly, int polyLength, Vector2* occludedUmbra) {
+ // Occluded umbra area is computed as the intersection of the projected 2D
+ // poly and umbra.
+ for (int i = 0; i < polyLength; i++) {
+ occludedUmbra[i].x = poly[i].x;
+ occludedUmbra[i].y = poly[i].y;
+ }
+
+ // Both umbra and incoming polygon are guaranteed to be CW, so we can call
+ // intersection() directly.
+ return intersection(umbra, umbraLength,
+ occludedUmbra, polyLength);
+}
+
+#define OCLLUDED_UMBRA_SHRINK_FACTOR 0.95f
+/**
+ * Generate a triangle strip given two convex polygons
+ *
+ * @param penumbra The outer polygon x,y vertexes
+ * @param penumbraLength The number of vertexes in the outer polygon
+ * @param umbra The inner outer polygon x,y vertexes
+ * @param umbraLength The number of vertexes in the inner polygon
+ * @param shadowTriangleStrip return an (x,y,alpha) triangle strip representing the shadow. Return
+ * empty strip if error.
+**/
+void SpotShadow::generateTriangleStrip(bool isCasterOpaque, const Vector2* penumbra,
+ int penumbraLength, const Vector2* umbra, int umbraLength,
+ const Vector3* poly, int polyLength, VertexBuffer& shadowTriangleStrip) {
+ const int rays = SHADOW_RAY_COUNT;
+ const int size = 2 * rays;
+ const float step = M_PI * 2 / rays;
+ // Centroid of the umbra.
+ Vector2 centroid = ShadowTessellator::centroid2d(umbra, umbraLength);
+#if DEBUG_SHADOW
+ ALOGD("centroid2d = %f , %f", centroid.x, centroid.y);
+#endif
+ // Intersection to the penumbra.
+ float penumbraDistPerRay[rays];
+ // Intersection to the umbra.
+ float umbraDistPerRay[rays];
+ // Intersection to the occluded umbra area.
+ float occludedUmbraDistPerRay[rays];
+
+ // convert CW polygons to ray distance encoding, aborting on conversion failure
+ if (!convertPolyToRayDist(umbra, umbraLength, centroid, umbraDistPerRay)) return;
+ if (!convertPolyToRayDist(penumbra, penumbraLength, centroid, penumbraDistPerRay)) return;
+
+ bool hasOccludedUmbraArea = false;
+ if (isCasterOpaque) {
+ Vector2 occludedUmbra[polyLength + umbraLength];
+ int occludedUmbraLength = calculateOccludedUmbra(umbra, umbraLength, poly, polyLength,
+ occludedUmbra);
+ // Make sure the centroid is inside the umbra, otherwise, fall back to the
+ // approach as if there is no occluded umbra area.
+ if (testPointInsidePolygon(centroid, occludedUmbra, occludedUmbraLength)) {
+ hasOccludedUmbraArea = true;
+ // Shrink the occluded umbra area to avoid pixel level artifacts.
+ for (int i = 0; i < occludedUmbraLength; i ++) {
+ occludedUmbra[i] = centroid + (occludedUmbra[i] - centroid) *
+ OCLLUDED_UMBRA_SHRINK_FACTOR;
+ }
+ if (!convertPolyToRayDist(occludedUmbra, occludedUmbraLength, centroid,
+ occludedUmbraDistPerRay)) {
+ return;
+ }
+ }
+ }
+
+ AlphaVertex* shadowVertices =
+ shadowTriangleStrip.alloc<AlphaVertex>(SHADOW_VERTEX_COUNT);
+
+ // NOTE: Shadow alpha values are transformed when stored in alphavertices,
+ // so that they can be consumed directly by gFS_Main_ApplyVertexAlphaShadowInterp
+ float transformedMaxAlpha = M_PI;
+
+ // Calculate the vertices (x, y, alpha) in the shadow area.
+ AlphaVertex centroidXYA;
+ AlphaVertex::set(&centroidXYA, centroid.x, centroid.y, transformedMaxAlpha);
+ for (int rayIndex = 0; rayIndex < rays; rayIndex++) {
+ float dx = cosf(step * rayIndex);
+ float dy = sinf(step * rayIndex);
+
+ // penumbra ring
+ float penumbraDistance = penumbraDistPerRay[rayIndex];
+ AlphaVertex::set(&shadowVertices[rayIndex],
+ dx * penumbraDistance + centroid.x,
+ dy * penumbraDistance + centroid.y, 0.0f);
+
+ // umbra ring
+ float umbraDistance = umbraDistPerRay[rayIndex];
+ AlphaVertex::set(&shadowVertices[rays + rayIndex],
+ dx * umbraDistance + centroid.x,
+ dy * umbraDistance + centroid.y,
+ transformedMaxAlpha);
+
+ // occluded umbra ring
+ if (hasOccludedUmbraArea) {
+ float occludedUmbraDistance = occludedUmbraDistPerRay[rayIndex];
+ AlphaVertex::set(&shadowVertices[2 * rays + rayIndex],
+ dx * occludedUmbraDistance + centroid.x,
+ dy * occludedUmbraDistance + centroid.y, transformedMaxAlpha);
+ } else {
+ // Put all vertices of the occluded umbra ring at the centroid.
+ shadowVertices[2 * rays + rayIndex] = centroidXYA;
+ }
+ }
+
+ shadowTriangleStrip.setMode(VertexBuffer::kTwoPolyRingShadow);
+ shadowTriangleStrip.computeBounds<AlphaVertex>();
+}
+
+/**
+ * This is only for experimental purpose.
+ * After intersections are calculated, we could smooth the polygon if needed.
+ * So far, we don't think it is more appealing yet.
+ *
+ * @param level The level of smoothness.
+ * @param rays The total number of rays.
+ * @param rayDist (In and Out) The distance for each ray.
+ *
+ */
+void SpotShadow::smoothPolygon(int level, int rays, float* rayDist) {
+ for (int k = 0; k < level; k++) {
+ for (int i = 0; i < rays; i++) {
+ float p1 = rayDist[(rays - 1 + i) % rays];
+ float p2 = rayDist[i];
+ float p3 = rayDist[(i + 1) % rays];
+ rayDist[i] = (p1 + p2 * 2 + p3) / 4;
+ }
+ }
+}
+
+#if DEBUG_SHADOW
+
+#define TEST_POINT_NUMBER 128
+
+/**
+ * Calculate the bounds for generating random test points.
+ */
+void SpotShadow::updateBound(const Vector2 inVector, Vector2& lowerBound,
+ Vector2& upperBound ) {
+ if (inVector.x < lowerBound.x) {
+ lowerBound.x = inVector.x;
+ }
+
+ if (inVector.y < lowerBound.y) {
+ lowerBound.y = inVector.y;
+ }
+
+ if (inVector.x > upperBound.x) {
+ upperBound.x = inVector.x;
+ }
+
+ if (inVector.y > upperBound.y) {
+ upperBound.y = inVector.y;
+ }
+}
+
+/**
+ * For debug purpose, when things go wrong, dump the whole polygon data.
+ */
+static void dumpPolygon(const Vector2* poly, int polyLength, const char* polyName) {
+ for (int i = 0; i < polyLength; i++) {
+ ALOGD("polygon %s i %d x %f y %f", polyName, i, poly[i].x, poly[i].y);
+ }
+}
+
+/**
+ * Test whether the polygon is convex.
+ */
+bool SpotShadow::testConvex(const Vector2* polygon, int polygonLength,
+ const char* name) {
+ bool isConvex = true;
+ for (int i = 0; i < polygonLength; i++) {
+ Vector2 start = polygon[i];
+ Vector2 middle = polygon[(i + 1) % polygonLength];
+ Vector2 end = polygon[(i + 2) % polygonLength];
+
+ double delta = (double(middle.x) - start.x) * (double(end.y) - start.y) -
+ (double(middle.y) - start.y) * (double(end.x) - start.x);
+ bool isCCWOrCoLinear = (delta >= EPSILON);
+
+ if (isCCWOrCoLinear) {
+ ALOGW("(Error Type 2): polygon (%s) is not a convex b/c start (x %f, y %f),"
+ "middle (x %f, y %f) and end (x %f, y %f) , delta is %f !!!",
+ name, start.x, start.y, middle.x, middle.y, end.x, end.y, delta);
+ isConvex = false;
+ break;
+ }
+ }
+ return isConvex;
+}
+
+/**
+ * Test whether or not the polygon (intersection) is within the 2 input polygons.
+ * Using Marte Carlo method, we generate a random point, and if it is inside the
+ * intersection, then it must be inside both source polygons.
+ */
+void SpotShadow::testIntersection(const Vector2* poly1, int poly1Length,
+ const Vector2* poly2, int poly2Length,
+ const Vector2* intersection, int intersectionLength) {
+ // Find the min and max of x and y.
+ Vector2 lowerBound(FLT_MAX, FLT_MAX);
+ Vector2 upperBound(-FLT_MAX, -FLT_MAX);
+ for (int i = 0; i < poly1Length; i++) {
+ updateBound(poly1[i], lowerBound, upperBound);
+ }
+ for (int i = 0; i < poly2Length; i++) {
+ updateBound(poly2[i], lowerBound, upperBound);
+ }
+
+ bool dumpPoly = false;
+ for (int k = 0; k < TEST_POINT_NUMBER; k++) {
+ // Generate a random point between minX, minY and maxX, maxY.
+ double randomX = rand() / double(RAND_MAX);
+ double randomY = rand() / double(RAND_MAX);
+
+ Vector2 testPoint;
+ testPoint.x = lowerBound.x + randomX * (upperBound.x - lowerBound.x);
+ testPoint.y = lowerBound.y + randomY * (upperBound.y - lowerBound.y);
+
+ // If the random point is in both poly 1 and 2, then it must be intersection.
+ if (testPointInsidePolygon(testPoint, intersection, intersectionLength)) {
+ if (!testPointInsidePolygon(testPoint, poly1, poly1Length)) {
+ dumpPoly = true;
+ ALOGW("(Error Type 1): one point (%f, %f) in the intersection is"
+ " not in the poly1",
+ testPoint.x, testPoint.y);
+ }
+
+ if (!testPointInsidePolygon(testPoint, poly2, poly2Length)) {
+ dumpPoly = true;
+ ALOGW("(Error Type 1): one point (%f, %f) in the intersection is"
+ " not in the poly2",
+ testPoint.x, testPoint.y);
+ }
+ }
+ }
+
+ if (dumpPoly) {
+ dumpPolygon(intersection, intersectionLength, "intersection");
+ for (int i = 1; i < intersectionLength; i++) {
+ Vector2 delta = intersection[i] - intersection[i - 1];
+ ALOGD("Intersetion i, %d Vs i-1 is delta %f", i, delta.lengthSquared());
+ }
+
+ dumpPolygon(poly1, poly1Length, "poly 1");
+ dumpPolygon(poly2, poly2Length, "poly 2");
+ }
+}
+#endif
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/SpotShadow.h b/libs/hwui/SpotShadow.h
new file mode 100644
index 0000000..d65ea89
--- /dev/null
+++ b/libs/hwui/SpotShadow.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_SPOT_SHADOW_H
+#define ANDROID_HWUI_SPOT_SHADOW_H
+
+#include "Debug.h"
+#include "Vector.h"
+#include "VertexBuffer.h"
+
+namespace android {
+namespace uirenderer {
+
+class SpotShadow {
+public:
+ static void createSpotShadow(bool isCasterOpaque, const Vector3* poly,
+ int polyLength, const Vector3& lightCenter, float lightSize,
+ int lightVertexCount, VertexBuffer& retStrips);
+
+private:
+ static int calculateOccludedUmbra(const Vector2* umbra, int umbraLength,
+ const Vector3* poly, int polyLength, Vector2* occludedUmbra);
+ static void computeSpotShadow(bool isCasterOpaque, const Vector3* lightPoly,
+ int lightPolyLength, const Vector3& lightCenter, const Vector3* poly,
+ int polyLength, VertexBuffer& retstrips);
+
+ static void computeLightPolygon(int points, const Vector3& lightCenter,
+ float size, Vector3* ret);
+
+ static void smoothPolygon(int level, int rays, float* rayDist);
+ static float rayIntersectPoly(const Vector2* poly, int polyLength,
+ const Vector2& point, float dx, float dy);
+
+ static void xsort(Vector2* points, int pointsLength);
+ static int hull(Vector2* points, int pointsLength, Vector2* retPoly);
+ static bool ccw(double ax, double ay, double bx, double by, double cx, double cy);
+ static int intersection(const Vector2* poly1, int poly1length, Vector2* poly2, int poly2length);
+ static void sort(Vector2* poly, int polyLength, const Vector2& center);
+
+ static void swap(Vector2* points, int i, int j);
+ static void quicksortCirc(Vector2* points, int low, int high, const Vector2& center);
+ static void quicksortX(Vector2* points, int low, int high);
+
+ static bool testPointInsidePolygon(const Vector2 testPoint, const Vector2* poly, int len);
+ static void makeClockwise(Vector2* polygon, int len);
+ static void reverse(Vector2* polygon, int len);
+ static inline bool lineIntersection(double x1, double y1, double x2, double y2,
+ double x3, double y3, double x4, double y4, Vector2& ret);
+
+ static void generateTriangleStrip(bool isCasterOpaque, const Vector2* penumbra,
+ int penumbraLength, const Vector2* umbra, int umbraLength,
+ const Vector3* poly, int polyLength, VertexBuffer& retstrips);
+
+#if DEBUG_SHADOW
+ // Verification utility function.
+ static bool testConvex(const Vector2* polygon, int polygonLength,
+ const char* name);
+ static void testIntersection(const Vector2* poly1, int poly1Length,
+ const Vector2* poly2, int poly2Length,
+ const Vector2* intersection, int intersectionLength);
+ static void updateBound(const Vector2 inVector, Vector2& lowerBound, Vector2& upperBound );
+#endif
+
+}; // SpotShadow
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_SPOT_SHADOW_H
diff --git a/libs/hwui/StatefulBaseRenderer.cpp b/libs/hwui/StatefulBaseRenderer.cpp
new file mode 100644
index 0000000..06c5ab4
--- /dev/null
+++ b/libs/hwui/StatefulBaseRenderer.cpp
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <SkCanvas.h>
+
+#include "StatefulBaseRenderer.h"
+
+#include "utils/MathUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+StatefulBaseRenderer::StatefulBaseRenderer()
+ : mDirtyClip(false)
+ , mWidth(-1)
+ , mHeight(-1)
+ , mSaveCount(1)
+ , mFirstSnapshot(new Snapshot)
+ , mSnapshot(mFirstSnapshot) {
+}
+
+void StatefulBaseRenderer::initializeSaveStack(float clipLeft, float clipTop,
+ float clipRight, float clipBottom, const Vector3& lightCenter) {
+ mSnapshot = new Snapshot(mFirstSnapshot,
+ SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
+ mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom);
+ mSnapshot->fbo = getTargetFbo();
+ mSnapshot->setRelativeLightCenter(lightCenter);
+ mSaveCount = 1;
+}
+
+void StatefulBaseRenderer::setViewport(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ mFirstSnapshot->initializeViewport(width, height);
+ onViewportInitialized();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Save (layer)
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Non-virtual implementation of save, guaranteed to save without side-effects
+ *
+ * The approach here and in restoreSnapshot(), allows subclasses to directly manipulate the save
+ * stack, and ensures restoreToCount() doesn't call back into subclass overrides.
+ */
+int StatefulBaseRenderer::saveSnapshot(int flags) {
+ mSnapshot = new Snapshot(mSnapshot, flags);
+ return mSaveCount++;
+}
+
+int StatefulBaseRenderer::save(int flags) {
+ return saveSnapshot(flags);
+}
+
+/**
+ * Non-virtual implementation of restore, guaranteed to restore without side-effects.
+ */
+void StatefulBaseRenderer::restoreSnapshot() {
+ sp<Snapshot> toRemove = mSnapshot;
+ sp<Snapshot> toRestore = mSnapshot->previous;
+
+ mSaveCount--;
+ mSnapshot = toRestore;
+
+ // subclass handles restore implementation
+ onSnapshotRestored(*toRemove, *toRestore);
+}
+
+void StatefulBaseRenderer::restore() {
+ if (mSaveCount > 1) {
+ restoreSnapshot();
+ }
+}
+
+void StatefulBaseRenderer::restoreToCount(int saveCount) {
+ if (saveCount < 1) saveCount = 1;
+
+ while (mSaveCount > saveCount) {
+ restoreSnapshot();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Matrix
+///////////////////////////////////////////////////////////////////////////////
+
+void StatefulBaseRenderer::getMatrix(Matrix4* matrix) const {
+ matrix->load(*(mSnapshot->transform));
+}
+
+void StatefulBaseRenderer::getMatrix(SkMatrix* matrix) const {
+ mSnapshot->transform->copyTo(*matrix);
+}
+
+void StatefulBaseRenderer::translate(float dx, float dy, float dz) {
+ mSnapshot->transform->translate(dx, dy, dz);
+}
+
+void StatefulBaseRenderer::rotate(float degrees) {
+ mSnapshot->transform->rotate(degrees, 0.0f, 0.0f, 1.0f);
+}
+
+void StatefulBaseRenderer::scale(float sx, float sy) {
+ mSnapshot->transform->scale(sx, sy, 1.0f);
+}
+
+void StatefulBaseRenderer::skew(float sx, float sy) {
+ mSnapshot->transform->skew(sx, sy);
+}
+
+void StatefulBaseRenderer::setMatrix(const SkMatrix& matrix) {
+ mSnapshot->transform->load(matrix);
+}
+
+void StatefulBaseRenderer::setMatrix(const Matrix4& matrix) {
+ mSnapshot->transform->load(matrix);
+}
+
+void StatefulBaseRenderer::concatMatrix(const SkMatrix& matrix) {
+ mat4 transform(matrix);
+ mSnapshot->transform->multiply(transform);
+}
+
+void StatefulBaseRenderer::concatMatrix(const Matrix4& matrix) {
+ mSnapshot->transform->multiply(matrix);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Clip
+///////////////////////////////////////////////////////////////////////////////
+
+bool StatefulBaseRenderer::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
+ if (CC_LIKELY(currentTransform()->rectToRect())) {
+ mDirtyClip |= mSnapshot->clip(left, top, right, bottom, op);
+ return !mSnapshot->clipRect->isEmpty();
+ }
+
+ SkPath path;
+ path.addRect(left, top, right, bottom);
+
+ return StatefulBaseRenderer::clipPath(&path, op);
+}
+
+bool StatefulBaseRenderer::clipPath(const SkPath* path, SkRegion::Op op) {
+ SkMatrix transform;
+ currentTransform()->copyTo(transform);
+
+ SkPath transformed;
+ path->transform(transform, &transformed);
+
+ SkRegion clip;
+ if (!mSnapshot->previous->clipRegion->isEmpty()) {
+ clip.setRegion(*mSnapshot->previous->clipRegion);
+ } else {
+ if (mSnapshot->previous == firstSnapshot()) {
+ clip.setRect(0, 0, getWidth(), getHeight());
+ } else {
+ Rect* bounds = mSnapshot->previous->clipRect;
+ clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom);
+ }
+ }
+
+ SkRegion region;
+ region.setPath(transformed, clip);
+
+ // region is the transformed input path, masked by the previous clip
+ mDirtyClip |= mSnapshot->clipRegionTransformed(region, op);
+ return !mSnapshot->clipRect->isEmpty();
+}
+
+bool StatefulBaseRenderer::clipRegion(const SkRegion* region, SkRegion::Op op) {
+ mDirtyClip |= mSnapshot->clipRegionTransformed(*region, op);
+ return !mSnapshot->clipRect->isEmpty();
+}
+
+void StatefulBaseRenderer::setClippingOutline(LinearAllocator& allocator, const Outline* outline) {
+ Rect bounds;
+ float radius;
+ if (!outline->getAsRoundRect(&bounds, &radius)) return; // only RR supported
+
+ if (!MathUtils::isPositive(radius)) {
+ // TODO: consider storing this rect separately, so that this can't be replaced with clip ops
+ clipRect(bounds.left, bounds.top, bounds.right, bounds.bottom, SkRegion::kIntersect_Op);
+ return;
+ }
+ setClippingRoundRect(allocator, bounds, radius);
+}
+
+void StatefulBaseRenderer::setClippingRoundRect(LinearAllocator& allocator,
+ const Rect& rect, float radius) {
+ mSnapshot->setClippingRoundRect(allocator, rect, radius);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Quick Rejection
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Calculates whether content drawn within the passed bounds would be outside of, or intersect with
+ * the clipRect. Does not modify the scissor.
+ *
+ * @param clipRequired if not null, will be set to true if element intersects clip
+ * (and wasn't rejected)
+ *
+ * @param snapOut if set, the geometry will be treated as having an AA ramp.
+ * See Rect::snapGeometryToPixelBoundaries()
+ */
+bool StatefulBaseRenderer::calculateQuickRejectForScissor(float left, float top,
+ float right, float bottom,
+ bool* clipRequired, bool* roundRectClipRequired,
+ bool snapOut) const {
+ if (mSnapshot->isIgnored() || bottom <= top || right <= left) {
+ return true;
+ }
+
+ Rect r(left, top, right, bottom);
+ currentTransform()->mapRect(r);
+ r.snapGeometryToPixelBoundaries(snapOut);
+
+ Rect clipRect(*currentClipRect());
+ clipRect.snapToPixelBoundaries();
+
+ if (!clipRect.intersects(r)) return true;
+
+ // clip is required if geometry intersects clip rect
+ if (clipRequired) {
+ *clipRequired = !clipRect.contains(r);
+ }
+
+ // round rect clip is required if RR clip exists, and geometry intersects its corners
+ if (roundRectClipRequired) {
+ *roundRectClipRequired = mSnapshot->roundRectClipState != NULL
+ && mSnapshot->roundRectClipState->areaRequiresRoundRectClip(r);
+ }
+ return false;
+}
+
+/**
+ * Returns false if drawing won't be clipped out.
+ *
+ * Makes the decision conservatively, by rounding out the mapped rect before comparing with the
+ * clipRect. To be used when perfect, pixel accuracy is not possible (esp. with tessellation) but
+ * rejection is still desired.
+ *
+ * This function, unlike quickRejectSetupScissor, should be used where precise geometry information
+ * isn't known (esp. when geometry adjusts based on scale). Generally, this will be first pass
+ * rejection where precise rejection isn't important, or precise information isn't available.
+ */
+bool StatefulBaseRenderer::quickRejectConservative(float left, float top,
+ float right, float bottom) const {
+ if (mSnapshot->isIgnored() || bottom <= top || right <= left) {
+ return true;
+ }
+
+ Rect r(left, top, right, bottom);
+ currentTransform()->mapRect(r);
+ r.roundOut(); // rounded out to be conservative
+
+ Rect clipRect(*currentClipRect());
+ clipRect.snapToPixelBoundaries();
+
+ if (!clipRect.intersects(r)) return true;
+
+ return false;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/StatefulBaseRenderer.h b/libs/hwui/StatefulBaseRenderer.h
new file mode 100644
index 0000000..3957d36
--- /dev/null
+++ b/libs/hwui/StatefulBaseRenderer.h
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_STATEFUL_BASE_RENDERER_H
+#define ANDROID_HWUI_STATEFUL_BASE_RENDERER_H
+
+#include <utils/RefBase.h>
+
+#include "Renderer.h"
+#include "Snapshot.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Abstract Renderer subclass, which implements Canvas state methods.
+ *
+ * Manages the Snapshot stack, implementing matrix, save/restore, and clipping methods in the
+ * Renderer interface. Drawing and recording classes that extend StatefulBaseRenderer will have
+ * different use cases:
+ *
+ * Drawing subclasses (i.e. OpenGLRenderer) can query attributes (such as transform) or hook into
+ * changes (e.g. save/restore) with minimal surface area for manipulating the stack itself.
+ *
+ * Recording subclasses (i.e. DisplayListRenderer) can both record and pass through state operations
+ * to StatefulBaseRenderer, so that not only will querying operations work (getClip/Matrix), but so
+ * that quickRejection can also be used.
+ */
+class StatefulBaseRenderer : public Renderer {
+public:
+ StatefulBaseRenderer();
+
+ virtual status_t prepare(bool opaque) {
+ return prepareDirty(0.0f, 0.0f, mWidth, mHeight, opaque);
+ }
+
+ /**
+ * Initialize the first snapshot, computing the projection matrix, and stores the dimensions of
+ * the render target.
+ */
+ virtual void setViewport(int width, int height);
+ void initializeSaveStack(float clipLeft, float clipTop, float clipRight, float clipBottom,
+ const Vector3& lightCenter);
+
+ // getters
+ bool hasRectToRectTransform() const {
+ return CC_LIKELY(currentTransform()->rectToRect());
+ }
+
+ // Save (layer)
+ virtual int getSaveCount() const { return mSaveCount; }
+ virtual int save(int flags);
+ virtual void restore();
+ virtual void restoreToCount(int saveCount);
+ //virtual int saveLayer(float left, float top, float right, float bottom,
+ // int alpha, SkXfermode::Mode mode, int flags);
+
+ // Matrix
+ void getMatrix(Matrix4* outMatrix) const;
+ virtual void getMatrix(SkMatrix* outMatrix) const;
+ virtual void translate(float dx, float dy, float dz = 0.0f);
+ virtual void rotate(float degrees);
+ virtual void scale(float sx, float sy);
+ virtual void skew(float sx, float sy);
+
+ virtual void setMatrix(const SkMatrix& matrix);
+ void setMatrix(const Matrix4& matrix); // internal only convenience method
+ virtual void concatMatrix(const SkMatrix& matrix);
+ void concatMatrix(const Matrix4& matrix); // internal only convenience method
+
+ // Clip
+ virtual const Rect& getLocalClipBounds() const { return mSnapshot->getLocalClip(); }
+
+ virtual bool quickRejectConservative(float left, float top, float right, float bottom) const;
+
+ virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
+ virtual bool clipPath(const SkPath* path, SkRegion::Op op);
+ virtual bool clipRegion(const SkRegion* region, SkRegion::Op op);
+
+ /**
+ * Does not support different clipping Ops (that is, every call to setClippingOutline is
+ * effectively using SkRegion::kReplaceOp)
+ *
+ * The clipping outline is independent from the regular clip.
+ */
+ void setClippingOutline(LinearAllocator& allocator, const Outline* outline);
+ void setClippingRoundRect(LinearAllocator& allocator,
+ const Rect& rect, float radius);
+
+protected:
+ const Rect& getRenderTargetClipBounds() const { return mSnapshot->getRenderTargetClip(); }
+
+ int getWidth() { return mWidth; }
+ int getHeight() { return mHeight; }
+
+ // Save
+ int saveSnapshot(int flags);
+ void restoreSnapshot();
+
+ // allows subclasses to control what value is stored in snapshot's fbo field in
+ // initializeSaveStack
+ virtual GLuint getTargetFbo() const {
+ return -1;
+ }
+
+ // Clip
+ bool calculateQuickRejectForScissor(float left, float top, float right, float bottom,
+ bool* clipRequired, bool* roundRectClipRequired, bool snapOut) const;
+
+ /**
+ * Called just after a restore has occurred. The 'removed' snapshot popped from the stack,
+ * 'restored' snapshot has become the top/current.
+ *
+ * Subclasses can override this method to handle layer restoration
+ */
+ virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {};
+
+ virtual void onViewportInitialized() {};
+
+ inline const Rect* currentClipRect() const {
+ return mSnapshot->clipRect;
+ }
+
+ inline const mat4* currentTransform() const {
+ return mSnapshot->transform;
+ }
+
+ inline const Snapshot* currentSnapshot() const {
+ return mSnapshot != NULL ? mSnapshot.get() : mFirstSnapshot.get();
+ }
+
+ inline const Snapshot* firstSnapshot() const {
+ return mFirstSnapshot.get();
+ }
+
+ // indicites that the clip has been changed since the last time it was consumed
+ bool mDirtyClip;
+
+private:
+ // Dimensions of the drawing surface
+ int mWidth, mHeight;
+
+ // Number of saved states
+ int mSaveCount;
+
+ // Base state
+ sp<Snapshot> mFirstSnapshot;
+
+protected:
+ // Current state
+ // TODO: should become private, once hooks needed by OpenGLRenderer are added
+ sp<Snapshot> mSnapshot;
+}; // class StatefulBaseRenderer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_STATEFUL_BASE_RENDERER_H
diff --git a/libs/hwui/Stencil.cpp b/libs/hwui/Stencil.cpp
index 2764523..8ce57db 100644
--- a/libs/hwui/Stencil.cpp
+++ b/libs/hwui/Stencil.cpp
@@ -35,7 +35,7 @@ namespace uirenderer {
Stencil::Stencil(): mState(kDisabled) {
}
-uint32_t Stencil::getStencilSize() {
+uint8_t Stencil::getStencilSize() {
return STENCIL_BUFFER_SIZE;
}
diff --git a/libs/hwui/Stencil.h b/libs/hwui/Stencil.h
index 83ad668..c5e5186 100644
--- a/libs/hwui/Stencil.h
+++ b/libs/hwui/Stencil.h
@@ -40,7 +40,7 @@ public:
* Returns the desired size for the stencil buffer. If the returned value
* is 0, then no stencil buffer is required.
*/
- ANDROID_API static uint32_t getStencilSize();
+ ANDROID_API static uint8_t getStencilSize();
/**
* Returns the smallest stencil format accepted by render buffers.
diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp
new file mode 100644
index 0000000..0a9aeb8
--- /dev/null
+++ b/libs/hwui/TessellationCache.cpp
@@ -0,0 +1,476 @@
+/*
+ * Copyright (C) 2014 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/JenkinsHash.h>
+#include <utils/Trace.h>
+
+#include "Caches.h"
+#include "OpenGLRenderer.h"
+#include "PathTessellator.h"
+#include "ShadowTessellator.h"
+#include "TessellationCache.h"
+
+#include "thread/Signal.h"
+#include "thread/Task.h"
+#include "thread/TaskProcessor.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache entries
+///////////////////////////////////////////////////////////////////////////////
+
+TessellationCache::Description::Description()
+ : type(kNone)
+ , scaleX(1.0f)
+ , scaleY(1.0f)
+ , aa(false)
+ , cap(SkPaint::kDefault_Cap)
+ , style(SkPaint::kFill_Style)
+ , strokeWidth(1.0f) {
+ memset(&shape, 0, sizeof(Shape));
+}
+
+TessellationCache::Description::Description(Type type, const Matrix4& transform, const SkPaint& paint)
+ : type(type)
+ , aa(paint.isAntiAlias())
+ , cap(paint.getStrokeCap())
+ , style(paint.getStyle())
+ , strokeWidth(paint.getStrokeWidth()) {
+ PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY);
+ memset(&shape, 0, sizeof(Shape));
+}
+
+hash_t TessellationCache::Description::hash() const {
+ uint32_t hash = JenkinsHashMix(0, type);
+ hash = JenkinsHashMix(hash, aa);
+ hash = JenkinsHashMix(hash, cap);
+ hash = JenkinsHashMix(hash, style);
+ hash = JenkinsHashMix(hash, android::hash_type(strokeWidth));
+ hash = JenkinsHashMix(hash, android::hash_type(scaleX));
+ hash = JenkinsHashMix(hash, android::hash_type(scaleY));
+ hash = JenkinsHashMixBytes(hash, (uint8_t*) &shape, sizeof(Shape));
+ return JenkinsHashWhiten(hash);
+}
+
+void TessellationCache::Description::setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const {
+ matrix->loadScale(scaleX, scaleY, 1.0f);
+ paint->setAntiAlias(aa);
+ paint->setStrokeCap(cap);
+ paint->setStyle(style);
+ paint->setStrokeWidth(strokeWidth);
+}
+
+TessellationCache::ShadowDescription::ShadowDescription()
+ : nodeKey(NULL) {
+ memset(&matrixData, 0, 16 * sizeof(float));
+}
+
+TessellationCache::ShadowDescription::ShadowDescription(const void* nodeKey, const Matrix4* drawTransform)
+ : nodeKey(nodeKey) {
+ memcpy(&matrixData, drawTransform->data, 16 * sizeof(float));
+}
+
+hash_t TessellationCache::ShadowDescription::hash() const {
+ uint32_t hash = JenkinsHashMixBytes(0, (uint8_t*) &nodeKey, sizeof(const void*));
+ hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, 16 * sizeof(float));
+ return JenkinsHashWhiten(hash);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// General purpose tessellation task processing
+///////////////////////////////////////////////////////////////////////////////
+
+class TessellationCache::TessellationTask : public Task<VertexBuffer*> {
+public:
+ TessellationTask(Tessellator tessellator, const Description& description)
+ : tessellator(tessellator)
+ , description(description) {
+ }
+
+ ~TessellationTask() {}
+
+ Tessellator tessellator;
+ Description description;
+};
+
+class TessellationCache::TessellationProcessor : public TaskProcessor<VertexBuffer*> {
+public:
+ TessellationProcessor(Caches& caches)
+ : TaskProcessor<VertexBuffer*>(&caches.tasks) {}
+ ~TessellationProcessor() {}
+
+ virtual void onProcess(const sp<Task<VertexBuffer*> >& task) {
+ TessellationTask* t = static_cast<TessellationTask*>(task.get());
+ ATRACE_NAME("shape tessellation");
+ VertexBuffer* buffer = t->tessellator(t->description);
+ t->setResult(buffer);
+ }
+};
+
+class TessellationCache::Buffer {
+public:
+ Buffer(const sp<Task<VertexBuffer*> >& task)
+ : mTask(task)
+ , mBuffer(NULL) {
+ }
+
+ ~Buffer() {
+ mTask.clear();
+ delete mBuffer;
+ }
+
+ unsigned int getSize() {
+ blockOnPrecache();
+ return mBuffer->getSize();
+ }
+
+ const VertexBuffer* getVertexBuffer() {
+ blockOnPrecache();
+ return mBuffer;
+ }
+
+private:
+ void blockOnPrecache() {
+ if (mTask != NULL) {
+ mBuffer = mTask->getResult();
+ LOG_ALWAYS_FATAL_IF(mBuffer == NULL, "Failed to precache");
+ mTask.clear();
+ }
+ }
+ sp<Task<VertexBuffer*> > mTask;
+ VertexBuffer* mBuffer;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Shadow tessellation task processing
+///////////////////////////////////////////////////////////////////////////////
+
+class ShadowTask : public Task<TessellationCache::vertexBuffer_pair_t*> {
+public:
+ ShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque,
+ const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ,
+ const Vector3& lightCenter, float lightRadius)
+ : drawTransform(*drawTransform)
+ , localClip(localClip)
+ , opaque(opaque)
+ , casterPerimeter(*casterPerimeter)
+ , transformXY(*transformXY)
+ , transformZ(*transformZ)
+ , lightCenter(lightCenter)
+ , lightRadius(lightRadius) {
+ }
+
+ ~ShadowTask() {
+ TessellationCache::vertexBuffer_pair_t* bufferPair = getResult();
+ delete bufferPair->getFirst();
+ delete bufferPair->getSecond();
+ delete bufferPair;
+ }
+
+ /* Note - we deep copy all task parameters, because *even though* pointers into Allocator
+ * controlled objects (like the SkPath and Matrix4s) should be safe for the entire frame,
+ * certain Allocators are destroyed before trim() is called to flush incomplete tasks.
+ *
+ * These deep copies could be avoided, long term, by cancelling or flushing outstanding tasks
+ * before tearning down single-frame LinearAllocators.
+ */
+ const Matrix4 drawTransform;
+ const Rect localClip;
+ bool opaque;
+ const SkPath casterPerimeter;
+ const Matrix4 transformXY;
+ const Matrix4 transformZ;
+ const Vector3 lightCenter;
+ const float lightRadius;
+};
+
+static void mapPointFakeZ(Vector3& point, const mat4* transformXY, const mat4* transformZ) {
+ // map z coordinate with true 3d matrix
+ point.z = transformZ->mapZ(point);
+
+ // map x,y coordinates with draw/Skia matrix
+ transformXY->mapPoint(point.x, point.y);
+}
+
+static void tessellateShadows(
+ const Matrix4* drawTransform, const Rect* localClip,
+ bool isCasterOpaque, const SkPath* casterPerimeter,
+ const Matrix4* casterTransformXY, const Matrix4* casterTransformZ,
+ const Vector3& lightCenter, float lightRadius,
+ VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer) {
+
+ // tessellate caster outline into a 2d polygon
+ Vector<Vertex> casterVertices2d;
+ const float casterRefinementThresholdSquared = 20.0f; // TODO: experiment with this value
+ PathTessellator::approximatePathOutlineVertices(*casterPerimeter,
+ casterRefinementThresholdSquared, casterVertices2d);
+ if (!ShadowTessellator::isClockwisePath(*casterPerimeter)) {
+ ShadowTessellator::reverseVertexArray(casterVertices2d.editArray(),
+ casterVertices2d.size());
+ }
+
+ if (casterVertices2d.size() == 0) return;
+
+ // map 2d caster poly into 3d
+ const int casterVertexCount = casterVertices2d.size();
+ Vector3 casterPolygon[casterVertexCount];
+ float minZ = FLT_MAX;
+ float maxZ = -FLT_MAX;
+ for (int i = 0; i < casterVertexCount; i++) {
+ const Vertex& point2d = casterVertices2d[i];
+ casterPolygon[i] = (Vector3){point2d.x, point2d.y, 0};
+ mapPointFakeZ(casterPolygon[i], casterTransformXY, casterTransformZ);
+ minZ = fmin(minZ, casterPolygon[i].z);
+ maxZ = fmax(maxZ, casterPolygon[i].z);
+ }
+
+ // map the centroid of the caster into 3d
+ Vector2 centroid = ShadowTessellator::centroid2d(
+ reinterpret_cast<const Vector2*>(casterVertices2d.array()),
+ casterVertexCount);
+ Vector3 centroid3d = {centroid.x, centroid.y, 0};
+ mapPointFakeZ(centroid3d, casterTransformXY, casterTransformZ);
+
+ // if the caster intersects the z=0 plane, lift it in Z so it doesn't
+ if (minZ < SHADOW_MIN_CASTER_Z) {
+ float casterLift = SHADOW_MIN_CASTER_Z - minZ;
+ for (int i = 0; i < casterVertexCount; i++) {
+ casterPolygon[i].z += casterLift;
+ }
+ centroid3d.z += casterLift;
+ }
+
+ // Check whether we want to draw the shadow at all by checking the caster's bounds against clip.
+ // We only have ortho projection, so we can just ignore the Z in caster for
+ // simple rejection calculation.
+ Rect casterBounds(casterPerimeter->getBounds());
+ casterTransformXY->mapRect(casterBounds);
+
+ // actual tessellation of both shadows
+ ShadowTessellator::tessellateAmbientShadow(
+ isCasterOpaque, casterPolygon, casterVertexCount, centroid3d,
+ casterBounds, *localClip, maxZ, ambientBuffer);
+
+ ShadowTessellator::tessellateSpotShadow(
+ isCasterOpaque, casterPolygon, casterVertexCount,
+ *drawTransform, lightCenter, lightRadius, casterBounds, *localClip,
+ spotBuffer);
+}
+
+class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t*> {
+public:
+ ShadowProcessor(Caches& caches)
+ : TaskProcessor<TessellationCache::vertexBuffer_pair_t*>(&caches.tasks) {}
+ ~ShadowProcessor() {}
+
+ virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t*> >& task) {
+ ShadowTask* t = static_cast<ShadowTask*>(task.get());
+ ATRACE_NAME("shadow tessellation");
+
+ VertexBuffer* ambientBuffer = new VertexBuffer;
+ VertexBuffer* spotBuffer = new VertexBuffer;
+ tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter,
+ &t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius,
+ *ambientBuffer, *spotBuffer);
+
+ t->setResult(new TessellationCache::vertexBuffer_pair_t(ambientBuffer, spotBuffer));
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache constructor/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+TessellationCache::TessellationCache()
+ : mSize(0)
+ , mMaxSize(MB(DEFAULT_VERTEX_CACHE_SIZE))
+ , mCache(LruCache<Description, Buffer*>::kUnlimitedCapacity)
+ , mShadowCache(LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*>::kUnlimitedCapacity) {
+ char property[PROPERTY_VALUE_MAX];
+ if (property_get(PROPERTY_VERTEX_CACHE_SIZE, property, NULL) > 0) {
+ INIT_LOGD(" Setting %s cache size to %sMB", name, property);
+ setMaxSize(MB(atof(property)));
+ } else {
+ INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_VERTEX_CACHE_SIZE);
+ }
+
+ mCache.setOnEntryRemovedListener(&mBufferRemovedListener);
+ mShadowCache.setOnEntryRemovedListener(&mBufferPairRemovedListener);
+ mDebugEnabled = readDebugLevel() & kDebugCaches;
+}
+
+TessellationCache::~TessellationCache() {
+ mCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t TessellationCache::getSize() {
+ LruCache<Description, Buffer*>::Iterator iter(mCache);
+ uint32_t size = 0;
+ while (iter.next()) {
+ size += iter.value()->getSize();
+ }
+ return size;
+}
+
+uint32_t TessellationCache::getMaxSize() {
+ return mMaxSize;
+}
+
+void TessellationCache::setMaxSize(uint32_t maxSize) {
+ mMaxSize = maxSize;
+ while (mSize > mMaxSize) {
+ mCache.removeOldest();
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+
+void TessellationCache::trim() {
+ uint32_t size = getSize();
+ while (size > mMaxSize) {
+ size -= mCache.peekOldestValue()->getSize();
+ mCache.removeOldest();
+ }
+ mShadowCache.clear();
+}
+
+void TessellationCache::clear() {
+ mCache.clear();
+ mShadowCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void TessellationCache::BufferRemovedListener::operator()(Description& description,
+ Buffer*& buffer) {
+ delete buffer;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Shadows
+///////////////////////////////////////////////////////////////////////////////
+
+void TessellationCache::precacheShadows(const Matrix4* drawTransform, const Rect& localClip,
+ bool opaque, const SkPath* casterPerimeter,
+ const Matrix4* transformXY, const Matrix4* transformZ,
+ const Vector3& lightCenter, float lightRadius) {
+ ShadowDescription key(casterPerimeter, drawTransform);
+
+ sp<ShadowTask> task = new ShadowTask(drawTransform, localClip, opaque,
+ casterPerimeter, transformXY, transformZ, lightCenter, lightRadius);
+ if (mShadowProcessor == NULL) {
+ mShadowProcessor = new ShadowProcessor(Caches::getInstance());
+ }
+ mShadowProcessor->add(task);
+
+ task->incStrong(NULL); // not using sp<>s, so manually ref while in the cache
+ mShadowCache.put(key, task.get());
+}
+
+void TessellationCache::getShadowBuffers(const Matrix4* drawTransform, const Rect& localClip,
+ bool opaque, const SkPath* casterPerimeter,
+ const Matrix4* transformXY, const Matrix4* transformZ,
+ const Vector3& lightCenter, float lightRadius, vertexBuffer_pair_t& outBuffers) {
+ ShadowDescription key(casterPerimeter, drawTransform);
+ ShadowTask* task = static_cast<ShadowTask*>(mShadowCache.get(key));
+ if (!task) {
+ precacheShadows(drawTransform, localClip, opaque, casterPerimeter,
+ transformXY, transformZ, lightCenter, lightRadius);
+ task = static_cast<ShadowTask*>(mShadowCache.get(key));
+ }
+ LOG_ALWAYS_FATAL_IF(task == NULL, "shadow not precached");
+ outBuffers = *(task->getResult());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Tessellation precaching
+///////////////////////////////////////////////////////////////////////////////
+
+TessellationCache::Buffer* TessellationCache::getOrCreateBuffer(
+ const Description& entry, Tessellator tessellator) {
+ Buffer* buffer = mCache.get(entry);
+ if (!buffer) {
+ // not cached, enqueue a task to fill the buffer
+ sp<TessellationTask> task = new TessellationTask(tessellator, entry);
+ buffer = new Buffer(task);
+
+ if (mProcessor == NULL) {
+ mProcessor = new TessellationProcessor(Caches::getInstance());
+ }
+ mProcessor->add(task);
+ mCache.put(entry, buffer);
+ }
+ return buffer;
+}
+
+static VertexBuffer* tessellatePath(const TessellationCache::Description& description,
+ const SkPath& path) {
+ Matrix4 matrix;
+ SkPaint paint;
+ description.setupMatrixAndPaint(&matrix, &paint);
+ VertexBuffer* buffer = new VertexBuffer();
+ PathTessellator::tessellatePath(path, &paint, matrix, *buffer);
+ return buffer;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RoundRect
+///////////////////////////////////////////////////////////////////////////////
+
+static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description) {
+ SkRect rect = SkRect::MakeWH(description.shape.roundRect.width,
+ description.shape.roundRect.height);
+ float rx = description.shape.roundRect.rx;
+ float ry = description.shape.roundRect.ry;
+ if (description.style == SkPaint::kStrokeAndFill_Style) {
+ float outset = description.strokeWidth / 2;
+ rect.outset(outset, outset);
+ rx += outset;
+ ry += outset;
+ }
+ SkPath path;
+ path.addRoundRect(rect, rx, ry);
+ return tessellatePath(description, path);
+}
+
+TessellationCache::Buffer* TessellationCache::getRoundRectBuffer(
+ const Matrix4& transform, const SkPaint& paint,
+ float width, float height, float rx, float ry) {
+ Description entry(Description::kRoundRect, transform, paint);
+ entry.shape.roundRect.width = width;
+ entry.shape.roundRect.height = height;
+ entry.shape.roundRect.rx = rx;
+ entry.shape.roundRect.ry = ry;
+ return getOrCreateBuffer(entry, &tessellateRoundRect);
+}
+const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform, const SkPaint& paint,
+ float width, float height, float rx, float ry) {
+ return getRoundRectBuffer(transform, paint, width, height, rx, ry)->getVertexBuffer();
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h
new file mode 100644
index 0000000..688a699
--- /dev/null
+++ b/libs/hwui/TessellationCache.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_TESSELLATION_CACHE_H
+#define ANDROID_HWUI_TESSELLATION_CACHE_H
+
+#include <utils/LruCache.h>
+#include <utils/Mutex.h>
+#include <utils/Vector.h>
+
+#include "Debug.h"
+#include "utils/Macros.h"
+#include "utils/Pair.h"
+#include "VertexBuffer.h"
+
+class SkBitmap;
+class SkCanvas;
+class SkPaint;
+class SkPath;
+struct SkRect;
+
+namespace android {
+namespace uirenderer {
+
+class Caches;
+
+///////////////////////////////////////////////////////////////////////////////
+// Classes
+///////////////////////////////////////////////////////////////////////////////
+
+class TessellationCache {
+public:
+ typedef Pair<VertexBuffer*, VertexBuffer*> vertexBuffer_pair_t;
+
+ struct Description {
+ DESCRIPTION_TYPE(Description);
+ enum Type {
+ kNone,
+ kRoundRect,
+ };
+
+ Type type;
+ float scaleX;
+ float scaleY;
+ bool aa;
+ SkPaint::Cap cap;
+ SkPaint::Style style;
+ float strokeWidth;
+ union Shape {
+ struct RoundRect {
+ float width;
+ float height;
+ float rx;
+ float ry;
+ } roundRect;
+ } shape;
+
+ Description();
+ Description(Type type, const Matrix4& transform, const SkPaint& paint);
+ hash_t hash() const;
+ void setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const;
+ };
+
+ struct ShadowDescription {
+ DESCRIPTION_TYPE(ShadowDescription);
+ const void* nodeKey;
+ float matrixData[16];
+
+ ShadowDescription();
+ ShadowDescription(const void* nodeKey, const Matrix4* drawTransform);
+ hash_t hash() const;
+ };
+
+ TessellationCache();
+ ~TessellationCache();
+
+ /**
+ * Clears the cache. This causes all TessellationBuffers to be deleted.
+ */
+ void clear();
+
+ /**
+ * Sets the maximum size of the cache in bytes.
+ */
+ void setMaxSize(uint32_t maxSize);
+ /**
+ * Returns the maximum size of the cache in bytes.
+ */
+ uint32_t getMaxSize();
+ /**
+ * Returns the current size of the cache in bytes.
+ */
+ uint32_t getSize();
+
+ /**
+ * Trims the contents of the cache, removing items until it's under its
+ * specified limit.
+ *
+ * Trimming is used for caches that support pre-caching from a worker
+ * thread. During pre-caching the maximum limit of the cache can be
+ * exceeded for the duration of the frame. It is therefore required to
+ * trim the cache at the end of the frame to keep the total amount of
+ * memory used under control.
+ *
+ * Also removes transient Shadow VertexBuffers, which aren't cached between frames.
+ */
+ void trim();
+
+ // TODO: precache/get for Oval, Lines, Points, etc.
+
+ void precacheRoundRect(const Matrix4& transform, const SkPaint& paint,
+ float width, float height, float rx, float ry) {
+ getRoundRectBuffer(transform, paint, width, height, rx, ry);
+ }
+ const VertexBuffer* getRoundRect(const Matrix4& transform, const SkPaint& paint,
+ float width, float height, float rx, float ry);
+
+ void precacheShadows(const Matrix4* drawTransform, const Rect& localClip,
+ bool opaque, const SkPath* casterPerimeter,
+ const Matrix4* transformXY, const Matrix4* transformZ,
+ const Vector3& lightCenter, float lightRadius);
+
+ void getShadowBuffers(const Matrix4* drawTransform, const Rect& localClip,
+ bool opaque, const SkPath* casterPerimeter,
+ const Matrix4* transformXY, const Matrix4* transformZ,
+ const Vector3& lightCenter, float lightRadius,
+ vertexBuffer_pair_t& outBuffers);
+
+private:
+ class Buffer;
+ class TessellationTask;
+ class TessellationProcessor;
+
+ typedef VertexBuffer* (*Tessellator)(const Description&);
+
+ Buffer* getRectBuffer(const Matrix4& transform, const SkPaint& paint,
+ float width, float height);
+ Buffer* getRoundRectBuffer(const Matrix4& transform, const SkPaint& paint,
+ float width, float height, float rx, float ry);
+
+ Buffer* getOrCreateBuffer(const Description& entry, Tessellator tessellator);
+
+ uint32_t mSize;
+ uint32_t mMaxSize;
+
+ bool mDebugEnabled;
+
+ mutable Mutex mLock;
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // General tessellation caching
+ ///////////////////////////////////////////////////////////////////////////////
+ sp<TaskProcessor<VertexBuffer*> > mProcessor;
+ LruCache<Description, Buffer*> mCache;
+ class BufferRemovedListener : public OnEntryRemoved<Description, Buffer*> {
+ void operator()(Description& description, Buffer*& buffer);
+ };
+ BufferRemovedListener mBufferRemovedListener;
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // Shadow tessellation caching
+ ///////////////////////////////////////////////////////////////////////////////
+ sp<TaskProcessor<vertexBuffer_pair_t*> > mShadowProcessor;
+
+ // holds a pointer, and implicit strong ref to each shadow task of the frame
+ LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*> mShadowCache;
+ class BufferPairRemovedListener : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t*>*> {
+ void operator()(ShadowDescription& description, Task<vertexBuffer_pair_t*>*& bufferPairTask) {
+ bufferPairTask->decStrong(NULL);
+ }
+ };
+ BufferPairRemovedListener mBufferPairRemovedListener;
+
+}; // class TessellationCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_PATH_CACHE_H
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
index 0b2c130..4eec462 100644
--- a/libs/hwui/TextDropShadowCache.cpp
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -147,7 +147,7 @@ void TextDropShadowCache::setMaxSize(uint32_t maxSize) {
// Callbacks
///////////////////////////////////////////////////////////////////////////////
-void TextDropShadowCache::operator()(ShadowText& text, ShadowTexture*& texture) {
+void TextDropShadowCache::operator()(ShadowText&, ShadowTexture*& texture) {
if (texture) {
mSize -= texture->bitmapSize;
@@ -168,7 +168,7 @@ void TextDropShadowCache::clear() {
mCache.clear();
}
-ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32_t len,
+ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, uint32_t len,
int numGlyphs, float radius, const float* positions) {
ShadowText entry(paint, radius, len, text, positions);
ShadowTexture* texture = mCache.get(entry);
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
index 04d7357..54b930b 100644
--- a/libs/hwui/TextDropShadowCache.h
+++ b/libs/hwui/TextDropShadowCache.h
@@ -38,7 +38,7 @@ struct ShadowText {
}
// len is the number of bytes in text
- ShadowText(SkPaint* paint, float radius, uint32_t len, const char* srcText,
+ ShadowText(const SkPaint* paint, float radius, uint32_t len, const char* srcText,
const float* positions):
len(len), radius(radius), positions(positions) {
// TODO: Propagate this through the API, we should not cast here
@@ -135,7 +135,7 @@ public:
*/
void operator()(ShadowText& text, ShadowTexture*& texture);
- ShadowTexture* get(SkPaint* paint, const char* text, uint32_t len,
+ ShadowTexture* get(const SkPaint* paint, const char* text, uint32_t len,
int numGlyphs, float radius, const float* positions);
/**
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 7923ce7..e783905 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -25,14 +25,14 @@ namespace android {
namespace uirenderer {
Texture::Texture(): id(0), generation(0), blend(false), width(0), height(0),
- cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL),
+ cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL), isInUse(false),
mWrapS(GL_CLAMP_TO_EDGE), mWrapT(GL_CLAMP_TO_EDGE),
mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST),
mFirstFilter(true), mFirstWrap(true), mCaches(Caches::getInstance()) {
}
Texture::Texture(Caches& caches): id(0), generation(0), blend(false), width(0), height(0),
- cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL),
+ cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL), isInUse(false),
mWrapS(GL_CLAMP_TO_EDGE), mWrapT(GL_CLAMP_TO_EDGE),
mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST),
mFirstFilter(true), mFirstWrap(true), mCaches(caches) {
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index d48ec59..d5601f8 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -94,6 +94,12 @@ public:
*/
const UvMapper* uvMapper;
+ /**
+ * Whether or not the Texture is marked in use and thus not evictable for
+ * the current frame. This is reset at the start of a new frame.
+ */
+ bool isInUse;
+
private:
/**
* Last wrap modes set on this texture. Defaults to GL_CLAMP_TO_EDGE.
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 2f102e0..3b8a9a4 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "OpenGLRenderer"
+#define ATRACE_TAG ATRACE_TAG_VIEW
#include <GLES2/gl2.h>
@@ -34,7 +35,7 @@ namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
TextureCache::TextureCache():
- mCache(LruCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
+ mCache(LruCache<const SkPixelRef*, Texture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE) {
char property[PROPERTY_VALUE_MAX];
@@ -58,7 +59,7 @@ TextureCache::TextureCache():
}
TextureCache::TextureCache(uint32_t maxByteSize):
- mCache(LruCache<SkBitmap*, Texture*>::kUnlimitedCapacity),
+ mCache(LruCache<const SkPixelRef*, Texture*>::kUnlimitedCapacity),
mSize(0), mMaxSize(maxByteSize) {
init();
}
@@ -103,7 +104,7 @@ void TextureCache::setFlushRate(float flushRate) {
// Callbacks
///////////////////////////////////////////////////////////////////////////////
-void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) {
+void TextureCache::operator()(const SkPixelRef*&, Texture*& texture) {
// This will be called already locked
if (texture) {
mSize -= texture->bitmapSize;
@@ -121,47 +122,93 @@ void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) {
// Caching
///////////////////////////////////////////////////////////////////////////////
-Texture* TextureCache::get(SkBitmap* bitmap) {
- Texture* texture = mCache.get(bitmap);
+void TextureCache::resetMarkInUse() {
+ LruCache<const SkPixelRef*, Texture*>::Iterator iter(mCache);
+ while (iter.next()) {
+ iter.value()->isInUse = false;
+ }
+}
+
+bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) {
+ if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
+ ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)",
+ bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize);
+ return false;
+ }
+ return true;
+}
+
+// Returns a prepared Texture* that either is already in the cache or can fit
+// in the cache (and is thus added to the cache)
+Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) {
+ Texture* texture = mCache.get(bitmap->pixelRef());
if (!texture) {
- if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
- ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)",
- bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize);
+ if (!canMakeTextureFromBitmap(bitmap)) {
return NULL;
}
const uint32_t size = bitmap->rowBytes() * bitmap->height();
+ bool canCache = size < mMaxSize;
// Don't even try to cache a bitmap that's bigger than the cache
- if (size < mMaxSize) {
- while (mSize + size > mMaxSize) {
+ while (canCache && mSize + size > mMaxSize) {
+ Texture* oldest = mCache.peekOldestValue();
+ if (oldest && !oldest->isInUse) {
mCache.removeOldest();
+ } else {
+ canCache = false;
}
}
- texture = new Texture();
- texture->bitmapSize = size;
- generateTexture(bitmap, texture, false);
+ if (canCache) {
+ texture = new Texture();
+ texture->bitmapSize = size;
+ generateTexture(bitmap, texture, false);
- if (size < mMaxSize) {
mSize += size;
TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
bitmap, texture->id, size, mSize);
if (mDebugEnabled) {
ALOGD("Texture created, size = %d", size);
}
- mCache.put(bitmap, texture);
- } else {
- texture->cleanup = true;
+ mCache.put(bitmap->pixelRef(), texture);
}
- } else if (bitmap->getGenerationID() != texture->generation) {
+ } 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
generateTexture(bitmap, texture, true);
}
return texture;
}
-Texture* TextureCache::getTransient(SkBitmap* bitmap) {
+bool TextureCache::prefetchAndMarkInUse(const SkBitmap* bitmap) {
+ Texture* texture = getCachedTexture(bitmap);
+ if (texture) {
+ texture->isInUse = true;
+ }
+ return texture;
+}
+
+Texture* TextureCache::get(const SkBitmap* bitmap) {
+ Texture* texture = getCachedTexture(bitmap);
+
+ if (!texture) {
+ if (!canMakeTextureFromBitmap(bitmap)) {
+ return NULL;
+ }
+
+ const uint32_t size = bitmap->rowBytes() * bitmap->height();
+ texture = new Texture();
+ texture->bitmapSize = size;
+ generateTexture(bitmap, texture, false);
+ texture->cleanup = true;
+ }
+
+ return texture;
+}
+
+Texture* TextureCache::getTransient(const SkBitmap* bitmap) {
Texture* texture = new Texture();
texture->bitmapSize = bitmap->rowBytes() * bitmap->height();
texture->cleanup = true;
@@ -171,11 +218,11 @@ Texture* TextureCache::getTransient(SkBitmap* bitmap) {
return texture;
}
-void TextureCache::remove(SkBitmap* bitmap) {
- mCache.remove(bitmap);
+void TextureCache::remove(const SkBitmap* bitmap) {
+ mCache.remove(bitmap->pixelRef());
}
-void TextureCache::removeDeferred(SkBitmap* bitmap) {
+void TextureCache::removeDeferred(const SkBitmap* bitmap) {
Mutex::Autolock _l(mLock);
mGarbage.push(bitmap);
}
@@ -184,8 +231,8 @@ void TextureCache::clearGarbage() {
Mutex::Autolock _l(mLock);
size_t count = mGarbage.size();
for (size_t i = 0; i < count; i++) {
- SkBitmap* bitmap = mGarbage.itemAt(i);
- mCache.remove(bitmap);
+ const SkBitmap* bitmap = mGarbage.itemAt(i);
+ mCache.remove(bitmap->pixelRef());
delete bitmap;
}
mGarbage.clear();
@@ -211,7 +258,7 @@ void TextureCache::flush() {
}
}
-void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate) {
+void TextureCache::generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate) {
SkAutoLockPixels alp(*bitmap);
if (!bitmap->readyToDraw()) {
@@ -219,6 +266,8 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege
return;
}
+ ATRACE_CALL();
+
// 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 = Extensions::getInstance().hasNPot();
@@ -239,20 +288,20 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege
Caches::getInstance().bindTexture(texture->id);
- switch (bitmap->getConfig()) {
- case SkBitmap::kA8_Config:
+ switch (bitmap->colorType()) {
+ case kAlpha_8_SkColorType:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(),
texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels());
texture->blend = true;
break;
- case SkBitmap::kRGB_565_Config:
+ case kRGB_565_SkColorType:
glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(),
texture->width, texture->height, GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels());
texture->blend = false;
break;
- case SkBitmap::kARGB_8888_Config:
+ case kN32_SkColorType:
glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(),
texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels());
@@ -260,14 +309,14 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege
// decoding happened
texture->blend = !bitmap->isOpaque();
break;
- case SkBitmap::kARGB_4444_Config:
- case SkBitmap::kIndex8_Config:
+ case kARGB_4444_SkColorType:
+ case kIndex_8_SkColorType:
glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel());
uploadLoFiTexture(resize, bitmap, texture->width, texture->height);
texture->blend = !bitmap->isOpaque();
break;
default:
- ALOGW("Unsupported bitmap config: %d", bitmap->getConfig());
+ ALOGW("Unsupported bitmap colorType: %d", bitmap->colorType());
break;
}
@@ -284,11 +333,10 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege
}
}
-void TextureCache::uploadLoFiTexture(bool resize, SkBitmap* bitmap,
+void TextureCache::uploadLoFiTexture(bool resize, const SkBitmap* bitmap,
uint32_t width, uint32_t height) {
SkBitmap rgbaBitmap;
- rgbaBitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height, 0, bitmap->alphaType());
- rgbaBitmap.allocPixels();
+ rgbaBitmap.allocPixels(SkImageInfo::MakeN32(width, height, bitmap->alphaType()));
rgbaBitmap.eraseColor(0);
SkCanvas canvas(rgbaBitmap);
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index 909d762..61db5b0 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -49,7 +49,7 @@ namespace uirenderer {
* Any texture added to the cache causing the cache to grow beyond the maximum
* allowed size will also cause the oldest texture to be kicked out.
*/
-class TextureCache: public OnEntryRemoved<SkBitmap*, Texture*> {
+class TextureCache: public OnEntryRemoved<const SkPixelRef*, Texture*> {
public:
TextureCache();
TextureCache(uint32_t maxByteSize);
@@ -59,28 +59,40 @@ public:
* Used as a callback when an entry is removed from the cache.
* Do not invoke directly.
*/
- void operator()(SkBitmap*& bitmap, Texture*& texture);
+ void operator()(const SkPixelRef*& pixelRef, Texture*& texture);
+
+ /**
+ * Resets all Textures to not be marked as in use
+ */
+ void resetMarkInUse();
+
+ /**
+ * Attempts to precache the SkBitmap. Returns true if a Texture was successfully
+ * acquired for the bitmap, false otherwise. If a Texture was acquired it is
+ * marked as in use.
+ */
+ bool prefetchAndMarkInUse(const SkBitmap* bitmap);
/**
* Returns the texture associated with the specified bitmap. If the texture
* cannot be found in the cache, a new texture is generated.
*/
- Texture* get(SkBitmap* bitmap);
+ Texture* get(const SkBitmap* bitmap);
/**
* Returns the texture associated with the specified bitmap. The generated
* texture is not kept in the cache. The caller must destroy the texture.
*/
- Texture* getTransient(SkBitmap* bitmap);
+ Texture* getTransient(const SkBitmap* bitmap);
/**
* Removes the texture associated with the specified bitmap.
* Upon remove the texture is freed.
*/
- void remove(SkBitmap* bitmap);
+ void remove(const SkBitmap* bitmap);
/**
* Removes the texture associated with the specified bitmap. This is meant
* to be called from threads that are not the EGL context thread.
*/
- void removeDeferred(SkBitmap* bitmap);
+ void removeDeferred(const SkBitmap* bitmap);
/**
* Process deferred removals.
*/
@@ -116,21 +128,26 @@ public:
void setFlushRate(float flushRate);
private:
+
+ bool canMakeTextureFromBitmap(const SkBitmap* bitmap);
+
+ Texture* getCachedTexture(const SkBitmap* bitmap);
+
/**
* 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(SkBitmap* bitmap, Texture* texture, bool regenerate = false);
+ void generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate = false);
- void uploadLoFiTexture(bool resize, SkBitmap* bitmap, uint32_t width, uint32_t height);
+ void uploadLoFiTexture(bool resize, const SkBitmap* bitmap, uint32_t width, uint32_t height);
void uploadToTexture(bool resize, GLenum format, GLsizei stride, GLsizei bpp,
GLsizei width, GLsizei height, GLenum type, const GLvoid * data);
void init();
- LruCache<SkBitmap*, Texture*> mCache;
+ LruCache<const SkPixelRef*, Texture*> mCache;
uint32_t mSize;
uint32_t mMaxSize;
@@ -140,7 +157,7 @@ private:
bool mDebugEnabled;
- Vector<SkBitmap*> mGarbage;
+ Vector<const SkBitmap*> mGarbage;
mutable Mutex mLock;
}; // class TextureCache
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
new file mode 100644
index 0000000..331f157
--- /dev/null
+++ b/libs/hwui/TreeInfo.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TREEINFO_H
+#define TREEINFO_H
+
+#include <string>
+
+#include <utils/Timers.h>
+
+#include "DamageAccumulator.h"
+#include "utils/Macros.h"
+
+namespace android {
+namespace uirenderer {
+
+class BaseRenderNodeAnimator;
+class AnimationListener;
+class OpenGLRenderer;
+class RenderState;
+
+class AnimationHook {
+public:
+ virtual void callOnFinished(BaseRenderNodeAnimator* animator, AnimationListener* listener) = 0;
+protected:
+ ~AnimationHook() {}
+};
+
+class ErrorHandler {
+public:
+ virtual void onError(const std::string& message) = 0;
+protected:
+ ~ErrorHandler() {}
+};
+
+// This would be a struct, but we want to PREVENT_COPY_AND_ASSIGN
+class TreeInfo {
+ PREVENT_COPY_AND_ASSIGN(TreeInfo);
+public:
+ enum TraversalMode {
+ // The full monty - sync, push, run animators, etc... Used by DrawFrameTask
+ // May only be used if both the UI thread and RT thread are blocked on the
+ // prepare
+ MODE_FULL,
+ // Run only what can be done safely on RT thread. Currently this only means
+ // animators, but potentially things like SurfaceTexture updates
+ // could be handled by this as well if there are no listeners
+ MODE_RT_ONLY,
+ };
+
+ explicit TreeInfo(TraversalMode mode, RenderState& renderState)
+ : mode(mode)
+ , frameTimeMs(0)
+ , animationHook(NULL)
+ , prepareTextures(mode == MODE_FULL)
+ , damageAccumulator(NULL)
+ , renderState(renderState)
+ , renderer(NULL)
+ , errorHandler(NULL)
+ {}
+
+ explicit TreeInfo(TraversalMode mode, const TreeInfo& clone)
+ : mode(mode)
+ , frameTimeMs(clone.frameTimeMs)
+ , animationHook(clone.animationHook)
+ , prepareTextures(mode == MODE_FULL)
+ , damageAccumulator(clone.damageAccumulator)
+ , renderState(clone.renderState)
+ , renderer(clone.renderer)
+ , errorHandler(clone.errorHandler)
+ {}
+
+ const TraversalMode mode;
+ nsecs_t frameTimeMs;
+ AnimationHook* animationHook;
+ // TODO: Remove this? Currently this is used to signal to stop preparing
+ // textures if we run out of cache space.
+ bool prepareTextures;
+
+ // Must not be null during actual usage
+ DamageAccumulator* damageAccumulator;
+ RenderState& renderState;
+ // The renderer that will be drawing the next frame. Use this to push any
+ // layer updates or similar. May be NULL.
+ OpenGLRenderer* renderer;
+ ErrorHandler* errorHandler;
+
+ struct Out {
+ Out()
+ : hasFunctors(false)
+ , hasAnimations(false)
+ , requiresUiRedraw(false)
+ , canDrawThisFrame(true)
+ {}
+ bool hasFunctors;
+ // This is only updated if evaluateAnimations is true
+ bool hasAnimations;
+ // This is set to true if there is an animation that RenderThread cannot
+ // animate itself, such as if hasFunctors is true
+ // This is only set if hasAnimations is true
+ bool requiresUiRedraw;
+ // This is set to true if draw() can be called this frame
+ // false means that we must delay until the next vsync pulse as frame
+ // production is outrunning consumption
+ // NOTE that if this is false CanvasContext will set either requiresUiRedraw
+ // *OR* will post itself for the next vsync automatically, use this
+ // only to avoid calling draw()
+ bool canDrawThisFrame;
+ } out;
+
+ // TODO: Damage calculations
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* TREEINFO_H */
diff --git a/libs/hwui/Vector.h b/libs/hwui/Vector.h
index 497924e..2a9f01c 100644
--- a/libs/hwui/Vector.h
+++ b/libs/hwui/Vector.h
@@ -24,16 +24,13 @@ namespace uirenderer {
// Classes
///////////////////////////////////////////////////////////////////////////////
+// MUST BE A POD - this means no ctor or dtor!
struct Vector2 {
float x;
float y;
- Vector2() :
- x(0.0f), y(0.0f) {
- }
-
- Vector2(float px, float py) :
- x(px), y(py) {
+ float lengthSquared() const {
+ return x * x + y * y;
}
float length() const {
@@ -71,19 +68,19 @@ struct Vector2 {
}
Vector2 operator+(const Vector2& v) const {
- return Vector2(x + v.x, y + v.y);
+ return (Vector2){x + v.x, y + v.y};
}
Vector2 operator-(const Vector2& v) const {
- return Vector2(x - v.x, y - v.y);
+ return (Vector2){x - v.x, y - v.y};
}
Vector2 operator/(float s) const {
- return Vector2(x / s, y / s);
+ return (Vector2){x / s, y / s};
}
Vector2 operator*(float s) const {
- return Vector2(x * s, y * s);
+ return (Vector2){x * s, y * s};
}
void normalize() {
@@ -93,7 +90,7 @@ struct Vector2 {
}
Vector2 copyNormalized() const {
- Vector2 v(x, y);
+ Vector2 v = {x, y};
v.normalize();
return v;
}
@@ -107,11 +104,17 @@ struct Vector2 {
}
}; // class Vector2
-///////////////////////////////////////////////////////////////////////////////
-// Types
-///////////////////////////////////////////////////////////////////////////////
+// MUST BE A POD - this means no ctor or dtor!
+class Vector3 {
+public:
+ float x;
+ float y;
+ float z;
-typedef Vector2 vec2;
+ void dump() {
+ ALOGD("Vector3[%.2f, %.2f, %.2f]", x, y, z);
+ }
+};
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h
index 790d4fc..4ff0b18 100644
--- a/libs/hwui/Vertex.h
+++ b/libs/hwui/Vertex.h
@@ -33,21 +33,22 @@ struct Vertex {
* Program::set()), and used to make geometry damage rect calculation conservative (see
* Rect::snapGeometryToPixelBoundaries())
*/
- static const float gGeometryFudgeFactor = 0.0656f;
+ static float GeometryFudgeFactor() { return 0.0656f; }
- float position[2];
+
+ float x, y;
static inline void set(Vertex* vertex, float x, float y) {
- vertex[0].position[0] = x;
- vertex[0].position[1] = y;
+ vertex[0].x = x;
+ vertex[0].y = y;
}
- static inline void set(Vertex* vertex, vec2 val) {
+ static inline void set(Vertex* vertex, Vector2 val) {
set(vertex, val.x, val.y);
}
static inline void copyWithOffset(Vertex* vertex, const Vertex& src, float x, float y) {
- set(vertex, src.position[0] + x, src.position[1] + y);
+ set(vertex, src.x + x, src.y + y);
}
}; // struct Vertex
@@ -56,19 +57,19 @@ struct Vertex {
* Simple structure to describe a vertex with a position and texture UV.
*/
struct TextureVertex {
- float position[2];
- float texture[2];
+ float x, y;
+ float u, v;
static inline void set(TextureVertex* vertex, float x, float y, float u, float v) {
- vertex[0].position[0] = x;
- vertex[0].position[1] = y;
- vertex[0].texture[0] = u;
- vertex[0].texture[1] = v;
+ vertex[0].x = x;
+ vertex[0].y = y;
+ vertex[0].u = u;
+ vertex[0].v = v;
}
static inline void setUV(TextureVertex* vertex, float u, float v) {
- vertex[0].texture[0] = u;
- vertex[0].texture[1] = v;
+ vertex[0].u = u;
+ vertex[0].v = v;
}
}; // struct TextureVertex
@@ -76,17 +77,17 @@ struct TextureVertex {
* Simple structure to describe a vertex with a position, texture UV and ARGB color.
*/
struct ColorTextureVertex : TextureVertex {
- float color[4];
+ float r, g, b, a;
static inline void set(ColorTextureVertex* vertex, float x, float y,
float u, float v, int color) {
TextureVertex::set(vertex, x, y, u, v);
const float a = ((color >> 24) & 0xff) / 255.0f;
- vertex[0].color[0] = a * ((color >> 16) & 0xff) / 255.0f;
- vertex[0].color[1] = a * ((color >> 8) & 0xff) / 255.0f;
- vertex[0].color[2] = a * ((color ) & 0xff) / 255.0f;
- vertex[0].color[3] = a;
+ vertex[0].r = a * ((color >> 16) & 0xff) / 255.0f;
+ vertex[0].g = a * ((color >> 8) & 0xff) / 255.0f;
+ vertex[0].b = a * ((color ) & 0xff) / 255.0f;
+ vertex[0].a = a;
}
}; // struct ColorTextureVertex
@@ -103,7 +104,7 @@ struct AlphaVertex : Vertex {
static inline void copyWithOffset(AlphaVertex* vertex, const AlphaVertex& src,
float x, float y) {
- Vertex::set(vertex, src.position[0] + x, src.position[1] + y);
+ Vertex::set(vertex, src.x + x, src.y + y);
vertex[0].alpha = src.alpha;
}
diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h
new file mode 100644
index 0000000..3837f88
--- /dev/null
+++ b/libs/hwui/VertexBuffer.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_VERTEX_BUFFER_H
+#define ANDROID_HWUI_VERTEX_BUFFER_H
+
+
+namespace android {
+namespace uirenderer {
+
+class VertexBuffer {
+public:
+ enum Mode {
+ kStandard = 0,
+ kOnePolyRingShadow = 1,
+ kTwoPolyRingShadow = 2
+ };
+
+ VertexBuffer()
+ : mBuffer(0)
+ , mVertexCount(0)
+ , mByteCount(0)
+ , mMode(kStandard)
+ , mCleanupMethod(NULL)
+ {}
+
+ ~VertexBuffer() {
+ if (mCleanupMethod) mCleanupMethod(mBuffer);
+ }
+
+ /**
+ This should be the only method used by the Tessellator. Subsequent calls to
+ alloc will allocate space within the first allocation (useful if you want to
+ eventually allocate multiple regions within a single VertexBuffer, such as
+ with PathTessellator::tessellateLines())
+ */
+ template <class TYPE>
+ TYPE* alloc(int vertexCount) {
+ if (mVertexCount) {
+ TYPE* reallocBuffer = (TYPE*)mReallocBuffer;
+ // already have allocated the buffer, re-allocate space within
+ if (mReallocBuffer != mBuffer) {
+ // not first re-allocation, leave space for degenerate triangles to separate strips
+ reallocBuffer += 2;
+ }
+ mReallocBuffer = reallocBuffer + vertexCount;
+ return reallocBuffer;
+ }
+ mVertexCount = vertexCount;
+ mByteCount = mVertexCount * sizeof(TYPE);
+ mReallocBuffer = mBuffer = (void*)new TYPE[vertexCount];
+
+ mCleanupMethod = &(cleanup<TYPE>);
+
+ return (TYPE*)mBuffer;
+ }
+
+ template <class TYPE>
+ void copyInto(const VertexBuffer& srcBuffer, float xOffset, float yOffset) {
+ int verticesToCopy = srcBuffer.getVertexCount();
+
+ TYPE* dst = alloc<TYPE>(verticesToCopy);
+ TYPE* src = (TYPE*)srcBuffer.getBuffer();
+
+ for (int i = 0; i < verticesToCopy; i++) {
+ TYPE::copyWithOffset(&dst[i], src[i], xOffset, yOffset);
+ }
+ }
+
+ /**
+ * Brute force bounds computation, used only if the producer of this
+ * vertex buffer can't determine bounds more simply/efficiently
+ */
+ template <class TYPE>
+ void computeBounds(int vertexCount = 0) {
+ if (!mVertexCount) {
+ mBounds.setEmpty();
+ return;
+ }
+
+ // default: compute over every vertex
+ if (vertexCount == 0) vertexCount = mVertexCount;
+
+ TYPE* current = (TYPE*)mBuffer;
+ TYPE* end = current + vertexCount;
+ mBounds.set(current->x, current->y, current->x, current->y);
+ for (; current < end; current++) {
+ mBounds.expandToCoverVertex(current->x, current->y);
+ }
+ }
+
+ const void* getBuffer() const { return mBuffer; }
+ const Rect& getBounds() const { return mBounds; }
+ unsigned int getVertexCount() const { return mVertexCount; }
+ unsigned int getSize() const { return mByteCount; }
+ Mode getMode() const { return mMode; }
+
+ void setBounds(Rect bounds) { mBounds = bounds; }
+ void setMode(Mode mode) { mMode = mode; }
+
+ template <class TYPE>
+ void createDegenerateSeparators(int allocSize) {
+ TYPE* end = (TYPE*)mBuffer + mVertexCount;
+ for (TYPE* degen = (TYPE*)mBuffer + allocSize; degen < end; degen += 2 + allocSize) {
+ memcpy(degen, degen - 1, sizeof(TYPE));
+ memcpy(degen + 1, degen + 2, sizeof(TYPE));
+ }
+ }
+
+private:
+ template <class TYPE>
+ static void cleanup(void* buffer) {
+ delete[] (TYPE*)buffer;
+ }
+
+ Rect mBounds;
+ void* mBuffer;
+ unsigned int mVertexCount;
+ unsigned int mByteCount;
+ Mode mMode;
+
+ void* mReallocBuffer; // used for multi-allocation
+
+ void (*mCleanupMethod)(void*);
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_VERTEX_BUFFER_H
diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp
index d5f38b5..24ffb80 100644
--- a/libs/hwui/font/CacheTexture.cpp
+++ b/libs/hwui/font/CacheTexture.cpp
@@ -114,7 +114,7 @@ CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint3
mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount),
mCaches(Caches::getInstance()) {
mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
- mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
+ mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE);
// OpenGL ES 3.0+ lets us specify the row length for unpack operations such
// as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture.
@@ -143,7 +143,7 @@ void CacheTexture::init() {
// reset, then create a new remainder space to start again
reset();
mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE,
- mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE, true);
+ mWidth - TEXTURE_BORDER_SIZE, mHeight - TEXTURE_BORDER_SIZE);
}
void CacheTexture::releaseMesh() {
diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h
index 61b38f8..4cc4f22 100644
--- a/libs/hwui/font/CacheTexture.h
+++ b/libs/hwui/font/CacheTexture.h
@@ -54,7 +54,7 @@ struct CacheBlock {
CacheBlock* mNext;
CacheBlock* mPrev;
- CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height, bool empty = false):
+ CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height):
mX(x), mY(y), mWidth(width), mHeight(height), mNext(NULL), mPrev(NULL) {
}
diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp
index b115756..ba878ba 100644
--- a/libs/hwui/font/Font.cpp
+++ b/libs/hwui/font/Font.cpp
@@ -42,9 +42,11 @@ namespace uirenderer {
Font::Font(FontRenderer* state, const Font::FontDescription& desc) :
mState(state), mDescription(desc) {
+ mDeviceProperties = SkDeviceProperties::Make(SkDeviceProperties::Geometry::MakeDefault(), 1.0f);
}
-Font::FontDescription::FontDescription(const SkPaint* paint, const mat4& matrix) {
+Font::FontDescription::FontDescription(const SkPaint* paint, const SkMatrix& rasterMatrix)
+ : mLookupTransform(rasterMatrix) {
mFontId = SkTypeface::UniqueID(paint->getTypeface());
mFontSize = paint->getTextSize();
mFlags = 0;
@@ -57,9 +59,6 @@ Font::FontDescription::FontDescription(const SkPaint* paint, const mat4& matrix)
mStrokeWidth = paint->getStrokeWidth();
mAntiAliasing = paint->isAntiAlias();
mHinting = paint->getHinting();
- mLookupTransform.reset();
- mLookupTransform[SkMatrix::kMScaleX] = roundf(fmaxf(1.0f, matrix[mat4::kScaleX]));
- mLookupTransform[SkMatrix::kMScaleY] = roundf(fmaxf(1.0f, matrix[mat4::kScaleY]));
if (!mLookupTransform.invert(&mInverseLookupTransform)) {
ALOGW("Could not query the inverse lookup transform for this font");
}
@@ -141,12 +140,12 @@ void Font::invalidateTextureCache(CacheTexture* cacheTexture) {
void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
- int nPenX = x + glyph->mBitmapLeft;
- int nPenY = y + glyph->mBitmapTop;
-
int width = (int) glyph->mBitmapWidth;
int height = (int) glyph->mBitmapHeight;
+ int nPenX = x + glyph->mBitmapLeft;
+ int nPenY = y + glyph->mBitmapTop;
+
if (bounds->bottom > nPenY) {
bounds->bottom = nPenY;
}
@@ -163,12 +162,12 @@ void Font::measureCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
- float nPenX = x + glyph->mBitmapLeft;
- float nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
-
float width = (float) glyph->mBitmapWidth;
float height = (float) glyph->mBitmapHeight;
+ float nPenX = x + glyph->mBitmapLeft;
+ float nPenY = y + glyph->mBitmapTop + height;
+
float u1 = glyph->mBitmapMinU;
float u2 = glyph->mBitmapMaxU;
float v1 = glyph->mBitmapMinV;
@@ -182,10 +181,13 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
void Font::drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y,
uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) {
+ float width = (float) glyph->mBitmapWidth;
+ float height = (float) glyph->mBitmapHeight;
+
SkPoint p[4];
- p[0].iset(glyph->mBitmapLeft, glyph->mBitmapTop + glyph->mBitmapHeight);
- p[1].iset(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop + glyph->mBitmapHeight);
- p[2].iset(glyph->mBitmapLeft + glyph->mBitmapWidth, glyph->mBitmapTop);
+ p[0].iset(glyph->mBitmapLeft, glyph->mBitmapTop + height);
+ p[1].iset(glyph->mBitmapLeft + width, glyph->mBitmapTop + height);
+ p[2].iset(glyph->mBitmapLeft + width, glyph->mBitmapTop);
p[3].iset(glyph->mBitmapLeft, glyph->mBitmapTop);
mDescription.mInverseLookupTransform.mapPoints(p, 4);
@@ -278,12 +280,12 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float
glyph->mCacheTexture);
}
-CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching) {
+CachedGlyphInfo* Font::getCachedGlyph(const SkPaint* paint, glyph_t textUnit, bool precaching) {
CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(textUnit);
if (cachedGlyph) {
// Is the glyph still in texture cache?
if (!cachedGlyph->mIsValid) {
- SkAutoGlyphCache autoCache(*paint, NULL, &mDescription.mLookupTransform);
+ SkAutoGlyphCache autoCache(*paint, &mDeviceProperties, &mDescription.mLookupTransform);
const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), textUnit);
updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), cachedGlyph, precaching);
}
@@ -294,14 +296,14 @@ CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool pre
return cachedGlyph;
}
-void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y, const float* positions) {
render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL,
0, 0, NULL, positions);
}
-void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
- int numGlyphs, SkPath* path, float hOffset, float vOffset) {
+void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ int numGlyphs, const SkPath* path, float hOffset, float vOffset) {
if (numGlyphs == 0 || text == NULL || len == 0) {
return;
}
@@ -350,7 +352,7 @@ void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len
}
}
-void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, Rect *bounds, const float* positions) {
if (bounds == NULL) {
ALOGE("No return rectangle provided to measure text");
@@ -360,7 +362,7 @@ void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t le
render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds, positions);
}
-void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
+void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) {
ATRACE_NAME("precacheText");
if (numGlyphs == 0 || text == NULL) {
@@ -381,7 +383,7 @@ void Font::precache(SkPaint* paint, const char* text, int numGlyphs) {
}
}
-void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
if (numGlyphs == 0 || text == NULL || len == 0) {
@@ -427,8 +429,8 @@ void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len
}
}
-void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, SkGlyphCache* skiaGlyphCache,
- CachedGlyphInfo* glyph, bool precaching) {
+void Font::updateGlyphCache(const SkPaint* paint, const SkGlyph& skiaGlyph,
+ SkGlyphCache* skiaGlyphCache, CachedGlyphInfo* glyph, bool precaching) {
glyph->mAdvanceX = skiaGlyph.fAdvanceX;
glyph->mAdvanceY = skiaGlyph.fAdvanceY;
glyph->mBitmapLeft = skiaGlyph.fLeft;
@@ -471,11 +473,11 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, SkGlyphCac
}
}
-CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching) {
+CachedGlyphInfo* Font::cacheGlyph(const SkPaint* paint, glyph_t glyph, bool precaching) {
CachedGlyphInfo* newGlyph = new CachedGlyphInfo();
mCachedGlyphs.add(glyph, newGlyph);
- SkAutoGlyphCache autoCache(*paint, NULL, &mDescription.mLookupTransform);
+ SkAutoGlyphCache autoCache(*paint, &mDeviceProperties, &mDescription.mLookupTransform);
const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), glyph);
newGlyph->mIsValid = false;
newGlyph->mGlyphIndex = skiaGlyph.fID;
@@ -485,7 +487,7 @@ CachedGlyphInfo* Font::cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching
return newGlyph;
}
-Font* Font::create(FontRenderer* state, const SkPaint* paint, const mat4& matrix) {
+Font* Font::create(FontRenderer* state, const SkPaint* paint, const SkMatrix& matrix) {
FontDescription description(paint, matrix);
Font* font = state->mActiveFonts.get(description);
diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h
index f68b430..0f10464 100644
--- a/libs/hwui/font/Font.h
+++ b/libs/hwui/font/Font.h
@@ -17,8 +17,12 @@
#ifndef ANDROID_HWUI_FONT_H
#define ANDROID_HWUI_FONT_H
+#include <vector>
+
#include <utils/KeyedVector.h>
+#include <SkScalar.h>
+#include <SkDeviceProperties.h>
#include <SkGlyphCache.h>
#include <SkScalerContext.h>
#include <SkPaint.h>
@@ -48,7 +52,7 @@ public:
};
struct FontDescription {
- FontDescription(const SkPaint* paint, const mat4& matrix);
+ FontDescription(const SkPaint* paint, const SkMatrix& matrix);
static int compare(const FontDescription& lhs, const FontDescription& rhs);
@@ -77,11 +81,11 @@ public:
~Font();
- void render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y, const float* positions);
- void render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
- int numGlyphs, SkPath* path, float hOffset, float vOffset);
+ void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ int numGlyphs, const SkPath* path, float hOffset, float vOffset);
const Font::FontDescription& getDescription() const {
return mDescription;
@@ -90,7 +94,7 @@ public:
/**
* Creates a new font associated with the specified font state.
*/
- static Font* create(FontRenderer* state, const SkPaint* paint, const mat4& matrix);
+ static Font* create(FontRenderer* state, const SkPaint* paint, const SkMatrix& matrix);
private:
friend class FontRenderer;
@@ -106,20 +110,20 @@ private:
MEASURE,
};
- void precache(SkPaint* paint, const char* text, int numGlyphs);
+ void precache(const SkPaint* paint, const char* text, int numGlyphs);
- void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+ void render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions);
- void measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
+ void measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, Rect *bounds, const float* positions);
void invalidateTextureCache(CacheTexture* cacheTexture = NULL);
- CachedGlyphInfo* cacheGlyph(SkPaint* paint, glyph_t glyph, bool precaching);
- void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, SkGlyphCache* skiaGlyphCache,
- CachedGlyphInfo* glyph, bool precaching);
+ CachedGlyphInfo* cacheGlyph(const SkPaint* paint, glyph_t glyph, bool precaching);
+ void updateGlyphCache(const SkPaint* paint, const SkGlyph& skiaGlyph,
+ SkGlyphCache* skiaGlyphCache, CachedGlyphInfo* glyph, bool precaching);
void measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y,
uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
@@ -136,7 +140,8 @@ private:
void drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
SkPathMeasure& measure, SkPoint* position, SkVector* tangent);
- CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching = false);
+ CachedGlyphInfo* getCachedGlyph(const SkPaint* paint, glyph_t textUnit,
+ bool precaching = false);
FontRenderer* mState;
FontDescription mDescription;
@@ -145,6 +150,7 @@ private:
DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs;
bool mIdentityTransform;
+ SkDeviceProperties mDeviceProperties;
};
inline int strictly_order_type(const Font::FontDescription& lhs,
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
new file mode 100644
index 0000000..e673b0d
--- /dev/null
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2014 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 "CanvasContext.h"
+
+#include <private/hwui/DrawGlInfo.h>
+#include <strings.h>
+
+#include "EglManager.h"
+#include "RenderThread.h"
+#include "../Caches.h"
+#include "../DeferredLayerUpdater.h"
+#include "../RenderState.h"
+#include "../LayerRenderer.h"
+#include "../OpenGLRenderer.h"
+#include "../Stencil.h"
+
+#define TRIM_MEMORY_COMPLETE 80
+#define TRIM_MEMORY_UI_HIDDEN 20
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode)
+ : mRenderThread(thread)
+ , mEglManager(thread.eglManager())
+ , mEglSurface(EGL_NO_SURFACE)
+ , mDirtyRegionsEnabled(false)
+ , mOpaque(!translucent)
+ , mCanvas(NULL)
+ , mHaveNewSurface(false)
+ , mRootRenderNode(rootRenderNode) {
+}
+
+CanvasContext::~CanvasContext() {
+ destroyCanvasAndSurface();
+ mRenderThread.removeFrameCallback(this);
+}
+
+void CanvasContext::destroyCanvasAndSurface() {
+ if (mCanvas) {
+ delete mCanvas;
+ mCanvas = 0;
+ }
+ setSurface(NULL);
+}
+
+void CanvasContext::setSurface(ANativeWindow* window) {
+ mNativeWindow = window;
+
+ if (mEglSurface != EGL_NO_SURFACE) {
+ mEglManager.destroySurface(mEglSurface);
+ mEglSurface = EGL_NO_SURFACE;
+ }
+
+ if (window) {
+ mEglSurface = mEglManager.createSurface(window);
+ }
+
+ if (mEglSurface != EGL_NO_SURFACE) {
+ mDirtyRegionsEnabled = mEglManager.enableDirtyRegions(mEglSurface);
+ mHaveNewSurface = true;
+ makeCurrent();
+ } else {
+ mRenderThread.removeFrameCallback(this);
+ }
+}
+
+void CanvasContext::swapBuffers() {
+ mEglManager.swapBuffers(mEglSurface);
+ mHaveNewSurface = false;
+}
+
+void CanvasContext::requireSurface() {
+ LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE,
+ "requireSurface() called but no surface set!");
+ makeCurrent();
+}
+
+bool CanvasContext::initialize(ANativeWindow* window) {
+ if (mCanvas) return false;
+ setSurface(window);
+ mCanvas = new OpenGLRenderer(mRenderThread.renderState());
+ mCanvas->initProperties();
+ return true;
+}
+
+void CanvasContext::updateSurface(ANativeWindow* window) {
+ setSurface(window);
+}
+
+void CanvasContext::pauseSurface(ANativeWindow* window) {
+ // TODO: For now we just need a fence, in the future suspend any animations
+ // and such to prevent from trying to render into this surface
+}
+
+void CanvasContext::setup(int width, int height, const Vector3& lightCenter, float lightRadius,
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+ if (!mCanvas) return;
+ mCanvas->setViewport(width, height);
+ mCanvas->initLight(lightCenter, lightRadius, ambientShadowAlpha, spotShadowAlpha);
+}
+
+void CanvasContext::setOpaque(bool opaque) {
+ mOpaque = opaque;
+}
+
+void CanvasContext::makeCurrent() {
+ // TODO: Figure out why this workaround is needed, see b/13913604
+ // In the meantime this matches the behavior of GLRenderer, so it is not a regression
+ mHaveNewSurface |= mEglManager.makeCurrent(mEglSurface);
+}
+
+void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
+ bool success = layerUpdater->apply();
+ LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!");
+ if (layerUpdater->backingLayer()->deferredUpdateScheduled) {
+ mCanvas->pushLayerUpdate(layerUpdater->backingLayer());
+ }
+}
+
+void CanvasContext::prepareTree(TreeInfo& info) {
+ mRenderThread.removeFrameCallback(this);
+
+ info.frameTimeMs = mRenderThread.timeLord().frameTimeMs();
+ info.damageAccumulator = &mDamageAccumulator;
+ info.renderer = mCanvas;
+ mRootRenderNode->prepareTree(info);
+
+ int runningBehind = 0;
+ // TODO: This query is moderately expensive, investigate adding some sort
+ // of fast-path based off when we last called eglSwapBuffers() as well as
+ // last vsync time. Or something.
+ mNativeWindow->query(mNativeWindow.get(),
+ NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
+ info.out.canDrawThisFrame = !runningBehind;
+
+ if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
+ if (!info.out.requiresUiRedraw) {
+ // If animationsNeedsRedraw is set don't bother posting for an RT anim
+ // as we will just end up fighting the UI thread.
+ mRenderThread.postFrameCallback(this);
+ }
+ }
+}
+
+void CanvasContext::stopDrawing() {
+ mRenderThread.removeFrameCallback(this);
+}
+
+void CanvasContext::notifyFramePending() {
+ ATRACE_CALL();
+ mRenderThread.pushBackFrameCallback(this);
+}
+
+void CanvasContext::draw() {
+ LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
+ "drawRenderNode called on a context with no canvas or surface!");
+
+ profiler().markPlaybackStart();
+
+ SkRect dirty;
+ mDamageAccumulator.finish(&dirty);
+
+ EGLint width, height;
+ mEglManager.beginFrame(mEglSurface, &width, &height);
+ if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) {
+ mCanvas->setViewport(width, height);
+ dirty.setEmpty();
+ } else if (!mDirtyRegionsEnabled || mHaveNewSurface) {
+ dirty.setEmpty();
+ } else {
+ if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) {
+ ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?",
+ SK_RECT_ARGS(dirty), width, height);
+ dirty.setEmpty();
+ }
+ profiler().unionDirty(&dirty);
+ }
+
+ status_t status;
+ if (!dirty.isEmpty()) {
+ status = mCanvas->prepareDirty(dirty.fLeft, dirty.fTop,
+ dirty.fRight, dirty.fBottom, mOpaque);
+ } else {
+ status = mCanvas->prepare(mOpaque);
+ }
+
+ Rect outBounds;
+ status |= mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds);
+
+ profiler().draw(mCanvas);
+
+ mCanvas->finish();
+
+ profiler().markPlaybackEnd();
+
+ if (status & DrawGlInfo::kStatusDrew) {
+ swapBuffers();
+ }
+
+ profiler().finishFrame();
+}
+
+// Called by choreographer to do an RT-driven animation
+void CanvasContext::doFrame() {
+ if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) {
+ return;
+ }
+
+ ATRACE_CALL();
+
+ profiler().startFrame();
+
+ TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
+ prepareTree(info);
+ if (info.out.canDrawThisFrame) {
+ draw();
+ }
+}
+
+void CanvasContext::invokeFunctor(RenderThread& thread, Functor* functor) {
+ ATRACE_CALL();
+ DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
+ if (thread.eglManager().hasEglContext()) {
+ thread.eglManager().requireGlContext();
+ mode = DrawGlInfo::kModeProcess;
+ }
+
+ thread.renderState().invokeFunctor(functor, mode, NULL);
+}
+
+bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
+ requireGlContext();
+ layer->apply();
+ return LayerRenderer::copyLayer(mRenderThread.renderState(), layer->backingLayer(), bitmap);
+}
+
+void CanvasContext::destroyHardwareResources() {
+ stopDrawing();
+ if (mEglManager.hasEglContext()) {
+ requireGlContext();
+ mRootRenderNode->destroyHardwareResources();
+ Caches::getInstance().flush(Caches::kFlushMode_Layers);
+ }
+}
+
+void CanvasContext::trimMemory(RenderThread& thread, int level) {
+ // No context means nothing to free
+ if (!thread.eglManager().hasEglContext()) return;
+
+ thread.eglManager().requireGlContext();
+ if (level >= TRIM_MEMORY_COMPLETE) {
+ Caches::getInstance().flush(Caches::kFlushMode_Full);
+ thread.eglManager().destroy();
+ } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
+ Caches::getInstance().flush(Caches::kFlushMode_Moderate);
+ }
+}
+
+void CanvasContext::runWithGlContext(RenderTask* task) {
+ requireGlContext();
+ task->run();
+}
+
+Layer* CanvasContext::createRenderLayer(int width, int height) {
+ requireSurface();
+ return LayerRenderer::createRenderLayer(mRenderThread.renderState(), width, height);
+}
+
+Layer* CanvasContext::createTextureLayer() {
+ requireSurface();
+ return LayerRenderer::createTextureLayer(mRenderThread.renderState());
+}
+
+void CanvasContext::requireGlContext() {
+ mEglManager.requireGlContext();
+}
+
+void CanvasContext::setTextureAtlas(RenderThread& thread,
+ const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize) {
+ thread.eglManager().setTextureAtlas(buffer, map, mapSize);
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
new file mode 100644
index 0000000..2a01027
--- /dev/null
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CANVASCONTEXT_H_
+#define CANVASCONTEXT_H_
+
+#include <cutils/compiler.h>
+#include <EGL/egl.h>
+#include <SkBitmap.h>
+#include <utils/Functor.h>
+#include <utils/Vector.h>
+
+#include "../DamageAccumulator.h"
+#include "../DrawProfiler.h"
+#include "../RenderNode.h"
+#include "RenderTask.h"
+#include "RenderThread.h"
+
+#define FUNCTOR_PROCESS_DELAY 4
+
+namespace android {
+namespace uirenderer {
+
+class DeferredLayerUpdater;
+class OpenGLRenderer;
+class Rect;
+class Layer;
+
+namespace renderthread {
+
+class EglManager;
+
+// This per-renderer class manages the bridge between the global EGL context
+// and the render surface.
+class CanvasContext : public IFrameCallback {
+public:
+ CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode);
+ virtual ~CanvasContext();
+
+ bool initialize(ANativeWindow* window);
+ void updateSurface(ANativeWindow* window);
+ void pauseSurface(ANativeWindow* window);
+ void setup(int width, int height, const Vector3& lightCenter, float lightRadius,
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
+ void setOpaque(bool opaque);
+ void makeCurrent();
+ void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
+ void prepareTree(TreeInfo& info);
+ void draw();
+ void destroyCanvasAndSurface();
+
+ // IFrameCallback, Chroreographer-driven frame callback entry point
+ virtual void doFrame();
+
+ bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
+
+ void destroyHardwareResources();
+ static void trimMemory(RenderThread& thread, int level);
+
+ static void invokeFunctor(RenderThread& thread, Functor* functor);
+
+ void runWithGlContext(RenderTask* task);
+
+ Layer* createRenderLayer(int width, int height);
+ Layer* createTextureLayer();
+
+ ANDROID_API static void setTextureAtlas(RenderThread& thread,
+ const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
+
+ void stopDrawing();
+ void notifyFramePending();
+
+ DrawProfiler& profiler() { return mProfiler; }
+
+private:
+ friend class RegisterFrameCallbackTask;
+
+ void setSurface(ANativeWindow* window);
+ void swapBuffers();
+ void requireSurface();
+
+ void requireGlContext();
+
+ RenderThread& mRenderThread;
+ EglManager& mEglManager;
+ sp<ANativeWindow> mNativeWindow;
+ EGLSurface mEglSurface;
+ bool mDirtyRegionsEnabled;
+
+ bool mOpaque;
+ OpenGLRenderer* mCanvas;
+ bool mHaveNewSurface;
+ DamageAccumulator mDamageAccumulator;
+
+ const sp<RenderNode> mRootRenderNode;
+
+ DrawProfiler mProfiler;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+#endif /* CANVASCONTEXT_H_ */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
new file mode 100644
index 0000000..dd34e09
--- /dev/null
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_VIEW
+
+#include "DrawFrameTask.h"
+
+#include <utils/Log.h>
+#include <utils/Trace.h>
+
+#include "../DeferredLayerUpdater.h"
+#include "../DisplayList.h"
+#include "../RenderNode.h"
+#include "CanvasContext.h"
+#include "RenderThread.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+DrawFrameTask::DrawFrameTask()
+ : mRenderThread(NULL)
+ , mContext(NULL)
+ , mFrameTimeNanos(0)
+ , mRecordDurationNanos(0)
+ , mDensity(1.0f) // safe enough default
+ , mSyncResult(kSync_OK) {
+}
+
+DrawFrameTask::~DrawFrameTask() {
+}
+
+void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context) {
+ mRenderThread = thread;
+ mContext = context;
+}
+
+void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
+ LOG_ALWAYS_FATAL_IF(!mContext, "Lifecycle violation, there's no context to pushLayerUpdate with!");
+
+ for (size_t i = 0; i < mLayers.size(); i++) {
+ if (mLayers[i].get() == layer) {
+ return;
+ }
+ }
+ mLayers.push_back(layer);
+}
+
+void DrawFrameTask::removeLayerUpdate(DeferredLayerUpdater* layer) {
+ for (size_t i = 0; i < mLayers.size(); i++) {
+ if (mLayers[i].get() == layer) {
+ mLayers.erase(mLayers.begin() + i);
+ return;
+ }
+ }
+}
+
+int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) {
+ LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
+
+ mSyncResult = kSync_OK;
+ mFrameTimeNanos = frameTimeNanos;
+ mRecordDurationNanos = recordDurationNanos;
+ postAndWait();
+
+ // Reset the single-frame data
+ mFrameTimeNanos = 0;
+ mRecordDurationNanos = 0;
+
+ return mSyncResult;
+}
+
+void DrawFrameTask::postAndWait() {
+ AutoMutex _lock(mLock);
+ mRenderThread->queue(this);
+ mSignal.wait(mLock);
+}
+
+void DrawFrameTask::run() {
+ ATRACE_NAME("DrawFrame");
+
+ mContext->profiler().setDensity(mDensity);
+ mContext->profiler().startFrame(mRecordDurationNanos);
+
+ bool canUnblockUiThread;
+ bool canDrawThisFrame;
+ {
+ TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState());
+ canUnblockUiThread = syncFrameState(info);
+ canDrawThisFrame = info.out.canDrawThisFrame;
+ }
+
+ // Grab a copy of everything we need
+ CanvasContext* context = mContext;
+
+ // From this point on anything in "this" is *UNSAFE TO ACCESS*
+ if (canUnblockUiThread) {
+ unblockUiThread();
+ }
+
+ if (CC_LIKELY(canDrawThisFrame)) {
+ context->draw();
+ }
+
+ if (!canUnblockUiThread) {
+ unblockUiThread();
+ }
+}
+
+bool DrawFrameTask::syncFrameState(TreeInfo& info) {
+ ATRACE_CALL();
+ mRenderThread->timeLord().vsyncReceived(mFrameTimeNanos);
+ mContext->makeCurrent();
+ Caches::getInstance().textureCache.resetMarkInUse();
+
+ for (size_t i = 0; i < mLayers.size(); i++) {
+ mContext->processLayerUpdate(mLayers[i].get());
+ }
+ mLayers.clear();
+ mContext->prepareTree(info);
+
+ if (info.out.hasAnimations) {
+ if (info.out.requiresUiRedraw) {
+ mSyncResult |= kSync_UIRedrawRequired;
+ }
+ }
+ // If prepareTextures is false, we ran out of texture cache space
+ return info.prepareTextures;
+}
+
+void DrawFrameTask::unblockUiThread() {
+ AutoMutex _lock(mLock);
+ mSignal.signal();
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
new file mode 100644
index 0000000..243cc5d
--- /dev/null
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef DRAWFRAMETASK_H
+#define DRAWFRAMETASK_H
+
+#include <vector>
+
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
+#include <utils/StrongPointer.h>
+
+#include "RenderTask.h"
+
+#include "../Rect.h"
+#include "../TreeInfo.h"
+
+namespace android {
+namespace uirenderer {
+
+class DeferredLayerUpdater;
+class DisplayListData;
+class RenderNode;
+
+namespace renderthread {
+
+class CanvasContext;
+class RenderThread;
+
+enum SyncResult {
+ kSync_OK = 0,
+ kSync_UIRedrawRequired = 1 << 0,
+};
+
+/*
+ * This is a special Super Task. It is re-used multiple times by RenderProxy,
+ * and contains state (such as layer updaters & new DisplayListDatas) that is
+ * tracked across many frames not just a single frame.
+ * It is the sync-state task, and will kick off the post-sync draw
+ */
+class DrawFrameTask : public RenderTask {
+public:
+ DrawFrameTask();
+ virtual ~DrawFrameTask();
+
+ void setContext(RenderThread* thread, CanvasContext* context);
+
+ void pushLayerUpdate(DeferredLayerUpdater* layer);
+ void removeLayerUpdate(DeferredLayerUpdater* layer);
+
+ void setDensity(float density) { mDensity = density; }
+ int drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos);
+
+ virtual void run();
+
+private:
+ void postAndWait();
+ bool syncFrameState(TreeInfo& info);
+ void unblockUiThread();
+
+ Mutex mLock;
+ Condition mSignal;
+
+ RenderThread* mRenderThread;
+ CanvasContext* mContext;
+
+ /*********************************************
+ * Single frame data
+ *********************************************/
+ nsecs_t mFrameTimeNanos;
+ nsecs_t mRecordDurationNanos;
+ float mDensity;
+ std::vector< sp<DeferredLayerUpdater> > mLayers;
+
+ int mSyncResult;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* DRAWFRAMETASK_H */
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
new file mode 100644
index 0000000..05ca34d
--- /dev/null
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#define LOG_TAG "EglContext"
+
+#include "EglManager.h"
+
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include "../RenderState.h"
+#include "RenderThread.h"
+
+#define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions"
+#define GLES_VERSION 2
+
+// Android-specific addition that is used to show when frames began in systrace
+EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+#define ERROR_CASE(x) case x: return #x;
+static const char* egl_error_str(EGLint error) {
+ switch (error) {
+ ERROR_CASE(EGL_SUCCESS)
+ ERROR_CASE(EGL_NOT_INITIALIZED)
+ ERROR_CASE(EGL_BAD_ACCESS)
+ ERROR_CASE(EGL_BAD_ALLOC)
+ ERROR_CASE(EGL_BAD_ATTRIBUTE)
+ ERROR_CASE(EGL_BAD_CONFIG)
+ ERROR_CASE(EGL_BAD_CONTEXT)
+ ERROR_CASE(EGL_BAD_CURRENT_SURFACE)
+ ERROR_CASE(EGL_BAD_DISPLAY)
+ ERROR_CASE(EGL_BAD_MATCH)
+ ERROR_CASE(EGL_BAD_NATIVE_PIXMAP)
+ ERROR_CASE(EGL_BAD_NATIVE_WINDOW)
+ ERROR_CASE(EGL_BAD_PARAMETER)
+ ERROR_CASE(EGL_BAD_SURFACE)
+ ERROR_CASE(EGL_CONTEXT_LOST)
+ default:
+ return "Unknown error";
+ }
+}
+static const char* egl_error_str() {
+ return egl_error_str(eglGetError());
+}
+
+static bool load_dirty_regions_property() {
+ char buf[PROPERTY_VALUE_MAX];
+ int len = property_get(PROPERTY_RENDER_DIRTY_REGIONS, buf, "true");
+ return !strncasecmp("true", buf, len);
+}
+
+EglManager::EglManager(RenderThread& thread)
+ : mRenderThread(thread)
+ , mEglDisplay(EGL_NO_DISPLAY)
+ , mEglConfig(0)
+ , mEglContext(EGL_NO_CONTEXT)
+ , mPBufferSurface(EGL_NO_SURFACE)
+ , mRequestDirtyRegions(load_dirty_regions_property())
+ , mCurrentSurface(EGL_NO_SURFACE)
+ , mAtlasMap(NULL)
+ , mAtlasMapSize(0) {
+ mCanSetDirtyRegions = mRequestDirtyRegions;
+ ALOGD("Render dirty regions requested: %s", mRequestDirtyRegions ? "true" : "false");
+}
+
+void EglManager::initialize() {
+ if (hasEglContext()) return;
+
+ mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
+ "Failed to get EGL_DEFAULT_DISPLAY! err=%s", egl_error_str());
+
+ EGLint major, minor;
+ LOG_ALWAYS_FATAL_IF(eglInitialize(mEglDisplay, &major, &minor) == EGL_FALSE,
+ "Failed to initialize display %p! err=%s", mEglDisplay, egl_error_str());
+
+ ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor);
+
+ loadConfig();
+ createContext();
+ usePBufferSurface();
+ mRenderThread.renderState().onGLContextCreated();
+ initAtlas();
+}
+
+bool EglManager::hasEglContext() {
+ return mEglDisplay != EGL_NO_DISPLAY;
+}
+
+void EglManager::requireGlContext() {
+ LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY, "No EGL context");
+
+ // We don't care *WHAT* surface is active, just that one is active to give
+ // us access to the GL context
+ if (mCurrentSurface == EGL_NO_SURFACE) {
+ usePBufferSurface();
+ }
+}
+
+void EglManager::loadConfig() {
+ EGLint swapBehavior = mCanSetDirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+ EGLint attribs[] = {
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_DEPTH_SIZE, 0,
+ EGL_CONFIG_CAVEAT, EGL_NONE,
+ EGL_STENCIL_SIZE, Stencil::getStencilSize(),
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
+ EGL_NONE
+ };
+
+ EGLint num_configs = 1;
+ if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs)
+ || num_configs != 1) {
+ // Failed to get a valid config
+ if (mCanSetDirtyRegions) {
+ ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...");
+ // Try again without dirty regions enabled
+ mCanSetDirtyRegions = false;
+ loadConfig();
+ } else {
+ LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str());
+ }
+ }
+}
+
+void EglManager::createContext() {
+ EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, EGL_NONE };
+ mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs);
+ LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT,
+ "Failed to create context, error = %s", egl_error_str());
+}
+
+void EglManager::setTextureAtlas(const sp<GraphicBuffer>& buffer,
+ int64_t* map, size_t mapSize) {
+
+ // Already initialized
+ if (mAtlasBuffer.get()) {
+ ALOGW("Multiple calls to setTextureAtlas!");
+ delete map;
+ return;
+ }
+
+ mAtlasBuffer = buffer;
+ mAtlasMap = map;
+ mAtlasMapSize = mapSize;
+
+ if (hasEglContext()) {
+ usePBufferSurface();
+ initAtlas();
+ }
+}
+
+void EglManager::initAtlas() {
+ if (mAtlasBuffer.get()) {
+ Caches::getInstance().assetAtlas.init(mAtlasBuffer, mAtlasMap, mAtlasMapSize);
+ }
+}
+
+void EglManager::usePBufferSurface() {
+ LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY,
+ "usePBufferSurface() called on uninitialized GlobalContext!");
+
+ if (mPBufferSurface == EGL_NO_SURFACE) {
+ EGLint attribs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE };
+ mPBufferSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs);
+ }
+ makeCurrent(mPBufferSurface);
+}
+
+EGLSurface EglManager::createSurface(EGLNativeWindowType window) {
+ initialize();
+ EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, NULL);
+ LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
+ "Failed to create EGLSurface for window %p, eglErr = %s",
+ (void*) window, egl_error_str());
+ return surface;
+}
+
+void EglManager::destroySurface(EGLSurface surface) {
+ if (isCurrent(surface)) {
+ makeCurrent(EGL_NO_SURFACE);
+ }
+ if (!eglDestroySurface(mEglDisplay, surface)) {
+ ALOGW("Failed to destroy surface %p, error=%s", (void*)surface, egl_error_str());
+ }
+}
+
+void EglManager::destroy() {
+ if (mEglDisplay == EGL_NO_DISPLAY) return;
+
+ usePBufferSurface();
+ if (Caches::hasInstance()) {
+ Caches::getInstance().terminate();
+ }
+
+ eglDestroyContext(mEglDisplay, mEglContext);
+ eglDestroySurface(mEglDisplay, mPBufferSurface);
+ eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ eglTerminate(mEglDisplay);
+ eglReleaseThread();
+
+ mEglDisplay = EGL_NO_DISPLAY;
+ mEglContext = EGL_NO_CONTEXT;
+ mPBufferSurface = EGL_NO_SURFACE;
+ mCurrentSurface = EGL_NO_SURFACE;
+}
+
+bool EglManager::makeCurrent(EGLSurface surface) {
+ if (isCurrent(surface)) return false;
+
+ if (surface == EGL_NO_SURFACE) {
+ // If we are setting EGL_NO_SURFACE we don't care about any of the potential
+ // return errors, which would only happen if mEglDisplay had already been
+ // destroyed in which case the current context is already NO_CONTEXT
+ eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ } else if (!eglMakeCurrent(mEglDisplay, surface, surface, mEglContext)) {
+ LOG_ALWAYS_FATAL("Failed to make current on surface %p, error=%s",
+ (void*)surface, egl_error_str());
+ }
+ mCurrentSurface = surface;
+ return true;
+}
+
+void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) {
+ LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
+ "Tried to beginFrame on EGL_NO_SURFACE!");
+ makeCurrent(surface);
+ if (width) {
+ eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width);
+ }
+ if (height) {
+ eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height);
+ }
+ eglBeginFrame(mEglDisplay, surface);
+}
+
+void EglManager::swapBuffers(EGLSurface surface) {
+ eglSwapBuffers(mEglDisplay, surface);
+ EGLint err = eglGetError();
+ LOG_ALWAYS_FATAL_IF(err != EGL_SUCCESS,
+ "Encountered EGL error %d %s during rendering", err, egl_error_str(err));
+}
+
+bool EglManager::enableDirtyRegions(EGLSurface surface) {
+ if (!mRequestDirtyRegions) return false;
+
+ if (mCanSetDirtyRegions) {
+ if (!eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED)) {
+ ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s",
+ (void*) surface, egl_error_str());
+ return false;
+ }
+ return true;
+ }
+ // Perhaps it is already enabled?
+ EGLint value;
+ if (!eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &value)) {
+ ALOGW("Failed to query EGL_SWAP_BEHAVIOR on surface %p, error=%p",
+ (void*) surface, egl_error_str());
+ return false;
+ }
+ return value == EGL_BUFFER_PRESERVED;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
new file mode 100644
index 0000000..a844cfc
--- /dev/null
+++ b/libs/hwui/renderthread/EglManager.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef EGLMANAGER_H
+#define EGLMANAGER_H
+
+#include <cutils/compiler.h>
+#include <EGL/egl.h>
+#include <ui/GraphicBuffer.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+class RenderThread;
+
+// This class contains the shared global EGL objects, such as EGLDisplay
+// and EGLConfig, which are re-used by CanvasContext
+class EglManager {
+public:
+ // Returns true on success, false on failure
+ void initialize();
+
+ bool hasEglContext();
+ void requireGlContext();
+
+ void usePBufferSurface();
+ EGLSurface createSurface(EGLNativeWindowType window);
+ void destroySurface(EGLSurface surface);
+
+ void destroy();
+
+ bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; }
+ // Returns true if the current surface changed, false if it was already current
+ bool makeCurrent(EGLSurface surface);
+ void beginFrame(EGLSurface surface, EGLint* width, EGLint* height);
+ void swapBuffers(EGLSurface surface);
+
+ bool enableDirtyRegions(EGLSurface surface);
+
+ void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize);
+
+private:
+ friend class RenderThread;
+
+ EglManager(RenderThread& thread);
+ // EglContext is never destroyed, method is purposely not implemented
+ ~EglManager();
+
+ void loadConfig();
+ void createContext();
+ void initAtlas();
+
+ RenderThread& mRenderThread;
+
+ EGLDisplay mEglDisplay;
+ EGLConfig mEglConfig;
+ EGLContext mEglContext;
+ EGLSurface mPBufferSurface;
+
+ const bool mRequestDirtyRegions;
+ bool mCanSetDirtyRegions;
+
+ EGLSurface mCurrentSurface;
+
+ sp<GraphicBuffer> mAtlasBuffer;
+ int64_t* mAtlasMap;
+ size_t mAtlasMapSize;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* EGLMANAGER_H */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
new file mode 100644
index 0000000..d9b96f6
--- /dev/null
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2013 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 "RenderProxy.h"
+
+#include "CanvasContext.h"
+#include "RenderTask.h"
+#include "RenderThread.h"
+
+#include "../DeferredLayerUpdater.h"
+#include "../DisplayList.h"
+#include "../LayerRenderer.h"
+#include "../Rect.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+#define ARGS(method) method ## Args
+
+#define CREATE_BRIDGE0(name) CREATE_BRIDGE(name,,,,,,,,)
+#define CREATE_BRIDGE1(name, a1) CREATE_BRIDGE(name, a1,,,,,,,)
+#define CREATE_BRIDGE2(name, a1, a2) CREATE_BRIDGE(name, a1,a2,,,,,,)
+#define CREATE_BRIDGE3(name, a1, a2, a3) CREATE_BRIDGE(name, a1,a2,a3,,,,,)
+#define CREATE_BRIDGE4(name, a1, a2, a3, a4) CREATE_BRIDGE(name, a1,a2,a3,a4,,,,)
+#define CREATE_BRIDGE5(name, a1, a2, a3, a4, a5) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,,,)
+#define CREATE_BRIDGE6(name, a1, a2, a3, a4, a5, a6) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,a6,,)
+#define CREATE_BRIDGE7(name, a1, a2, a3, a4, a5, a6, a7) CREATE_BRIDGE(name, a1,a2,a3,a4,a5,a6,a7,)
+#define CREATE_BRIDGE(name, a1, a2, a3, a4, a5, a6, a7, a8) \
+ typedef struct { \
+ a1; a2; a3; a4; a5; a6; a7; a8; \
+ } ARGS(name); \
+ static void* Bridge_ ## name(ARGS(name)* args)
+
+#define SETUP_TASK(method) \
+ LOG_ALWAYS_FATAL_IF( METHOD_INVOKE_PAYLOAD_SIZE < sizeof(ARGS(method)), \
+ "METHOD_INVOKE_PAYLOAD_SIZE %zu is smaller than sizeof(" #method "Args) %zu", \
+ METHOD_INVOKE_PAYLOAD_SIZE, sizeof(ARGS(method))); \
+ MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
+ ARGS(method) *args = (ARGS(method) *) task->payload()
+
+CREATE_BRIDGE3(createContext, RenderThread* thread, bool translucent, RenderNode* rootRenderNode) {
+ return new CanvasContext(*args->thread, args->translucent, args->rootRenderNode);
+}
+
+RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode)
+ : mRenderThread(RenderThread::getInstance())
+ , mContext(0) {
+ SETUP_TASK(createContext);
+ args->translucent = translucent;
+ args->rootRenderNode = rootRenderNode;
+ args->thread = &mRenderThread;
+ mContext = (CanvasContext*) postAndWait(task);
+ mDrawFrameTask.setContext(&mRenderThread, mContext);
+}
+
+RenderProxy::~RenderProxy() {
+ destroyContext();
+}
+
+CREATE_BRIDGE1(destroyContext, CanvasContext* context) {
+ delete args->context;
+ return NULL;
+}
+
+void RenderProxy::destroyContext() {
+ if (mContext) {
+ SETUP_TASK(destroyContext);
+ args->context = mContext;
+ mContext = 0;
+ mDrawFrameTask.setContext(NULL, NULL);
+ // This is also a fence as we need to be certain that there are no
+ // outstanding mDrawFrame tasks posted before it is destroyed
+ postAndWait(task);
+ }
+}
+
+CREATE_BRIDGE2(setFrameInterval, RenderThread* thread, nsecs_t frameIntervalNanos) {
+ args->thread->timeLord().setFrameInterval(args->frameIntervalNanos);
+ return NULL;
+}
+
+void RenderProxy::setFrameInterval(nsecs_t frameIntervalNanos) {
+ SETUP_TASK(setFrameInterval);
+ args->thread = &mRenderThread;
+ args->frameIntervalNanos = frameIntervalNanos;
+ post(task);
+}
+
+CREATE_BRIDGE1(loadSystemProperties, CanvasContext* context) {
+ bool needsRedraw = false;
+ if (Caches::hasInstance()) {
+ needsRedraw = Caches::getInstance().initProperties();
+ }
+ if (args->context->profiler().loadSystemProperties()) {
+ needsRedraw = true;
+ }
+ return (void*) needsRedraw;
+}
+
+bool RenderProxy::loadSystemProperties() {
+ SETUP_TASK(loadSystemProperties);
+ args->context = mContext;
+ return (bool) postAndWait(task);
+}
+
+CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) {
+ return (void*) args->context->initialize(args->window);
+}
+
+bool RenderProxy::initialize(const sp<ANativeWindow>& window) {
+ SETUP_TASK(initialize);
+ args->context = mContext;
+ args->window = window.get();
+ return (bool) postAndWait(task);
+}
+
+CREATE_BRIDGE2(updateSurface, CanvasContext* context, ANativeWindow* window) {
+ args->context->updateSurface(args->window);
+ return NULL;
+}
+
+void RenderProxy::updateSurface(const sp<ANativeWindow>& window) {
+ SETUP_TASK(updateSurface);
+ args->context = mContext;
+ args->window = window.get();
+ postAndWait(task);
+}
+
+CREATE_BRIDGE2(pauseSurface, CanvasContext* context, ANativeWindow* window) {
+ args->context->pauseSurface(args->window);
+ return NULL;
+}
+
+void RenderProxy::pauseSurface(const sp<ANativeWindow>& window) {
+ SETUP_TASK(pauseSurface);
+ args->context = mContext;
+ args->window = window.get();
+ postAndWait(task);
+}
+
+CREATE_BRIDGE7(setup, CanvasContext* context, int width, int height,
+ Vector3 lightCenter, float lightRadius,
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+ args->context->setup(args->width, args->height, args->lightCenter, args->lightRadius,
+ args->ambientShadowAlpha, args->spotShadowAlpha);
+ return NULL;
+}
+
+void RenderProxy::setup(int width, int height, const Vector3& lightCenter, float lightRadius,
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+ SETUP_TASK(setup);
+ args->context = mContext;
+ args->width = width;
+ args->height = height;
+ args->lightCenter = lightCenter;
+ args->lightRadius = lightRadius;
+ args->ambientShadowAlpha = ambientShadowAlpha;
+ args->spotShadowAlpha = spotShadowAlpha;
+ post(task);
+}
+
+CREATE_BRIDGE2(setOpaque, CanvasContext* context, bool opaque) {
+ args->context->setOpaque(args->opaque);
+ return NULL;
+}
+
+void RenderProxy::setOpaque(bool opaque) {
+ SETUP_TASK(setOpaque);
+ args->context = mContext;
+ args->opaque = opaque;
+ post(task);
+}
+
+int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
+ float density) {
+ mDrawFrameTask.setDensity(density);
+ return mDrawFrameTask.drawFrame(frameTimeNanos, recordDurationNanos);
+}
+
+CREATE_BRIDGE1(destroyCanvasAndSurface, CanvasContext* context) {
+ args->context->destroyCanvasAndSurface();
+ return NULL;
+}
+
+void RenderProxy::destroyCanvasAndSurface() {
+ SETUP_TASK(destroyCanvasAndSurface);
+ args->context = mContext;
+ // destroyCanvasAndSurface() needs a fence as when it returns the
+ // underlying BufferQueue is going to be released from under
+ // the render thread.
+ postAndWait(task);
+}
+
+CREATE_BRIDGE2(invokeFunctor, RenderThread* thread, Functor* functor) {
+ CanvasContext::invokeFunctor(*args->thread, args->functor);
+ return NULL;
+}
+
+void RenderProxy::invokeFunctor(Functor* functor, bool waitForCompletion) {
+ ATRACE_CALL();
+ RenderThread& thread = RenderThread::getInstance();
+ SETUP_TASK(invokeFunctor);
+ args->thread = &thread;
+ args->functor = functor;
+ if (waitForCompletion) {
+ // waitForCompletion = true is expected to be fairly rare and only
+ // happen in destruction. Thus it should be fine to temporarily
+ // create a Mutex
+ Mutex mutex;
+ Condition condition;
+ SignalingRenderTask syncTask(task, &mutex, &condition);
+ AutoMutex _lock(mutex);
+ thread.queue(&syncTask);
+ condition.wait(mutex);
+ } else {
+ thread.queue(task);
+ }
+}
+
+CREATE_BRIDGE2(runWithGlContext, CanvasContext* context, RenderTask* task) {
+ args->context->runWithGlContext(args->task);
+ return NULL;
+}
+
+void RenderProxy::runWithGlContext(RenderTask* gltask) {
+ SETUP_TASK(runWithGlContext);
+ args->context = mContext;
+ args->task = gltask;
+ postAndWait(task);
+}
+
+CREATE_BRIDGE1(destroyLayer, Layer* layer) {
+ LayerRenderer::destroyLayer(args->layer);
+ return NULL;
+}
+
+void RenderProxy::enqueueDestroyLayer(Layer* layer) {
+ SETUP_TASK(destroyLayer);
+ args->layer = layer;
+ RenderThread::getInstance().queue(task);
+}
+
+CREATE_BRIDGE3(createDisplayListLayer, CanvasContext* context, int width, int height) {
+ Layer* layer = args->context->createRenderLayer(args->width, args->height);
+ if (!layer) return 0;
+ return new DeferredLayerUpdater(layer, RenderProxy::enqueueDestroyLayer);
+}
+
+DeferredLayerUpdater* RenderProxy::createDisplayListLayer(int width, int height) {
+ SETUP_TASK(createDisplayListLayer);
+ args->width = width;
+ args->height = height;
+ args->context = mContext;
+ void* retval = postAndWait(task);
+ DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(retval);
+ return layer;
+}
+
+CREATE_BRIDGE1(createTextureLayer, CanvasContext* context) {
+ Layer* layer = args->context->createTextureLayer();
+ if (!layer) return 0;
+ return new DeferredLayerUpdater(layer, RenderProxy::enqueueDestroyLayer);
+}
+
+DeferredLayerUpdater* RenderProxy::createTextureLayer() {
+ SETUP_TASK(createTextureLayer);
+ args->context = mContext;
+ void* retval = postAndWait(task);
+ DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(retval);
+ return layer;
+}
+
+CREATE_BRIDGE3(copyLayerInto, CanvasContext* context, DeferredLayerUpdater* layer,
+ SkBitmap* bitmap) {
+ bool success = args->context->copyLayerInto(args->layer, args->bitmap);
+ return (void*) success;
+}
+
+bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
+ SETUP_TASK(copyLayerInto);
+ args->context = mContext;
+ args->layer = layer;
+ args->bitmap = bitmap;
+ return (bool) postAndWait(task);
+}
+
+void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) {
+ mDrawFrameTask.pushLayerUpdate(layer);
+}
+
+void RenderProxy::cancelLayerUpdate(DeferredLayerUpdater* layer) {
+ mDrawFrameTask.removeLayerUpdate(layer);
+}
+
+CREATE_BRIDGE1(detachSurfaceTexture, DeferredLayerUpdater* layer) {
+ args->layer->detachSurfaceTexture();
+ return NULL;
+}
+
+void RenderProxy::detachSurfaceTexture(DeferredLayerUpdater* layer) {
+ SETUP_TASK(detachSurfaceTexture);
+ args->layer = layer;
+ postAndWait(task);
+}
+
+CREATE_BRIDGE1(destroyHardwareResources, CanvasContext* context) {
+ args->context->destroyHardwareResources();
+ return NULL;
+}
+
+void RenderProxy::destroyHardwareResources() {
+ SETUP_TASK(destroyHardwareResources);
+ args->context = mContext;
+ post(task);
+}
+
+CREATE_BRIDGE2(timMemory, RenderThread* thread, int level) {
+ CanvasContext::trimMemory(*args->thread, args->level);
+ return NULL;
+}
+
+void RenderProxy::trimMemory(int level) {
+ // Avoid creating a RenderThread to do a trimMemory.
+ if (RenderThread::hasInstance()) {
+ RenderThread& thread = RenderThread::getInstance();
+ SETUP_TASK(timMemory);
+ args->thread = &thread;
+ args->level = level;
+ thread.queue(task);
+ }
+}
+
+CREATE_BRIDGE0(fence) {
+ // Intentionally empty
+ return NULL;
+}
+
+void RenderProxy::fence() {
+ SETUP_TASK(fence);
+ postAndWait(task);
+}
+
+CREATE_BRIDGE1(stopDrawing, CanvasContext* context) {
+ args->context->stopDrawing();
+ return NULL;
+}
+
+void RenderProxy::stopDrawing() {
+ SETUP_TASK(stopDrawing);
+ args->context = mContext;
+ postAndWait(task);
+}
+
+CREATE_BRIDGE1(notifyFramePending, CanvasContext* context) {
+ args->context->notifyFramePending();
+ return NULL;
+}
+
+void RenderProxy::notifyFramePending() {
+ SETUP_TASK(notifyFramePending);
+ args->context = mContext;
+ mRenderThread.queueAtFront(task);
+}
+
+CREATE_BRIDGE2(dumpProfileInfo, CanvasContext* context, int fd) {
+ args->context->profiler().dumpData(args->fd);
+ return NULL;
+}
+
+void RenderProxy::dumpProfileInfo(int fd) {
+ SETUP_TASK(dumpProfileInfo);
+ args->context = mContext;
+ args->fd = fd;
+ postAndWait(task);
+}
+
+CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, size_t size) {
+ CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size);
+ args->buffer->decStrong(0);
+ return NULL;
+}
+
+void RenderProxy::setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size) {
+ SETUP_TASK(setTextureAtlas);
+ args->thread = &mRenderThread;
+ args->buffer = buffer.get();
+ args->buffer->incStrong(0);
+ args->map = map;
+ args->size = size;
+ post(task);
+}
+
+void RenderProxy::post(RenderTask* task) {
+ mRenderThread.queue(task);
+}
+
+void* RenderProxy::postAndWait(MethodInvokeRenderTask* task) {
+ void* retval;
+ task->setReturnPtr(&retval);
+ SignalingRenderTask syncTask(task, &mSyncMutex, &mSyncCondition);
+ AutoMutex _lock(mSyncMutex);
+ mRenderThread.queue(&syncTask);
+ mSyncCondition.wait(mSyncMutex);
+ return retval;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
new file mode 100644
index 0000000..28d0173
--- /dev/null
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RENDERPROXY_H_
+#define RENDERPROXY_H_
+
+#include "RenderTask.h"
+
+#include <cutils/compiler.h>
+#include <EGL/egl.h>
+#include <SkBitmap.h>
+#include <utils/Condition.h>
+#include <utils/Functor.h>
+#include <utils/Mutex.h>
+#include <utils/Timers.h>
+#include <utils/StrongPointer.h>
+#include <utils/Vector.h>
+
+#include "../Caches.h"
+#include "DrawFrameTask.h"
+
+namespace android {
+namespace uirenderer {
+
+class DeferredLayerUpdater;
+class RenderNode;
+class DisplayListData;
+class Layer;
+class Rect;
+
+namespace renderthread {
+
+class CanvasContext;
+class ErrorChannel;
+class RenderThread;
+class RenderProxyBridge;
+
+/*
+ * RenderProxy is strictly single threaded. All methods must be invoked on the owning
+ * thread. It is important to note that RenderProxy may be deleted while it has
+ * tasks post()'d as a result. Therefore any RenderTask that is post()'d must not
+ * reference RenderProxy or any of its fields. The exception here is that postAndWait()
+ * references RenderProxy fields. This is safe as RenderProxy cannot
+ * be deleted if it is blocked inside a call.
+ */
+class ANDROID_API RenderProxy {
+public:
+ ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode);
+ ANDROID_API virtual ~RenderProxy();
+
+ ANDROID_API void setFrameInterval(nsecs_t frameIntervalNanos);
+ ANDROID_API bool loadSystemProperties();
+
+ ANDROID_API bool initialize(const sp<ANativeWindow>& window);
+ ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
+ ANDROID_API void pauseSurface(const sp<ANativeWindow>& window);
+ ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius,
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
+ ANDROID_API void setOpaque(bool opaque);
+ ANDROID_API int syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
+ float density);
+ ANDROID_API void destroyCanvasAndSurface();
+
+ ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion);
+
+ ANDROID_API void runWithGlContext(RenderTask* task);
+
+ static void enqueueDestroyLayer(Layer* layer);
+ ANDROID_API DeferredLayerUpdater* createDisplayListLayer(int width, int height);
+ ANDROID_API DeferredLayerUpdater* createTextureLayer();
+ ANDROID_API bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
+ ANDROID_API void pushLayerUpdate(DeferredLayerUpdater* layer);
+ ANDROID_API void cancelLayerUpdate(DeferredLayerUpdater* layer);
+ ANDROID_API void detachSurfaceTexture(DeferredLayerUpdater* layer);
+
+ ANDROID_API void destroyHardwareResources();
+ ANDROID_API static void trimMemory(int level);
+
+ ANDROID_API void fence();
+ ANDROID_API void stopDrawing();
+ ANDROID_API void notifyFramePending();
+
+ ANDROID_API void dumpProfileInfo(int fd);
+
+ ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size);
+
+private:
+ RenderThread& mRenderThread;
+ CanvasContext* mContext;
+
+ DrawFrameTask mDrawFrameTask;
+
+ Mutex mSyncMutex;
+ Condition mSyncCondition;
+
+ void destroyContext();
+
+ void post(RenderTask* task);
+ void* postAndWait(MethodInvokeRenderTask* task);
+
+ // Friend class to help with bridging
+ friend class RenderProxyBridge;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+#endif /* RENDERPROXY_H_ */
diff --git a/libs/hwui/renderthread/RenderTask.cpp b/libs/hwui/renderthread/RenderTask.cpp
new file mode 100644
index 0000000..7ca61e4
--- /dev/null
+++ b/libs/hwui/renderthread/RenderTask.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#define LOG_TAG "RenderTask"
+
+#include "RenderTask.h"
+
+#include <utils/Log.h>
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+void SignalingRenderTask::run() {
+ mTask->run();
+ mLock->lock();
+ mSignal->signal();
+ mLock->unlock();
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderTask.h b/libs/hwui/renderthread/RenderTask.h
new file mode 100644
index 0000000..1554a16
--- /dev/null
+++ b/libs/hwui/renderthread/RenderTask.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RENDERTASK_H_
+#define RENDERTASK_H_
+
+#include <cutils/compiler.h>
+#include <utils/Timers.h>
+
+namespace android {
+class Mutex;
+class Condition;
+namespace uirenderer {
+namespace renderthread {
+
+#define METHOD_INVOKE_PAYLOAD_SIZE (8 * sizeof(void*))
+
+/*
+ * Notes about memory management
+ *
+ * RenderThread will only invoke RenderTask::run(). It is the responsibility
+ * of the RenderTask to know if it needs to suicide at the end of run() or
+ * if some other lifecycle is being used. As such, it is not valid to reference
+ * anything on RenderTask after the first call to run().
+ *
+ * For example SignalingRenderTask
+ * is expected to be stack allocated by the calling thread, so it does not
+ * suicide in run() but instead relies on the caller to destroy it.
+ *
+ * MethodInvokeRenderTask however is currently allocated with new, so it will
+ * suicide at the end of run(). TODO: Replace this with a small pool to avoid
+ * malloc/free churn of small objects?
+ */
+
+class ANDROID_API RenderTask {
+public:
+ ANDROID_API RenderTask() : mNext(0), mRunAt(0) {}
+ ANDROID_API virtual ~RenderTask() {}
+
+ ANDROID_API virtual void run() = 0;
+
+ RenderTask* mNext;
+ nsecs_t mRunAt; // nano-seconds on the SYSTEM_TIME_MONOTONIC clock
+};
+
+class SignalingRenderTask : public RenderTask {
+public:
+ // Takes ownership of task, caller owns lock and signal
+ SignalingRenderTask(RenderTask* task, Mutex* lock, Condition* signal)
+ : mTask(task), mLock(lock), mSignal(signal) {}
+ virtual void run();
+
+private:
+ RenderTask* mTask;
+ Mutex* mLock;
+ Condition* mSignal;
+};
+
+typedef void* (*RunnableMethod)(void* data);
+
+class MethodInvokeRenderTask : public RenderTask {
+public:
+ MethodInvokeRenderTask(RunnableMethod method)
+ : mMethod(method), mReturnPtr(0) {}
+
+ void* payload() { return mData; }
+ void setReturnPtr(void** retptr) { mReturnPtr = retptr; }
+
+ virtual void run() {
+ void* retval = mMethod(mData);
+ if (mReturnPtr) {
+ *mReturnPtr = retval;
+ }
+ // Commit suicide
+ delete this;
+ }
+private:
+ RunnableMethod mMethod;
+ char mData[METHOD_INVOKE_PAYLOAD_SIZE];
+ void** mReturnPtr;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+#endif /* RENDERTASK_H_ */
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
new file mode 100644
index 0000000..403e164
--- /dev/null
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2013 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 "RenderThread.h"
+
+#if defined(HAVE_PTHREADS)
+#include <sys/resource.h>
+#endif
+#include <gui/DisplayEventReceiver.h>
+#include <utils/Log.h>
+
+#include "../RenderState.h"
+#include "CanvasContext.h"
+#include "EglManager.h"
+#include "RenderProxy.h"
+
+namespace android {
+using namespace uirenderer::renderthread;
+ANDROID_SINGLETON_STATIC_INSTANCE(RenderThread);
+
+namespace uirenderer {
+namespace renderthread {
+
+// Number of events to read at a time from the DisplayEventReceiver pipe.
+// The value should be large enough that we can quickly drain the pipe
+// using just a few large reads.
+static const size_t EVENT_BUFFER_SIZE = 100;
+
+// Slight delay to give the UI time to push us a new frame before we replay
+static const int DISPATCH_FRAME_CALLBACKS_DELAY = 4;
+
+TaskQueue::TaskQueue() : mHead(0), mTail(0) {}
+
+RenderTask* TaskQueue::next() {
+ RenderTask* ret = mHead;
+ if (ret) {
+ mHead = ret->mNext;
+ if (!mHead) {
+ mTail = 0;
+ }
+ ret->mNext = 0;
+ }
+ return ret;
+}
+
+RenderTask* TaskQueue::peek() {
+ return mHead;
+}
+
+void TaskQueue::queue(RenderTask* task) {
+ // Since the RenderTask itself forms the linked list it is not allowed
+ // to have the same task queued twice
+ LOG_ALWAYS_FATAL_IF(task->mNext || mTail == task, "Task is already in the queue!");
+ if (mTail) {
+ // Fast path if we can just append
+ if (mTail->mRunAt <= task->mRunAt) {
+ mTail->mNext = task;
+ mTail = task;
+ } else {
+ // Need to find the proper insertion point
+ RenderTask* previous = 0;
+ RenderTask* next = mHead;
+ while (next && next->mRunAt <= task->mRunAt) {
+ previous = next;
+ next = next->mNext;
+ }
+ if (!previous) {
+ task->mNext = mHead;
+ mHead = task;
+ } else {
+ previous->mNext = task;
+ if (next) {
+ task->mNext = next;
+ } else {
+ mTail = task;
+ }
+ }
+ }
+ } else {
+ mTail = mHead = task;
+ }
+}
+
+void TaskQueue::queueAtFront(RenderTask* task) {
+ if (mTail) {
+ task->mNext = mHead;
+ mHead = task;
+ } else {
+ mTail = mHead = task;
+ }
+}
+
+void TaskQueue::remove(RenderTask* task) {
+ // TaskQueue is strict here to enforce that users are keeping track of
+ // their RenderTasks due to how their memory is managed
+ LOG_ALWAYS_FATAL_IF(!task->mNext && mTail != task,
+ "Cannot remove a task that isn't in the queue!");
+
+ // If task is the head we can just call next() to pop it off
+ // Otherwise we need to scan through to find the task before it
+ if (peek() == task) {
+ next();
+ } else {
+ RenderTask* previous = mHead;
+ while (previous->mNext != task) {
+ previous = previous->mNext;
+ }
+ previous->mNext = task->mNext;
+ if (mTail == task) {
+ mTail = previous;
+ }
+ }
+}
+
+class DispatchFrameCallbacks : public RenderTask {
+private:
+ RenderThread* mRenderThread;
+public:
+ DispatchFrameCallbacks(RenderThread* rt) : mRenderThread(rt) {}
+
+ virtual void run() {
+ mRenderThread->dispatchFrameCallbacks();
+ }
+};
+
+RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
+ , mNextWakeup(LLONG_MAX)
+ , mDisplayEventReceiver(0)
+ , mVsyncRequested(false)
+ , mFrameCallbackTaskPending(false)
+ , mFrameCallbackTask(0)
+ , mRenderState(NULL)
+ , mEglManager(NULL) {
+ mFrameCallbackTask = new DispatchFrameCallbacks(this);
+ mLooper = new Looper(false);
+ run("RenderThread");
+}
+
+RenderThread::~RenderThread() {
+ LOG_ALWAYS_FATAL("Can't destroy the render thread");
+}
+
+void RenderThread::initializeDisplayEventReceiver() {
+ LOG_ALWAYS_FATAL_IF(mDisplayEventReceiver, "Initializing a second DisplayEventReceiver?");
+ mDisplayEventReceiver = new DisplayEventReceiver();
+ status_t status = mDisplayEventReceiver->initCheck();
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "Initialization of DisplayEventReceiver "
+ "failed with status: %d", status);
+
+ // Register the FD
+ mLooper->addFd(mDisplayEventReceiver->getFd(), 0,
+ Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
+}
+
+void RenderThread::initThreadLocals() {
+ initializeDisplayEventReceiver();
+ mEglManager = new EglManager(*this);
+ mRenderState = new RenderState();
+}
+
+int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
+ if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+ ALOGE("Display event receiver pipe was closed or an error occurred. "
+ "events=0x%x", events);
+ return 0; // remove the callback
+ }
+
+ if (!(events & Looper::EVENT_INPUT)) {
+ ALOGW("Received spurious callback for unhandled poll event. "
+ "events=0x%x", events);
+ return 1; // keep the callback
+ }
+
+ reinterpret_cast<RenderThread*>(data)->drainDisplayEventQueue();
+
+ return 1; // keep the callback
+}
+
+static nsecs_t latestVsyncEvent(DisplayEventReceiver* receiver) {
+ DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+ nsecs_t latest = 0;
+ ssize_t n;
+ while ((n = receiver->getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+ for (ssize_t i = 0; i < n; i++) {
+ const DisplayEventReceiver::Event& ev = buf[i];
+ switch (ev.header.type) {
+ case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+ latest = ev.header.timestamp;
+ break;
+ }
+ }
+ }
+ if (n < 0) {
+ ALOGW("Failed to get events from display event receiver, status=%d", status_t(n));
+ }
+ return latest;
+}
+
+void RenderThread::drainDisplayEventQueue(bool skipCallbacks) {
+ ATRACE_CALL();
+ nsecs_t vsyncEvent = latestVsyncEvent(mDisplayEventReceiver);
+ if (vsyncEvent > 0) {
+ mVsyncRequested = false;
+ mTimeLord.vsyncReceived(vsyncEvent);
+ if (!skipCallbacks && !mFrameCallbackTaskPending) {
+ ATRACE_NAME("queue mFrameCallbackTask");
+ mFrameCallbackTaskPending = true;
+ queueDelayed(mFrameCallbackTask, DISPATCH_FRAME_CALLBACKS_DELAY);
+ }
+ }
+}
+
+void RenderThread::dispatchFrameCallbacks() {
+ ATRACE_CALL();
+ mFrameCallbackTaskPending = false;
+
+ std::set<IFrameCallback*> callbacks;
+ mFrameCallbacks.swap(callbacks);
+
+ for (std::set<IFrameCallback*>::iterator it = callbacks.begin(); it != callbacks.end(); it++) {
+ (*it)->doFrame();
+ }
+}
+
+void RenderThread::requestVsync() {
+ if (!mVsyncRequested) {
+ mVsyncRequested = true;
+ status_t status = mDisplayEventReceiver->requestNextVsync();
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
+ "requestNextVsync failed with status: %d", status);
+ }
+}
+
+bool RenderThread::threadLoop() {
+#if defined(HAVE_PTHREADS)
+ setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
+#endif
+ initThreadLocals();
+
+ int timeoutMillis = -1;
+ for (;;) {
+ int result = mLooper->pollOnce(timeoutMillis);
+ LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
+ "RenderThread Looper POLL_ERROR!");
+
+ nsecs_t nextWakeup;
+ // Process our queue, if we have anything
+ while (RenderTask* task = nextTask(&nextWakeup)) {
+ task->run();
+ // task may have deleted itself, do not reference it again
+ }
+ if (nextWakeup == LLONG_MAX) {
+ timeoutMillis = -1;
+ } else {
+ nsecs_t timeoutNanos = nextWakeup - systemTime(SYSTEM_TIME_MONOTONIC);
+ timeoutMillis = nanoseconds_to_milliseconds(timeoutNanos);
+ if (timeoutMillis < 0) {
+ timeoutMillis = 0;
+ }
+ }
+
+ if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
+ drainDisplayEventQueue(true);
+ mFrameCallbacks.insert(
+ mPendingRegistrationFrameCallbacks.begin(), mPendingRegistrationFrameCallbacks.end());
+ mPendingRegistrationFrameCallbacks.clear();
+ requestVsync();
+ }
+ }
+
+ return false;
+}
+
+void RenderThread::queue(RenderTask* task) {
+ AutoMutex _lock(mLock);
+ mQueue.queue(task);
+ if (mNextWakeup && task->mRunAt < mNextWakeup) {
+ mNextWakeup = 0;
+ mLooper->wake();
+ }
+}
+
+void RenderThread::queueAtFront(RenderTask* task) {
+ AutoMutex _lock(mLock);
+ mQueue.queueAtFront(task);
+ mLooper->wake();
+}
+
+void RenderThread::queueDelayed(RenderTask* task, int delayMs) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ task->mRunAt = now + milliseconds_to_nanoseconds(delayMs);
+ queue(task);
+}
+
+void RenderThread::remove(RenderTask* task) {
+ AutoMutex _lock(mLock);
+ mQueue.remove(task);
+}
+
+void RenderThread::postFrameCallback(IFrameCallback* callback) {
+ mPendingRegistrationFrameCallbacks.insert(callback);
+}
+
+void RenderThread::removeFrameCallback(IFrameCallback* callback) {
+ mFrameCallbacks.erase(callback);
+ mPendingRegistrationFrameCallbacks.erase(callback);
+}
+
+void RenderThread::pushBackFrameCallback(IFrameCallback* callback) {
+ if (mFrameCallbacks.erase(callback)) {
+ mPendingRegistrationFrameCallbacks.insert(callback);
+ }
+}
+
+RenderTask* RenderThread::nextTask(nsecs_t* nextWakeup) {
+ AutoMutex _lock(mLock);
+ RenderTask* next = mQueue.peek();
+ if (!next) {
+ mNextWakeup = LLONG_MAX;
+ } else {
+ mNextWakeup = next->mRunAt;
+ // Most tasks won't be delayed, so avoid unnecessary systemTime() calls
+ if (next->mRunAt <= 0 || next->mRunAt <= systemTime(SYSTEM_TIME_MONOTONIC)) {
+ next = mQueue.next();
+ } else {
+ next = 0;
+ }
+ }
+ if (nextWakeup) {
+ *nextWakeup = mNextWakeup;
+ }
+ return next;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
new file mode 100644
index 0000000..0b91e9d
--- /dev/null
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RENDERTHREAD_H_
+#define RENDERTHREAD_H_
+
+#include "RenderTask.h"
+
+#include <memory>
+#include <set>
+
+#include <cutils/compiler.h>
+#include <utils/Looper.h>
+#include <utils/Mutex.h>
+#include <utils/Singleton.h>
+#include <utils/Thread.h>
+
+#include "TimeLord.h"
+
+namespace android {
+
+class DisplayEventReceiver;
+
+namespace uirenderer {
+
+class RenderState;
+
+namespace renderthread {
+
+class DispatchFrameCallbacks;
+class EglManager;
+class RenderProxy;
+
+class TaskQueue {
+public:
+ TaskQueue();
+
+ RenderTask* next();
+ void queue(RenderTask* task);
+ void queueAtFront(RenderTask* task);
+ RenderTask* peek();
+ void remove(RenderTask* task);
+
+private:
+ RenderTask* mHead;
+ RenderTask* mTail;
+};
+
+// Mimics android.view.Choreographer.FrameCallback
+class IFrameCallback {
+public:
+ virtual void doFrame() = 0;
+
+protected:
+ ~IFrameCallback() {}
+};
+
+class ANDROID_API RenderThread : public Thread, protected Singleton<RenderThread> {
+public:
+ // RenderThread takes complete ownership of tasks that are queued
+ // and will delete them after they are run
+ ANDROID_API void queue(RenderTask* task);
+ ANDROID_API void queueAtFront(RenderTask* task);
+ void queueDelayed(RenderTask* task, int delayMs);
+ void remove(RenderTask* task);
+
+ // Mimics android.view.Choreographer
+ void postFrameCallback(IFrameCallback* callback);
+ void removeFrameCallback(IFrameCallback* callback);
+ // If the callback is currently registered, it will be pushed back until
+ // the next vsync. If it is not currently registered this does nothing.
+ void pushBackFrameCallback(IFrameCallback* callback);
+
+ TimeLord& timeLord() { return mTimeLord; }
+ RenderState& renderState() { return *mRenderState; }
+ EglManager& eglManager() { return *mEglManager; }
+
+protected:
+ virtual bool threadLoop();
+
+private:
+ friend class Singleton<RenderThread>;
+ friend class DispatchFrameCallbacks;
+ friend class RenderProxy;
+
+ RenderThread();
+ virtual ~RenderThread();
+
+ void initThreadLocals();
+ void initializeDisplayEventReceiver();
+ static int displayEventReceiverCallback(int fd, int events, void* data);
+ void drainDisplayEventQueue(bool skipCallbacks = false);
+ void dispatchFrameCallbacks();
+ void requestVsync();
+
+ // Returns the next task to be run. If this returns NULL nextWakeup is set
+ // to the time to requery for the nextTask to run. mNextWakeup is also
+ // set to this time
+ RenderTask* nextTask(nsecs_t* nextWakeup);
+
+ sp<Looper> mLooper;
+ Mutex mLock;
+
+ nsecs_t mNextWakeup;
+ TaskQueue mQueue;
+
+ DisplayEventReceiver* mDisplayEventReceiver;
+ bool mVsyncRequested;
+ std::set<IFrameCallback*> mFrameCallbacks;
+ // We defer the actual registration of these callbacks until
+ // both mQueue *and* mDisplayEventReceiver have been drained off all
+ // immediate events. This makes sure that we catch the next vsync, not
+ // the previous one
+ std::set<IFrameCallback*> mPendingRegistrationFrameCallbacks;
+ bool mFrameCallbackTaskPending;
+ DispatchFrameCallbacks* mFrameCallbackTask;
+
+ TimeLord mTimeLord;
+ RenderState* mRenderState;
+ EglManager* mEglManager;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+#endif /* RENDERTHREAD_H_ */
diff --git a/libs/hwui/renderthread/TimeLord.cpp b/libs/hwui/renderthread/TimeLord.cpp
new file mode 100644
index 0000000..758d96e
--- /dev/null
+++ b/libs/hwui/renderthread/TimeLord.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 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 "TimeLord.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+TimeLord::TimeLord()
+ : mFrameIntervalNanos(0)
+ , mFrameTimeNanos(0) {
+}
+
+void TimeLord::vsyncReceived(nsecs_t vsync) {
+ if (vsync > mFrameTimeNanos) {
+ mFrameTimeNanos = vsync;
+ }
+}
+
+nsecs_t TimeLord::frameTimeMs() {
+ // Logic copied from Choreographer.java
+ nsecs_t now = systemTime(CLOCK_MONOTONIC);
+ nsecs_t jitterNanos = now - mFrameTimeNanos;
+ if (jitterNanos >= mFrameIntervalNanos) {
+ nsecs_t lastFrameOffset = jitterNanos % mFrameIntervalNanos;
+ mFrameTimeNanos = now - lastFrameOffset;
+ }
+ return nanoseconds_to_milliseconds(mFrameTimeNanos);
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/TimeLord.h b/libs/hwui/renderthread/TimeLord.h
new file mode 100644
index 0000000..52c6d9e
--- /dev/null
+++ b/libs/hwui/renderthread/TimeLord.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TIMELORD_H
+#define TIMELORD_H
+
+#include <utils/Timers.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+class RenderThread;
+
+// This class serves as a helper to filter & manage frame times from multiple sources
+// ensuring that time flows linearly and smoothly
+class TimeLord {
+public:
+ void setFrameInterval(nsecs_t intervalNanos) { mFrameIntervalNanos = intervalNanos; }
+ void vsyncReceived(nsecs_t vsync);
+ nsecs_t frameTimeMs();
+
+private:
+ friend class RenderThread;
+
+ TimeLord();
+ ~TimeLord() {}
+
+ nsecs_t mFrameIntervalNanos;
+ nsecs_t mFrameTimeNanos;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* TIMELORD_H */
diff --git a/libs/hwui/thread/Task.h b/libs/hwui/thread/Task.h
index 9a211a2..30b6ff2 100644
--- a/libs/hwui/thread/Task.h
+++ b/libs/hwui/thread/Task.h
@@ -17,8 +17,6 @@
#ifndef ANDROID_HWUI_TASK_H
#define ANDROID_HWUI_TASK_H
-#define ATRACE_TAG ATRACE_TAG_VIEW
-
#include <utils/RefBase.h>
#include <utils/Trace.h>
@@ -40,7 +38,7 @@ public:
virtual ~Task() { }
T getResult() const {
- ATRACE_NAME("waitForTask");
+ ScopedTrace tracer(ATRACE_TAG_VIEW, "waitForTask");
return mFuture->get();
}
diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp
index 189895c..cb5401c 100644
--- a/libs/hwui/thread/TaskManager.cpp
+++ b/libs/hwui/thread/TaskManager.cpp
@@ -15,10 +15,14 @@
*/
#include <sys/sysinfo.h>
+#if defined(HAVE_PTHREADS)
+#include <sys/resource.h>
+#endif
+#include "TaskManager.h"
#include "Task.h"
#include "TaskProcessor.h"
-#include "TaskManager.h"
+#include "utils/MathUtils.h"
namespace android {
namespace uirenderer {
@@ -31,7 +35,8 @@ TaskManager::TaskManager() {
// Get the number of available CPUs. This value does not change over time.
int cpuCount = sysconf(_SC_NPROCESSORS_CONF);
- for (int i = 0; i < cpuCount / 2; i++) {
+ int workerCount = MathUtils::max(1, cpuCount / 2);
+ for (int i = 0; i < workerCount; i++) {
String8 name;
name.appendFormat("hwuiTask%d", i + 1);
mThreads.add(new WorkerThread(name));
@@ -77,6 +82,13 @@ bool TaskManager::addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBa
// Thread
///////////////////////////////////////////////////////////////////////////////
+status_t TaskManager::WorkerThread::readyToRun() {
+#if defined(HAVE_PTHREADS)
+ setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND);
+#endif
+ return NO_ERROR;
+}
+
bool TaskManager::WorkerThread::threadLoop() {
mSignal.wait();
Vector<TaskWrapper> tasks;
diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h
index f2a216f..5a933ab 100644
--- a/libs/hwui/thread/TaskManager.h
+++ b/libs/hwui/thread/TaskManager.h
@@ -84,6 +84,7 @@ private:
void exit();
private:
+ virtual status_t readyToRun();
virtual bool threadLoop();
// Lock for the list of tasks
diff --git a/libs/hwui/utils/Blur.cpp b/libs/hwui/utils/Blur.cpp
index 85d90d0..877a422 100644
--- a/libs/hwui/utils/Blur.cpp
+++ b/libs/hwui/utils/Blur.cpp
@@ -19,10 +19,47 @@
#include <math.h>
#include "Blur.h"
+#include "MathUtils.h"
namespace android {
namespace uirenderer {
+// This constant approximates the scaling done in the software path's
+// "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
+static const float BLUR_SIGMA_SCALE = 0.57735f;
+
+float Blur::convertRadiusToSigma(float radius) {
+ return radius > 0 ? BLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
+}
+
+float Blur::convertSigmaToRadius(float sigma) {
+ return sigma > 0.5f ? (sigma - 0.5f) / BLUR_SIGMA_SCALE : 0.0f;
+}
+
+// if the original radius was on an integer boundary and the resulting radius
+// is within the conversion error tolerance then we attempt to snap to the
+// original integer boundary.
+uint32_t Blur::convertRadiusToInt(float radius) {
+ const float radiusCeil = ceilf(radius);
+ if (MathUtils::areEqual(radiusCeil, radius)) {
+ return radiusCeil;
+ }
+ return radius;
+}
+
+/**
+ * HWUI has used a slightly different equation than Skia to generate the value
+ * for sigma and to preserve compatibility we have kept that logic.
+ *
+ * Based on some experimental radius and sigma values we approximate the
+ * equation sigma = f(radius) as sigma = radius * 0.3 + 0.6. The larger the
+ * radius gets, the more our gaussian blur will resemble a box blur since with
+ * large sigma the gaussian curve begins to lose its shape.
+ */
+static float legacyConvertRadiusToSigma(float radius) {
+ return radius > 0 ? 0.3f * radius + 0.6f : 0.0f;
+}
+
void Blur::generateGaussianWeights(float* weights, int32_t radius) {
// Compute gaussian weights for the blur
// e is the euler's number
@@ -31,13 +68,7 @@ void Blur::generateGaussianWeights(float* weights, int32_t radius) {
// g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
// x is of the form [-radius .. 0 .. radius]
// and sigma varies with radius.
- // Based on some experimental radius values and sigma's
- // we approximately fit sigma = f(radius) as
- // sigma = radius * 0.3 + 0.6
- // The larger the radius gets, the more our gaussian blur
- // will resemble a box blur since with large sigma
- // the gaussian curve begins to lose its shape
- float sigma = 0.3f * (float) radius + 0.6f;
+ float sigma = legacyConvertRadiusToSigma((float) radius);
// Now compute the coefficints
// We will store some redundant values to save some math during
diff --git a/libs/hwui/utils/Blur.h b/libs/hwui/utils/Blur.h
index 6c176e9..b145333 100644
--- a/libs/hwui/utils/Blur.h
+++ b/libs/hwui/utils/Blur.h
@@ -18,12 +18,22 @@
#define ANDROID_HWUI_BLUR_H
#include <stdint.h>
+#include <cutils/compiler.h>
namespace android {
namespace uirenderer {
class Blur {
public:
+ // If radius > 0, return the corresponding sigma, else return 0
+ ANDROID_API static float convertRadiusToSigma(float radius);
+ // If sigma > 0.5, return the corresponding radius, else return 0
+ ANDROID_API static float convertSigmaToRadius(float sigma);
+ // If the original radius was on an integer boundary then after the sigma to
+ // radius conversion a small rounding error may be introduced. This function
+ // accounts for that error and snaps to the appropriate integer boundary.
+ static uint32_t convertRadiusToInt(float radius);
+
static void generateGaussianWeights(float* weights, int32_t radius);
static void horizontal(float* weights, int32_t radius, const uint8_t* source,
uint8_t* dest, int32_t width, int32_t height);
diff --git a/libs/hwui/utils/GLUtils.cpp b/libs/hwui/utils/GLUtils.cpp
new file mode 100644
index 0000000..9b298ca
--- /dev/null
+++ b/libs/hwui/utils/GLUtils.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <utils/Log.h>
+
+#include "GLUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+void GLUtils::dumpGLErrors() {
+ GLenum status = GL_NO_ERROR;
+ while ((status = glGetError()) != GL_NO_ERROR) {
+ switch (status) {
+ case GL_INVALID_ENUM:
+ ALOGE("GL error: GL_INVALID_ENUM");
+ break;
+ case GL_INVALID_VALUE:
+ ALOGE("GL error: GL_INVALID_VALUE");
+ break;
+ case GL_INVALID_OPERATION:
+ ALOGE("GL error: GL_INVALID_OPERATION");
+ break;
+ case GL_OUT_OF_MEMORY:
+ ALOGE("GL error: Out of memory!");
+ break;
+ default:
+ ALOGE("GL error: 0x%x", status);
+ }
+ }
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/utils/GLUtils.h b/libs/hwui/utils/GLUtils.h
new file mode 100644
index 0000000..890e374
--- /dev/null
+++ b/libs/hwui/utils/GLUtils.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef GLUTILS_H
+#define GLUTILS_H
+
+namespace android {
+namespace uirenderer {
+
+class GLUtils {
+private:
+public:
+ /**
+ * Print out any GL errors with ALOGE
+ */
+ static void dumpGLErrors();
+
+}; // class GLUtils
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* GLUTILS_H */
diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h
new file mode 100644
index 0000000..5b7c87c
--- /dev/null
+++ b/libs/hwui/utils/Macros.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef MACROS_H
+#define MACROS_H
+
+#define PREVENT_COPY_AND_ASSIGN(Type) \
+ private: \
+ Type(const Type&); \
+ void operator=(const Type&)
+
+#define DESCRIPTION_TYPE(Type) \
+ int compare(const Type& rhs) const { return memcmp(this, &rhs, sizeof(Type));} \
+ bool operator==(const Type& other) const { return compare(other) == 0; } \
+ bool operator!=(const Type& other) const { return compare(other) != 0; } \
+ friend inline int strictly_order_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs) < 0; } \
+ friend inline int compare_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs); } \
+ friend inline hash_t hash_type(const Type& entry) { return entry.hash(); }
+
+#endif /* MACROS_H */
diff --git a/libs/hwui/utils/MathUtils.h b/libs/hwui/utils/MathUtils.h
new file mode 100644
index 0000000..6fb0411
--- /dev/null
+++ b/libs/hwui/utils/MathUtils.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef MATHUTILS_H
+#define MATHUTILS_H
+
+namespace android {
+namespace uirenderer {
+
+#define NON_ZERO_EPSILON (0.001f)
+#define ALPHA_EPSILON (0.001f)
+
+class MathUtils {
+public:
+ /**
+ * Check for floats that are close enough to zero.
+ */
+ inline static bool isZero(float value) {
+ return (value >= -NON_ZERO_EPSILON) && (value <= NON_ZERO_EPSILON);
+ }
+
+ inline static bool isPositive(float value) {
+ return value >= NON_ZERO_EPSILON;
+ }
+
+ /**
+ * Clamps alpha value, and snaps when very near 0 or 1
+ */
+ inline static float clampAlpha(float alpha) {
+ if (alpha <= ALPHA_EPSILON) {
+ return 0;
+ } else if (alpha >= (1 - ALPHA_EPSILON)) {
+ return 1;
+ } else {
+ return alpha;
+ }
+ }
+
+ /*
+ * Clamps positive tessellation scale values
+ */
+ inline static float clampTessellationScale(float scale) {
+ const float MIN_SCALE = 0.0001;
+ const float MAX_SCALE = 1e10;
+ if (scale < MIN_SCALE) {
+ return MIN_SCALE;
+ } else if (scale > MAX_SCALE) {
+ return MAX_SCALE;
+ }
+ return scale;
+ }
+
+ inline static bool areEqual(float valueA, float valueB) {
+ return isZero(valueA - valueB);
+ }
+
+ inline static int max(int a, int b) {
+ return a > b ? a : b;
+ }
+
+ inline static int min(int a, int b) {
+ return a < b ? a : b;
+ }
+
+ inline static float lerp(float v1, float v2, float t) {
+ return v1 + ((v2 - v1) * t);
+ }
+}; // class MathUtils
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* MATHUTILS_H */
diff --git a/libs/input/Android.mk b/libs/input/Android.mk
new file mode 100644
index 0000000..a7fb0e2
--- /dev/null
+++ b/libs/input/Android.mk
@@ -0,0 +1,53 @@
+# Copyright (C) 2010 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ PointerController.cpp \
+ SpriteController.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ liblog \
+ libutils \
+ libskia \
+ libgui \
+ libui \
+ libinput \
+ libinputflinger
+
+LOCAL_C_INCLUDES := \
+ frameworks/native/services
+
+
+LOCAL_CFLAGS += -Wno-unused-parameter
+
+LOCAL_MODULE:= libinputservice
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
+
+
+# Include subdirectory makefiles
+# ============================================================
+
+# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
+# team really wants is to build the stuff defined by this makefile.
+ifeq (,$(ONE_SHOT_MAKEFILE))
+include $(call first-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
new file mode 100644
index 0000000..9af521b
--- /dev/null
+++ b/libs/input/PointerController.cpp
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#define LOG_TAG "PointerController"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about pointer updates
+#define DEBUG_POINTER_UPDATES 0
+
+#include "PointerController.h"
+
+#include <cutils/log.h>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+#include <SkXfermode.h>
+
+namespace android {
+
+// --- PointerController ---
+
+// Time to wait before starting the fade when the pointer is inactive.
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+
+// Time to wait between animation frames.
+static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
+
+// Time to spend fading out the spot completely.
+static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
+
+// Time to spend fading out the pointer completely.
+static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
+
+
+// --- PointerController ---
+
+PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController) :
+ mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
+ mHandler = new WeakMessageHandler(this);
+
+ AutoMutex _l(mLock);
+
+ mLocked.animationPending = false;
+
+ mLocked.displayWidth = -1;
+ mLocked.displayHeight = -1;
+ mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
+
+ mLocked.presentation = PRESENTATION_POINTER;
+ mLocked.presentationChanged = false;
+
+ mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL;
+
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerX = 0;
+ mLocked.pointerY = 0;
+ mLocked.pointerAlpha = 0.0f; // pointer is initially faded
+ mLocked.pointerSprite = mSpriteController->createSprite();
+ mLocked.pointerIconChanged = false;
+
+ mLocked.buttonState = 0;
+
+ loadResources();
+}
+
+PointerController::~PointerController() {
+ mLooper->removeMessages(mHandler);
+
+ AutoMutex _l(mLock);
+
+ mLocked.pointerSprite.clear();
+
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ delete mLocked.spots.itemAt(i);
+ }
+ mLocked.spots.clear();
+ mLocked.recycledSprites.clear();
+}
+
+bool PointerController::getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const {
+ AutoMutex _l(mLock);
+
+ return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+}
+
+bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const {
+ if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) {
+ return false;
+ }
+
+ *outMinX = 0;
+ *outMinY = 0;
+ switch (mLocked.displayOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ case DISPLAY_ORIENTATION_270:
+ *outMaxX = mLocked.displayHeight - 1;
+ *outMaxY = mLocked.displayWidth - 1;
+ break;
+ default:
+ *outMaxX = mLocked.displayWidth - 1;
+ *outMaxY = mLocked.displayHeight - 1;
+ break;
+ }
+ return true;
+}
+
+void PointerController::move(float deltaX, float deltaY) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
+#endif
+ if (deltaX == 0.0f && deltaY == 0.0f) {
+ return;
+ }
+
+ AutoMutex _l(mLock);
+
+ setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+}
+
+void PointerController::setButtonState(int32_t buttonState) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("Set button state 0x%08x", buttonState);
+#endif
+ AutoMutex _l(mLock);
+
+ if (mLocked.buttonState != buttonState) {
+ mLocked.buttonState = buttonState;
+ }
+}
+
+int32_t PointerController::getButtonState() const {
+ AutoMutex _l(mLock);
+
+ return mLocked.buttonState;
+}
+
+void PointerController::setPosition(float x, float y) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
+#endif
+ AutoMutex _l(mLock);
+
+ setPositionLocked(x, y);
+}
+
+void PointerController::setPositionLocked(float x, float y) {
+ float minX, minY, maxX, maxY;
+ if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+ if (x <= minX) {
+ mLocked.pointerX = minX;
+ } else if (x >= maxX) {
+ mLocked.pointerX = maxX;
+ } else {
+ mLocked.pointerX = x;
+ }
+ if (y <= minY) {
+ mLocked.pointerY = minY;
+ } else if (y >= maxY) {
+ mLocked.pointerY = maxY;
+ } else {
+ mLocked.pointerY = y;
+ }
+ updatePointerLocked();
+ }
+}
+
+void PointerController::getPosition(float* outX, float* outY) const {
+ AutoMutex _l(mLock);
+
+ *outX = mLocked.pointerX;
+ *outY = mLocked.pointerY;
+}
+
+void PointerController::fade(Transition transition) {
+ AutoMutex _l(mLock);
+
+ // Remove the inactivity timeout, since we are fading now.
+ removeInactivityTimeoutLocked();
+
+ // Start fading.
+ if (transition == TRANSITION_IMMEDIATE) {
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerAlpha = 0.0f;
+ updatePointerLocked();
+ } else {
+ mLocked.pointerFadeDirection = -1;
+ startAnimationLocked();
+ }
+}
+
+void PointerController::unfade(Transition transition) {
+ AutoMutex _l(mLock);
+
+ // Always reset the inactivity timer.
+ resetInactivityTimeoutLocked();
+
+ // Start unfading.
+ if (transition == TRANSITION_IMMEDIATE) {
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerAlpha = 1.0f;
+ updatePointerLocked();
+ } else {
+ mLocked.pointerFadeDirection = 1;
+ startAnimationLocked();
+ }
+}
+
+void PointerController::setPresentation(Presentation presentation) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.presentation != presentation) {
+ mLocked.presentation = presentation;
+ mLocked.presentationChanged = true;
+
+ if (presentation != PRESENTATION_SPOT) {
+ fadeOutAndReleaseAllSpotsLocked();
+ }
+
+ updatePointerLocked();
+ }
+}
+
+void PointerController::setSpots(const PointerCoords* spotCoords,
+ const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("setSpots: idBits=%08x", spotIdBits.value);
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id,
+ c.getAxisValue(AMOTION_EVENT_AXIS_X),
+ c.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+#endif
+
+ AutoMutex _l(mLock);
+
+ mSpriteController->openTransaction();
+
+ // Add or move spots for fingers that are down.
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
+ ? mResources.spotTouch : mResources.spotHover;
+ float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+ float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+ Spot* spot = getSpotLocked(id);
+ if (!spot) {
+ spot = createAndAddSpotLocked(id);
+ }
+
+ spot->updateSprite(&icon, x, y);
+ }
+
+ // Remove spots for fingers that went up.
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id != Spot::INVALID_ID
+ && !spotIdBits.hasBit(spot->id)) {
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+ }
+
+ mSpriteController->closeTransaction();
+}
+
+void PointerController::clearSpots() {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("clearSpots");
+#endif
+
+ AutoMutex _l(mLock);
+
+ fadeOutAndReleaseAllSpotsLocked();
+}
+
+void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.inactivityTimeout != inactivityTimeout) {
+ mLocked.inactivityTimeout = inactivityTimeout;
+ resetInactivityTimeoutLocked();
+ }
+}
+
+void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) {
+ AutoMutex _l(mLock);
+
+ // Adjust to use the display's unrotated coordinate frame.
+ if (orientation == DISPLAY_ORIENTATION_90
+ || orientation == DISPLAY_ORIENTATION_270) {
+ int32_t temp = height;
+ height = width;
+ width = temp;
+ }
+
+ if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+ mLocked.displayWidth = width;
+ mLocked.displayHeight = height;
+
+ float minX, minY, maxX, maxY;
+ if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+ mLocked.pointerX = (minX + maxX) * 0.5f;
+ mLocked.pointerY = (minY + maxY) * 0.5f;
+ } else {
+ mLocked.pointerX = 0;
+ mLocked.pointerY = 0;
+ }
+
+ fadeOutAndReleaseAllSpotsLocked();
+ }
+
+ if (mLocked.displayOrientation != orientation) {
+ // Apply offsets to convert from the pixel top-left corner position to the pixel center.
+ // This creates an invariant frame of reference that we can easily rotate when
+ // taking into account that the pointer may be located at fractional pixel offsets.
+ float x = mLocked.pointerX + 0.5f;
+ float y = mLocked.pointerY + 0.5f;
+ float temp;
+
+ // Undo the previous rotation.
+ switch (mLocked.displayOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = x;
+ x = mLocked.displayWidth - y;
+ y = temp;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = mLocked.displayWidth - x;
+ y = mLocked.displayHeight - y;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ temp = x;
+ x = y;
+ y = mLocked.displayHeight - temp;
+ break;
+ }
+
+ // Perform the new rotation.
+ switch (orientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = x;
+ x = y;
+ y = mLocked.displayWidth - temp;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = mLocked.displayWidth - x;
+ y = mLocked.displayHeight - y;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ temp = x;
+ x = mLocked.displayHeight - y;
+ y = temp;
+ break;
+ }
+
+ // Apply offsets to convert from the pixel center to the pixel top-left corner position
+ // and save the results.
+ mLocked.pointerX = x - 0.5f;
+ mLocked.pointerY = y - 0.5f;
+ mLocked.displayOrientation = orientation;
+ }
+
+ updatePointerLocked();
+}
+
+void PointerController::setPointerIcon(const SpriteIcon& icon) {
+ AutoMutex _l(mLock);
+
+ mLocked.pointerIcon = icon.copy();
+ mLocked.pointerIconChanged = true;
+
+ updatePointerLocked();
+}
+
+void PointerController::handleMessage(const Message& message) {
+ switch (message.what) {
+ case MSG_ANIMATE:
+ doAnimate();
+ break;
+ case MSG_INACTIVITY_TIMEOUT:
+ doInactivityTimeout();
+ break;
+ }
+}
+
+void PointerController::doAnimate() {
+ AutoMutex _l(mLock);
+
+ bool keepAnimating = false;
+ mLocked.animationPending = false;
+ nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
+
+ // Animate pointer fade.
+ if (mLocked.pointerFadeDirection < 0) {
+ mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
+ if (mLocked.pointerAlpha <= 0.0f) {
+ mLocked.pointerAlpha = 0.0f;
+ mLocked.pointerFadeDirection = 0;
+ } else {
+ keepAnimating = true;
+ }
+ updatePointerLocked();
+ } else if (mLocked.pointerFadeDirection > 0) {
+ mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION;
+ if (mLocked.pointerAlpha >= 1.0f) {
+ mLocked.pointerAlpha = 1.0f;
+ mLocked.pointerFadeDirection = 0;
+ } else {
+ keepAnimating = true;
+ }
+ updatePointerLocked();
+ }
+
+ // Animate spots that are fading out and being removed.
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == Spot::INVALID_ID) {
+ spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
+ if (spot->alpha <= 0) {
+ mLocked.spots.removeAt(i--);
+ releaseSpotLocked(spot);
+ } else {
+ spot->sprite->setAlpha(spot->alpha);
+ keepAnimating = true;
+ }
+ }
+ }
+
+ if (keepAnimating) {
+ startAnimationLocked();
+ }
+}
+
+void PointerController::doInactivityTimeout() {
+ fade(TRANSITION_GRADUAL);
+}
+
+void PointerController::startAnimationLocked() {
+ if (!mLocked.animationPending) {
+ mLocked.animationPending = true;
+ mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
+ }
+}
+
+void PointerController::resetInactivityTimeoutLocked() {
+ mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+
+ nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT
+ ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
+ mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::removeInactivityTimeoutLocked() {
+ mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::updatePointerLocked() {
+ mSpriteController->openTransaction();
+
+ mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
+ mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+
+ if (mLocked.pointerAlpha > 0) {
+ mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
+ mLocked.pointerSprite->setVisible(true);
+ } else {
+ mLocked.pointerSprite->setVisible(false);
+ }
+
+ if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
+ mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER
+ ? mLocked.pointerIcon : mResources.spotAnchor);
+ mLocked.pointerIconChanged = false;
+ mLocked.presentationChanged = false;
+ }
+
+ mSpriteController->closeTransaction();
+}
+
+PointerController::Spot* PointerController::getSpotLocked(uint32_t id) {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == id) {
+ return spot;
+ }
+ }
+ return NULL;
+}
+
+PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) {
+ // Remove spots until we have fewer than MAX_SPOTS remaining.
+ while (mLocked.spots.size() >= MAX_SPOTS) {
+ Spot* spot = removeFirstFadingSpotLocked();
+ if (!spot) {
+ spot = mLocked.spots.itemAt(0);
+ mLocked.spots.removeAt(0);
+ }
+ releaseSpotLocked(spot);
+ }
+
+ // Obtain a sprite from the recycled pool.
+ sp<Sprite> sprite;
+ if (! mLocked.recycledSprites.isEmpty()) {
+ sprite = mLocked.recycledSprites.top();
+ mLocked.recycledSprites.pop();
+ } else {
+ sprite = mSpriteController->createSprite();
+ }
+
+ // Return the new spot.
+ Spot* spot = new Spot(id, sprite);
+ mLocked.spots.push(spot);
+ return spot;
+}
+
+PointerController::Spot* PointerController::removeFirstFadingSpotLocked() {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == Spot::INVALID_ID) {
+ mLocked.spots.removeAt(i);
+ return spot;
+ }
+ }
+ return NULL;
+}
+
+void PointerController::releaseSpotLocked(Spot* spot) {
+ spot->sprite->clearIcon();
+
+ if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
+ mLocked.recycledSprites.push(spot->sprite);
+ }
+
+ delete spot;
+}
+
+void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) {
+ if (spot->id != Spot::INVALID_ID) {
+ spot->id = Spot::INVALID_ID;
+ startAnimationLocked();
+ }
+}
+
+void PointerController::fadeOutAndReleaseAllSpotsLocked() {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+}
+
+void PointerController::loadResources() {
+ mPolicy->loadPointerResources(&mResources);
+}
+
+
+// --- PointerController::Spot ---
+
+void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) {
+ sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
+ sprite->setAlpha(alpha);
+ sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
+ sprite->setPosition(x, y);
+
+ this->x = x;
+ this->y = y;
+
+ if (icon != lastIcon) {
+ lastIcon = icon;
+ if (icon) {
+ sprite->setIcon(*icon);
+ sprite->setVisible(true);
+ } else {
+ sprite->setVisible(false);
+ }
+ }
+}
+
+} // namespace android
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
new file mode 100644
index 0000000..b9e4ce7
--- /dev/null
+++ b/libs/input/PointerController.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_POINTER_CONTROLLER_H
+#define _UI_POINTER_CONTROLLER_H
+
+#include "SpriteController.h"
+
+#include <ui/DisplayInfo.h>
+#include <input/Input.h>
+#include <inputflinger/PointerControllerInterface.h>
+#include <utils/BitSet.h>
+#include <utils/RefBase.h>
+#include <utils/Looper.h>
+#include <utils/String8.h>
+
+#include <SkBitmap.h>
+
+namespace android {
+
+/*
+ * Pointer resources.
+ */
+struct PointerResources {
+ SpriteIcon spotHover;
+ SpriteIcon spotTouch;
+ SpriteIcon spotAnchor;
+};
+
+
+/*
+ * Pointer controller policy interface.
+ *
+ * The pointer controller policy is used by the pointer controller to interact with
+ * the Window Manager and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class PointerControllerPolicyInterface : public virtual RefBase {
+protected:
+ PointerControllerPolicyInterface() { }
+ virtual ~PointerControllerPolicyInterface() { }
+
+public:
+ virtual void loadPointerResources(PointerResources* outResources) = 0;
+};
+
+
+/*
+ * Tracks pointer movements and draws the pointer sprite to a surface.
+ *
+ * Handles pointer acceleration and animation.
+ */
+class PointerController : public PointerControllerInterface, public MessageHandler {
+protected:
+ virtual ~PointerController();
+
+public:
+ enum InactivityTimeout {
+ INACTIVITY_TIMEOUT_NORMAL = 0,
+ INACTIVITY_TIMEOUT_SHORT = 1,
+ };
+
+ PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController);
+
+ virtual bool getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const;
+ virtual void move(float deltaX, float deltaY);
+ virtual void setButtonState(int32_t buttonState);
+ virtual int32_t getButtonState() const;
+ virtual void setPosition(float x, float y);
+ virtual void getPosition(float* outX, float* outY) const;
+ virtual void fade(Transition transition);
+ virtual void unfade(Transition transition);
+
+ virtual void setPresentation(Presentation presentation);
+ virtual void setSpots(const PointerCoords* spotCoords,
+ const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
+ virtual void clearSpots();
+
+ void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
+ void setPointerIcon(const SpriteIcon& icon);
+ void setInactivityTimeout(InactivityTimeout inactivityTimeout);
+
+private:
+ static const size_t MAX_RECYCLED_SPRITES = 12;
+ static const size_t MAX_SPOTS = 12;
+
+ enum {
+ MSG_ANIMATE,
+ MSG_INACTIVITY_TIMEOUT,
+ };
+
+ struct Spot {
+ static const uint32_t INVALID_ID = 0xffffffff;
+
+ uint32_t id;
+ sp<Sprite> sprite;
+ float alpha;
+ float scale;
+ float x, y;
+
+ inline Spot(uint32_t id, const sp<Sprite>& sprite)
+ : id(id), sprite(sprite), alpha(1.0f), scale(1.0f),
+ x(0.0f), y(0.0f), lastIcon(NULL) { }
+
+ void updateSprite(const SpriteIcon* icon, float x, float y);
+
+ private:
+ const SpriteIcon* lastIcon;
+ };
+
+ mutable Mutex mLock;
+
+ sp<PointerControllerPolicyInterface> mPolicy;
+ sp<Looper> mLooper;
+ sp<SpriteController> mSpriteController;
+ sp<WeakMessageHandler> mHandler;
+
+ PointerResources mResources;
+
+ struct Locked {
+ bool animationPending;
+ nsecs_t animationTime;
+
+ int32_t displayWidth;
+ int32_t displayHeight;
+ int32_t displayOrientation;
+
+ InactivityTimeout inactivityTimeout;
+
+ Presentation presentation;
+ bool presentationChanged;
+
+ int32_t pointerFadeDirection;
+ float pointerX;
+ float pointerY;
+ float pointerAlpha;
+ sp<Sprite> pointerSprite;
+ SpriteIcon pointerIcon;
+ bool pointerIconChanged;
+
+ int32_t buttonState;
+
+ Vector<Spot*> spots;
+ Vector<sp<Sprite> > recycledSprites;
+ } mLocked;
+
+ bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ void setPositionLocked(float x, float y);
+
+ void handleMessage(const Message& message);
+ void doAnimate();
+ void doInactivityTimeout();
+
+ void startAnimationLocked();
+
+ void resetInactivityTimeoutLocked();
+ void removeInactivityTimeoutLocked();
+ void updatePointerLocked();
+
+ Spot* getSpotLocked(uint32_t id);
+ Spot* createAndAddSpotLocked(uint32_t id);
+ Spot* removeFirstFadingSpotLocked();
+ void releaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseAllSpotsLocked();
+
+ void loadResources();
+};
+
+} // namespace android
+
+#endif // _UI_POINTER_CONTROLLER_H
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
new file mode 100644
index 0000000..5391393
--- /dev/null
+++ b/libs/input/SpriteController.cpp
@@ -0,0 +1,482 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "Sprites"
+
+//#define LOG_NDEBUG 0
+
+#include "SpriteController.h"
+
+#include <cutils/log.h>
+#include <utils/String8.h>
+#include <gui/Surface.h>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+#include <SkXfermode.h>
+#include <android/native_window.h>
+
+namespace android {
+
+// --- SpriteController ---
+
+SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) :
+ mLooper(looper), mOverlayLayer(overlayLayer) {
+ mHandler = new WeakMessageHandler(this);
+
+ mLocked.transactionNestingCount = 0;
+ mLocked.deferredSpriteUpdate = false;
+}
+
+SpriteController::~SpriteController() {
+ mLooper->removeMessages(mHandler);
+
+ if (mSurfaceComposerClient != NULL) {
+ mSurfaceComposerClient->dispose();
+ mSurfaceComposerClient.clear();
+ }
+}
+
+sp<Sprite> SpriteController::createSprite() {
+ return new SpriteImpl(this);
+}
+
+void SpriteController::openTransaction() {
+ AutoMutex _l(mLock);
+
+ mLocked.transactionNestingCount += 1;
+}
+
+void SpriteController::closeTransaction() {
+ AutoMutex _l(mLock);
+
+ LOG_ALWAYS_FATAL_IF(mLocked.transactionNestingCount == 0,
+ "Sprite closeTransaction() called but there is no open sprite transaction");
+
+ mLocked.transactionNestingCount -= 1;
+ if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) {
+ mLocked.deferredSpriteUpdate = false;
+ mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ }
+}
+
+void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
+ bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
+ mLocked.invalidatedSprites.push(sprite);
+ if (wasEmpty) {
+ if (mLocked.transactionNestingCount != 0) {
+ mLocked.deferredSpriteUpdate = true;
+ } else {
+ mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ }
+ }
+}
+
+void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
+ bool wasEmpty = mLocked.disposedSurfaces.isEmpty();
+ mLocked.disposedSurfaces.push(surfaceControl);
+ if (wasEmpty) {
+ mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
+ }
+}
+
+void SpriteController::handleMessage(const Message& message) {
+ switch (message.what) {
+ case MSG_UPDATE_SPRITES:
+ doUpdateSprites();
+ break;
+ case MSG_DISPOSE_SURFACES:
+ doDisposeSurfaces();
+ break;
+ }
+}
+
+void SpriteController::doUpdateSprites() {
+ // Collect information about sprite updates.
+ // Each sprite update record includes a reference to its associated sprite so we can
+ // be certain the sprites will not be deleted while this function runs. Sprites
+ // may invalidate themselves again during this time but we will handle those changes
+ // in the next iteration.
+ Vector<SpriteUpdate> updates;
+ size_t numSprites;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ numSprites = mLocked.invalidatedSprites.size();
+ for (size_t i = 0; i < numSprites; i++) {
+ const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i);
+
+ updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
+ sprite->resetDirtyLocked();
+ }
+ mLocked.invalidatedSprites.clear();
+ } // release lock
+
+ // Create missing surfaces.
+ bool surfaceChanged = false;
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+
+ if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) {
+ update.state.surfaceWidth = update.state.icon.bitmap.width();
+ update.state.surfaceHeight = update.state.icon.bitmap.height();
+ update.state.surfaceDrawn = false;
+ update.state.surfaceVisible = false;
+ update.state.surfaceControl = obtainSurface(
+ update.state.surfaceWidth, update.state.surfaceHeight);
+ if (update.state.surfaceControl != NULL) {
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+
+ // Resize sprites if needed, inside a global transaction.
+ bool haveGlobalTransaction = false;
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+
+ if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
+ int32_t desiredWidth = update.state.icon.bitmap.width();
+ int32_t desiredHeight = update.state.icon.bitmap.height();
+ if (update.state.surfaceWidth < desiredWidth
+ || update.state.surfaceHeight < desiredHeight) {
+ if (!haveGlobalTransaction) {
+ SurfaceComposerClient::openGlobalTransaction();
+ haveGlobalTransaction = true;
+ }
+
+ status_t status = update.state.surfaceControl->setSize(desiredWidth, desiredHeight);
+ if (status) {
+ ALOGE("Error %d resizing sprite surface from %dx%d to %dx%d",
+ status, update.state.surfaceWidth, update.state.surfaceHeight,
+ desiredWidth, desiredHeight);
+ } else {
+ update.state.surfaceWidth = desiredWidth;
+ update.state.surfaceHeight = desiredHeight;
+ update.state.surfaceDrawn = false;
+ update.surfaceChanged = surfaceChanged = true;
+
+ if (update.state.surfaceVisible) {
+ status = update.state.surfaceControl->hide();
+ if (status) {
+ ALOGE("Error %d hiding sprite surface after resize.", status);
+ } else {
+ update.state.surfaceVisible = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (haveGlobalTransaction) {
+ SurfaceComposerClient::closeGlobalTransaction();
+ }
+
+ // Redraw sprites if needed.
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+
+ if ((update.state.dirty & DIRTY_BITMAP) && update.state.surfaceDrawn) {
+ update.state.surfaceDrawn = false;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+
+ if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn
+ && update.state.wantSurfaceVisible()) {
+ sp<Surface> surface = update.state.surfaceControl->getSurface();
+ ANativeWindow_Buffer outBuffer;
+ status_t status = surface->lock(&outBuffer, NULL);
+ if (status) {
+ ALOGE("Error %d locking sprite surface before drawing.", status);
+ } else {
+ SkBitmap surfaceBitmap;
+ ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
+ surfaceBitmap.installPixels(SkImageInfo::MakeN32Premul(outBuffer.width, outBuffer.height),
+ outBuffer.bits, bpr);
+
+ SkCanvas surfaceCanvas(surfaceBitmap);
+
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
+
+ if (outBuffer.width > update.state.icon.bitmap.width()) {
+ paint.setColor(0); // transparent fill color
+ surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0,
+ outBuffer.width, update.state.icon.bitmap.height(), paint);
+ }
+ if (outBuffer.height > update.state.icon.bitmap.height()) {
+ paint.setColor(0); // transparent fill color
+ surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(),
+ outBuffer.width, outBuffer.height, paint);
+ }
+
+ status = surface->unlockAndPost();
+ if (status) {
+ ALOGE("Error %d unlocking and posting sprite surface after drawing.", status);
+ } else {
+ update.state.surfaceDrawn = true;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+ }
+
+ // Set sprite surface properties and make them visible.
+ bool haveTransaction = false;
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+
+ bool wantSurfaceVisibleAndDrawn = update.state.wantSurfaceVisible()
+ && update.state.surfaceDrawn;
+ bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible;
+ bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible;
+ if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
+ || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
+ | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
+ | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) {
+ status_t status;
+ if (!haveTransaction) {
+ SurfaceComposerClient::openGlobalTransaction();
+ haveTransaction = true;
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & DIRTY_ALPHA))) {
+ status = update.state.surfaceControl->setAlpha(update.state.alpha);
+ if (status) {
+ ALOGE("Error %d setting sprite surface alpha.", status);
+ }
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & (DIRTY_POSITION
+ | DIRTY_HOTSPOT)))) {
+ status = update.state.surfaceControl->setPosition(
+ update.state.positionX - update.state.icon.hotSpotX,
+ update.state.positionY - update.state.icon.hotSpotY);
+ if (status) {
+ ALOGE("Error %d setting sprite surface position.", status);
+ }
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible
+ || (update.state.dirty & DIRTY_TRANSFORMATION_MATRIX))) {
+ status = update.state.surfaceControl->setMatrix(
+ update.state.transformationMatrix.dsdx,
+ update.state.transformationMatrix.dtdx,
+ update.state.transformationMatrix.dsdy,
+ update.state.transformationMatrix.dtdy);
+ if (status) {
+ ALOGE("Error %d setting sprite surface transformation matrix.", status);
+ }
+ }
+
+ int32_t surfaceLayer = mOverlayLayer + update.state.layer;
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) {
+ status = update.state.surfaceControl->setLayer(surfaceLayer);
+ if (status) {
+ ALOGE("Error %d setting sprite surface layer.", status);
+ }
+ }
+
+ if (becomingVisible) {
+ status = update.state.surfaceControl->show();
+ if (status) {
+ ALOGE("Error %d showing sprite surface.", status);
+ } else {
+ update.state.surfaceVisible = true;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ } else if (becomingHidden) {
+ status = update.state.surfaceControl->hide();
+ if (status) {
+ ALOGE("Error %d hiding sprite surface.", status);
+ } else {
+ update.state.surfaceVisible = false;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+ }
+
+ if (haveTransaction) {
+ SurfaceComposerClient::closeGlobalTransaction();
+ }
+
+ // If any surfaces were changed, write back the new surface properties to the sprites.
+ if (surfaceChanged) { // acquire lock
+ AutoMutex _l(mLock);
+
+ for (size_t i = 0; i < numSprites; i++) {
+ const SpriteUpdate& update = updates.itemAt(i);
+
+ if (update.surfaceChanged) {
+ update.sprite->setSurfaceLocked(update.state.surfaceControl,
+ update.state.surfaceWidth, update.state.surfaceHeight,
+ update.state.surfaceDrawn, update.state.surfaceVisible);
+ }
+ }
+ } // release lock
+
+ // Clear the sprite update vector outside the lock. It is very important that
+ // we do not clear sprite references inside the lock since we could be releasing
+ // the last remaining reference to the sprite here which would result in the
+ // sprite being deleted and the lock being reacquired by the sprite destructor
+ // while already held.
+ updates.clear();
+}
+
+void SpriteController::doDisposeSurfaces() {
+ // Collect disposed surfaces.
+ Vector<sp<SurfaceControl> > disposedSurfaces;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ disposedSurfaces = mLocked.disposedSurfaces;
+ mLocked.disposedSurfaces.clear();
+ } // release lock
+
+ // Release the last reference to each surface outside of the lock.
+ // We don't want the surfaces to be deleted while we are holding our lock.
+ disposedSurfaces.clear();
+}
+
+void SpriteController::ensureSurfaceComposerClient() {
+ if (mSurfaceComposerClient == NULL) {
+ mSurfaceComposerClient = new SurfaceComposerClient();
+ }
+}
+
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height) {
+ ensureSurfaceComposerClient();
+
+ sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
+ String8("Sprite"), width, height, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eHidden |
+ ISurfaceComposerClient::eCursorWindow);
+ if (surfaceControl == NULL || !surfaceControl->isValid()) {
+ ALOGE("Error creating sprite surface.");
+ return NULL;
+ }
+ return surfaceControl;
+}
+
+
+// --- SpriteController::SpriteImpl ---
+
+SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
+ mController(controller) {
+}
+
+SpriteController::SpriteImpl::~SpriteImpl() {
+ AutoMutex _m(mController->mLock);
+
+ // Let the controller take care of deleting the last reference to sprite
+ // surfaces so that we do not block the caller on an IPC here.
+ if (mLocked.state.surfaceControl != NULL) {
+ mController->disposeSurfaceLocked(mLocked.state.surfaceControl);
+ mLocked.state.surfaceControl.clear();
+ }
+}
+
+void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
+ AutoMutex _l(mController->mLock);
+
+ uint32_t dirty;
+ if (icon.isValid()) {
+ icon.bitmap.copyTo(&mLocked.state.icon.bitmap, kN32_SkColorType);
+
+ if (!mLocked.state.icon.isValid()
+ || mLocked.state.icon.hotSpotX != icon.hotSpotX
+ || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+ mLocked.state.icon.hotSpotX = icon.hotSpotX;
+ mLocked.state.icon.hotSpotY = icon.hotSpotY;
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ } else {
+ dirty = DIRTY_BITMAP;
+ }
+ } else if (mLocked.state.icon.isValid()) {
+ mLocked.state.icon.bitmap.reset();
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ } else {
+ return; // setting to invalid icon and already invalid so nothing to do
+ }
+
+ invalidateLocked(dirty);
+}
+
+void SpriteController::SpriteImpl::setVisible(bool visible) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.visible != visible) {
+ mLocked.state.visible = visible;
+ invalidateLocked(DIRTY_VISIBILITY);
+ }
+}
+
+void SpriteController::SpriteImpl::setPosition(float x, float y) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.positionX != x || mLocked.state.positionY != y) {
+ mLocked.state.positionX = x;
+ mLocked.state.positionY = y;
+ invalidateLocked(DIRTY_POSITION);
+ }
+}
+
+void SpriteController::SpriteImpl::setLayer(int32_t layer) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.layer != layer) {
+ mLocked.state.layer = layer;
+ invalidateLocked(DIRTY_LAYER);
+ }
+}
+
+void SpriteController::SpriteImpl::setAlpha(float alpha) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.alpha != alpha) {
+ mLocked.state.alpha = alpha;
+ invalidateLocked(DIRTY_ALPHA);
+ }
+}
+
+void SpriteController::SpriteImpl::setTransformationMatrix(
+ const SpriteTransformationMatrix& matrix) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.transformationMatrix != matrix) {
+ mLocked.state.transformationMatrix = matrix;
+ invalidateLocked(DIRTY_TRANSFORMATION_MATRIX);
+ }
+}
+
+void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
+ bool wasDirty = mLocked.state.dirty;
+ mLocked.state.dirty |= dirty;
+
+ if (!wasDirty) {
+ mController->invalidateSpriteLocked(this);
+ }
+}
+
+} // namespace android
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
new file mode 100644
index 0000000..905833c
--- /dev/null
+++ b/libs/input/SpriteController.h
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_SPRITES_H
+#define _UI_SPRITES_H
+
+#include <utils/RefBase.h>
+#include <utils/Looper.h>
+
+#include <gui/SurfaceComposerClient.h>
+
+#include <SkBitmap.h>
+
+namespace android {
+
+/*
+ * Transformation matrix for a sprite.
+ */
+struct SpriteTransformationMatrix {
+ inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { }
+ inline SpriteTransformationMatrix(float dsdx, float dtdx, float dsdy, float dtdy) :
+ dsdx(dsdx), dtdx(dtdx), dsdy(dsdy), dtdy(dtdy) { }
+
+ float dsdx;
+ float dtdx;
+ float dsdy;
+ float dtdy;
+
+ inline bool operator== (const SpriteTransformationMatrix& other) {
+ return dsdx == other.dsdx
+ && dtdx == other.dtdx
+ && dsdy == other.dsdy
+ && dtdy == other.dtdy;
+ }
+
+ inline bool operator!= (const SpriteTransformationMatrix& other) {
+ return !(*this == other);
+ }
+};
+
+/*
+ * Icon that a sprite displays, including its hotspot.
+ */
+struct SpriteIcon {
+ inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { }
+ inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) :
+ bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { }
+
+ SkBitmap bitmap;
+ float hotSpotX;
+ float hotSpotY;
+
+ inline SpriteIcon copy() const {
+ SkBitmap bitmapCopy;
+ bitmap.copyTo(&bitmapCopy, kN32_SkColorType);
+ return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY);
+ }
+
+ inline void reset() {
+ bitmap.reset();
+ hotSpotX = 0;
+ hotSpotY = 0;
+ }
+
+ inline bool isValid() const {
+ return !bitmap.isNull() && !bitmap.empty();
+ }
+};
+
+/*
+ * A sprite is a simple graphical object that is displayed on-screen above other layers.
+ * The basic sprite class is an interface.
+ * The implementation is provided by the sprite controller.
+ */
+class Sprite : public RefBase {
+protected:
+ Sprite() { }
+ virtual ~Sprite() { }
+
+public:
+ enum {
+ // The base layer for pointer sprites.
+ BASE_LAYER_POINTER = 0, // reserve space for 1 pointer
+
+ // The base layer for spot sprites.
+ BASE_LAYER_SPOT = 1, // reserve space for MAX_POINTER_ID spots
+ };
+
+ /* Sets the bitmap that is drawn by the sprite.
+ * The sprite retains a copy of the bitmap for subsequent rendering. */
+ virtual void setIcon(const SpriteIcon& icon) = 0;
+
+ inline void clearIcon() {
+ setIcon(SpriteIcon());
+ }
+
+ /* Sets whether the sprite is visible. */
+ virtual void setVisible(bool visible) = 0;
+
+ /* Sets the sprite position on screen, relative to the sprite's hot spot. */
+ virtual void setPosition(float x, float y) = 0;
+
+ /* Sets the layer of the sprite, relative to the system sprite overlay layer.
+ * Layer 0 is the overlay layer, > 0 appear above this layer. */
+ virtual void setLayer(int32_t layer) = 0;
+
+ /* Sets the sprite alpha blend ratio between 0.0 and 1.0. */
+ virtual void setAlpha(float alpha) = 0;
+
+ /* Sets the sprite transformation matrix. */
+ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
+};
+
+/*
+ * Displays sprites on the screen.
+ *
+ * This interface is used by PointerController and SpotController to draw pointers or
+ * spot representations of fingers. It is not intended for general purpose use
+ * by other components.
+ *
+ * All sprite position updates and rendering is performed asynchronously.
+ *
+ * Clients are responsible for animating sprites by periodically updating their properties.
+ */
+class SpriteController : public MessageHandler {
+protected:
+ virtual ~SpriteController();
+
+public:
+ SpriteController(const sp<Looper>& looper, int32_t overlayLayer);
+
+ /* Creates a new sprite, initially invisible. */
+ sp<Sprite> createSprite();
+
+ /* Opens or closes a transaction to perform a batch of sprite updates as part of
+ * a single operation such as setPosition and setAlpha. It is not necessary to
+ * open a transaction when updating a single property.
+ * Calls to openTransaction() nest and must be matched by an equal number
+ * of calls to closeTransaction(). */
+ void openTransaction();
+ void closeTransaction();
+
+private:
+ enum {
+ MSG_UPDATE_SPRITES,
+ MSG_DISPOSE_SURFACES,
+ };
+
+ enum {
+ DIRTY_BITMAP = 1 << 0,
+ DIRTY_ALPHA = 1 << 1,
+ DIRTY_POSITION = 1 << 2,
+ DIRTY_TRANSFORMATION_MATRIX = 1 << 3,
+ DIRTY_LAYER = 1 << 4,
+ DIRTY_VISIBILITY = 1 << 5,
+ DIRTY_HOTSPOT = 1 << 6,
+ };
+
+ /* Describes the state of a sprite.
+ * This structure is designed so that it can be copied during updates so that
+ * surfaces can be resized and redrawn without blocking the client by holding a lock
+ * on the sprites for a long time.
+ * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */
+ struct SpriteState {
+ inline SpriteState() :
+ dirty(0), visible(false),
+ positionX(0), positionY(0), layer(0), alpha(1.0f),
+ surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
+ }
+
+ uint32_t dirty;
+
+ SpriteIcon icon;
+ bool visible;
+ float positionX;
+ float positionY;
+ int32_t layer;
+ float alpha;
+ SpriteTransformationMatrix transformationMatrix;
+
+ sp<SurfaceControl> surfaceControl;
+ int32_t surfaceWidth;
+ int32_t surfaceHeight;
+ bool surfaceDrawn;
+ bool surfaceVisible;
+
+ inline bool wantSurfaceVisible() const {
+ return visible && alpha > 0.0f && icon.isValid();
+ }
+ };
+
+ /* Client interface for a sprite.
+ * Requests acquire a lock on the controller, update local state and request the
+ * controller to invalidate the sprite.
+ * The real heavy lifting of creating, resizing and redrawing surfaces happens
+ * asynchronously with no locks held except in short critical section to copy
+ * the sprite state before the work and update the sprite surface control afterwards.
+ */
+ class SpriteImpl : public Sprite {
+ protected:
+ virtual ~SpriteImpl();
+
+ public:
+ SpriteImpl(const sp<SpriteController> controller);
+
+ virtual void setIcon(const SpriteIcon& icon);
+ virtual void setVisible(bool visible);
+ virtual void setPosition(float x, float y);
+ virtual void setLayer(int32_t layer);
+ virtual void setAlpha(float alpha);
+ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
+
+ inline const SpriteState& getStateLocked() const {
+ return mLocked.state;
+ }
+
+ inline void resetDirtyLocked() {
+ mLocked.state.dirty = 0;
+ }
+
+ inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl,
+ int32_t width, int32_t height, bool drawn, bool visible) {
+ mLocked.state.surfaceControl = surfaceControl;
+ mLocked.state.surfaceWidth = width;
+ mLocked.state.surfaceHeight = height;
+ mLocked.state.surfaceDrawn = drawn;
+ mLocked.state.surfaceVisible = visible;
+ }
+
+ private:
+ sp<SpriteController> mController;
+
+ struct Locked {
+ SpriteState state;
+ } mLocked; // guarded by mController->mLock
+
+ void invalidateLocked(uint32_t dirty);
+ };
+
+ /* Stores temporary information collected during the sprite update cycle. */
+ struct SpriteUpdate {
+ inline SpriteUpdate() : surfaceChanged(false) { }
+ inline SpriteUpdate(const sp<SpriteImpl> sprite, const SpriteState& state) :
+ sprite(sprite), state(state), surfaceChanged(false) {
+ }
+
+ sp<SpriteImpl> sprite;
+ SpriteState state;
+ bool surfaceChanged;
+ };
+
+ mutable Mutex mLock;
+
+ sp<Looper> mLooper;
+ const int32_t mOverlayLayer;
+ sp<WeakMessageHandler> mHandler;
+
+ sp<SurfaceComposerClient> mSurfaceComposerClient;
+
+ struct Locked {
+ Vector<sp<SpriteImpl> > invalidatedSprites;
+ Vector<sp<SurfaceControl> > disposedSurfaces;
+ uint32_t transactionNestingCount;
+ bool deferredSpriteUpdate;
+ } mLocked; // guarded by mLock
+
+ void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
+ void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
+
+ void handleMessage(const Message& message);
+ void doUpdateSprites();
+ void doDisposeSurfaces();
+
+ void ensureSurfaceComposerClient();
+ sp<SurfaceControl> obtainSurface(int32_t width, int32_t height);
+};
+
+} // namespace android
+
+#endif // _UI_SPRITES_H
diff --git a/libs/usb/tests/AccessoryChat/accessorychat/Android.mk b/libs/usb/tests/AccessoryChat/accessorychat/Android.mk
index 300224a..3e07155 100644
--- a/libs/usb/tests/AccessoryChat/accessorychat/Android.mk
+++ b/libs/usb/tests/AccessoryChat/accessorychat/Android.mk
@@ -11,7 +11,6 @@ LOCAL_SRC_FILES := accessorychat.c
LOCAL_MODULE := accessorychat
-LOCAL_C_INCLUDES += bionic/libc/kernel/common
LOCAL_STATIC_LIBRARIES := libusbhost libcutils
LOCAL_LDLIBS += -lpthread
LOCAL_CFLAGS := -g -O0
diff --git a/libs/usb/tests/AccessoryChat/accessorychat/linux/usb/f_accessory.h b/libs/usb/tests/AccessoryChat/accessorychat/linux/usb/f_accessory.h
new file mode 100644
index 0000000..75e017c
--- /dev/null
+++ b/libs/usb/tests/AccessoryChat/accessorychat/linux/usb/f_accessory.h
@@ -0,0 +1,53 @@
+/****************************************************************************
+ ****************************************************************************
+ ***
+ *** This header was automatically generated from a Linux kernel header
+ *** of the same name, to make information necessary for userspace to
+ *** call into the kernel available to libc. It contains only constants,
+ *** structures, and macros generated from the original header, and thus,
+ *** contains no copyrightable information.
+ ***
+ *** To edit the content of this header, modify the corresponding
+ *** source file (e.g. under external/kernel-headers/original/) then
+ *** run bionic/libc/kernel/tools/update_all.py
+ ***
+ *** Any manual change here will be lost the next time this script will
+ *** be run. You've been warned!
+ ***
+ ****************************************************************************
+ ****************************************************************************/
+#ifndef _UAPI_LINUX_USB_F_ACCESSORY_H
+#define _UAPI_LINUX_USB_F_ACCESSORY_H
+#define USB_ACCESSORY_VENDOR_ID 0x18D1
+#define USB_ACCESSORY_PRODUCT_ID 0x2D00
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01
+#define ACCESSORY_STRING_MANUFACTURER 0
+#define ACCESSORY_STRING_MODEL 1
+#define ACCESSORY_STRING_DESCRIPTION 2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ACCESSORY_STRING_VERSION 3
+#define ACCESSORY_STRING_URI 4
+#define ACCESSORY_STRING_SERIAL 5
+#define ACCESSORY_GET_PROTOCOL 51
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ACCESSORY_SEND_STRING 52
+#define ACCESSORY_START 53
+#define ACCESSORY_REGISTER_HID 54
+#define ACCESSORY_UNREGISTER_HID 55
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ACCESSORY_SET_HID_REPORT_DESC 56
+#define ACCESSORY_SEND_HID_EVENT 57
+#define ACCESSORY_SET_AUDIO_MODE 58
+#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256])
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256])
+#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256])
+#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256])
+#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256])
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256])
+#define ACCESSORY_IS_START_REQUESTED _IO('M', 7)
+#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8)
+#endif
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
diff --git a/libs/usb/tests/accessorytest/Android.mk b/libs/usb/tests/accessorytest/Android.mk
new file mode 100644
index 0000000..6d9a946
--- /dev/null
+++ b/libs/usb/tests/accessorytest/Android.mk
@@ -0,0 +1,25 @@
+LOCAL_PATH:= $(call my-dir)
+
+# Build for Linux host only
+ifeq ($(HOST_OS),linux)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := accessory.c \
+ audio.c \
+ hid.c \
+ usb.c
+
+LOCAL_C_INCLUDES += external/tinyalsa/include
+
+LOCAL_MODULE := accessorytest
+
+LOCAL_STATIC_LIBRARIES := libusbhost libcutils libtinyalsa
+LOCAL_LDLIBS += -lpthread
+LOCAL_CFLAGS := -g -O0
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif
diff --git a/libs/usb/tests/accessorytest/accessory.c b/libs/usb/tests/accessorytest/accessory.c
new file mode 100644
index 0000000..a3c47af
--- /dev/null
+++ b/libs/usb/tests/accessorytest/accessory.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2012 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "accessory.h"
+
+static void usage(char* name) {
+ fprintf(stderr, "Usage: %s [-a] [-h] [-ic input card] [-id input device] "
+ "[-oc output card] [-d output device] [-a] [-h] [-i]\n\n"
+ "\t-ic, -id, -oc and -od specify ALSA card and device numbers\n"
+ "\t-a : enables AccessoryChat mode\n"
+ "\t-i : enables HID pass through (requires running as root\n"
+ "\t-h : prints this usage message\n", name);
+}
+
+int main(int argc, char* argv[])
+{
+ unsigned int input_card = 2;
+ unsigned int input_device = 0;
+ unsigned int output_card = 0;
+ unsigned int output_device = 0;
+ unsigned int enable_accessory = 0;
+ unsigned int enable_hid = 0;
+
+ /* parse command line arguments */
+ argv += 1;
+ while (*argv) {
+ if (strcmp(*argv, "-ic") == 0) {
+ argv++;
+ if (*argv)
+ input_card = atoi(*argv);
+ } else if (strcmp(*argv, "-id") == 0) {
+ argv++;
+ if (*argv)
+ input_device = atoi(*argv);
+ } else if (strcmp(*argv, "-oc") == 0) {
+ argv++;
+ if (*argv)
+ output_card = atoi(*argv);
+ } else if (strcmp(*argv, "-od") == 0) {
+ argv++;
+ if (*argv)
+ output_device = atoi(*argv);
+ } else if (strcmp(*argv, "-a") == 0) {
+ enable_accessory = 1;
+ } else if (strcmp(*argv, "-h") == 0) {
+ usage(argv[0]);
+ return 1;
+ } else if (strcmp(*argv, "-i") == 0) {
+ enable_hid = 1;
+ }
+ if (*argv)
+ argv++;
+ }
+
+ init_audio(input_card, input_device, output_card, output_device);
+ if (enable_hid)
+ init_hid();
+ usb_run(enable_accessory);
+
+ return 0;
+}
diff --git a/libs/usb/tests/accessorytest/accessory.h b/libs/usb/tests/accessorytest/accessory.h
new file mode 100644
index 0000000..55c4550
--- /dev/null
+++ b/libs/usb/tests/accessorytest/accessory.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ACCESSORY_H__
+#define __ACCESSORY_H__
+
+int init_audio(unsigned int ic, unsigned int id, unsigned int oc, unsigned int od);
+void init_hid();
+void usb_run(int enable_accessory);
+
+struct usb_device* usb_wait_for_device();
+
+#endif /* __ACCESSORY_H__ */
diff --git a/libs/usb/tests/accessorytest/audio.c b/libs/usb/tests/accessorytest/audio.c
new file mode 100644
index 0000000..d23d9b3
--- /dev/null
+++ b/libs/usb/tests/accessorytest/audio.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2012 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <signal.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <errno.h>
+#include <string.h>
+
+#include <tinyalsa/asoundlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#include "accessory.h"
+
+#define BUFFER_COUNT 2
+#define BUFFER_SIZE 16384
+
+#define BUFFER_EMPTY 0
+#define BUFFER_BUSY 1
+#define BUFFER_FULL 2
+
+static char* buffers[BUFFER_COUNT];
+static int buffer_states[BUFFER_COUNT];
+static int empty_index = 0;
+static int full_index = -1;
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t empty_cond = PTHREAD_COND_INITIALIZER;
+static pthread_cond_t full_cond = PTHREAD_COND_INITIALIZER;
+
+static unsigned int input_card;
+static unsigned int input_device;
+
+static int get_empty()
+{
+ int index, other;
+
+ pthread_mutex_lock(&mutex);
+
+ while (empty_index == -1)
+ pthread_cond_wait(&empty_cond, &mutex);
+
+ index = empty_index;
+ other = (index == 0 ? 1 : 0);
+ buffer_states[index] = BUFFER_BUSY;
+ if (buffer_states[other] == BUFFER_EMPTY)
+ empty_index = other;
+ else
+ empty_index = -1;
+
+ pthread_mutex_unlock(&mutex);
+ return index;
+}
+
+static void put_empty(int index)
+{
+ pthread_mutex_lock(&mutex);
+
+ buffer_states[index] = BUFFER_EMPTY;
+ if (empty_index == -1) {
+ empty_index = index;
+ pthread_cond_signal(&empty_cond);
+ }
+
+ pthread_mutex_unlock(&mutex);
+}
+
+static int get_full()
+{
+ int index, other;
+
+ pthread_mutex_lock(&mutex);
+
+ while (full_index == -1)
+ pthread_cond_wait(&full_cond, &mutex);
+
+ index = full_index;
+ other = (index == 0 ? 1 : 0);
+ buffer_states[index] = BUFFER_BUSY;
+ if (buffer_states[other] == BUFFER_FULL)
+ full_index = other;
+ else
+ full_index = -1;
+
+ pthread_mutex_unlock(&mutex);
+ return index;
+}
+
+static void put_full(int index)
+{
+ pthread_mutex_lock(&mutex);
+
+ buffer_states[index] = BUFFER_FULL;
+ if (full_index == -1) {
+ full_index = index;
+ pthread_cond_signal(&full_cond);
+ }
+
+ pthread_mutex_unlock(&mutex);
+}
+
+static void* capture_thread(void* arg)
+{
+ struct pcm_config config;
+ struct pcm *pcm = NULL;
+
+ fprintf(stderr, "capture_thread start\n");
+
+ memset(&config, 0, sizeof(config));
+
+ config.channels = 2;
+ config.rate = 44100;
+ config.period_size = 1024;
+ config.period_count = 4;
+ config.format = PCM_FORMAT_S16_LE;
+
+ while (1) {
+ while (!pcm) {
+ pcm = pcm_open(input_card, input_device, PCM_IN, &config);
+ if (pcm && !pcm_is_ready(pcm)) {
+ pcm_close(pcm);
+ pcm = NULL;
+ }
+ if (!pcm)
+ sleep(1);
+ }
+
+ while (pcm) {
+ int index = get_empty();
+ if (pcm_read(pcm, buffers[index], BUFFER_SIZE)) {
+ put_empty(index);
+ pcm_close(pcm);
+ pcm = NULL;
+ } else {
+ put_full(index);
+ }
+ }
+ }
+
+ fprintf(stderr, "capture_thread done\n");
+ return NULL;
+}
+
+static void* play_thread(void* arg)
+{
+ struct pcm *pcm = arg;
+ char *buffer;
+ int index, err;
+
+ fprintf(stderr, "play_thread start\n");
+
+ while (1) {
+ index = get_full();
+
+ err = pcm_write(pcm, buffers[index], BUFFER_SIZE);
+ if (err)
+ fprintf(stderr, "pcm_write err: %d\n", err);
+
+ put_empty(index);
+ }
+
+ fprintf(stderr, "play_thread done\n");
+ pcm_close(pcm);
+ free(buffer);
+
+ return NULL;
+}
+
+int init_audio(unsigned int ic, unsigned int id, unsigned int oc, unsigned int od)
+{
+ pthread_t tid;
+ struct pcm_config config;
+ struct pcm *pcm;
+ int i;
+
+ input_card = ic;
+ input_device = id;
+
+ for (i = 0; i < BUFFER_COUNT; i++) {
+ buffers[i] = malloc(BUFFER_SIZE);
+ buffer_states[i] = BUFFER_EMPTY;
+ }
+
+ memset(&config, 0, sizeof(config));
+ config.channels = 2;
+ config.rate = 44100;
+ config.period_size = 1024;
+ config.period_count = 4;
+ config.format = PCM_FORMAT_S16_LE;
+
+ pcm = pcm_open(oc, od, PCM_OUT, &config);
+ if (!pcm || !pcm_is_ready(pcm)) {
+ fprintf(stderr, "Unable to open PCM device %d/%d for output (%s)\n",
+ oc, od, pcm_get_error(pcm));
+ return -1;
+ }
+
+ pthread_create(&tid, NULL, capture_thread, NULL);
+ pthread_create(&tid, NULL, play_thread, pcm);
+ return 0;
+}
diff --git a/libs/usb/tests/accessorytest/f_accessory.h b/libs/usb/tests/accessorytest/f_accessory.h
new file mode 100644
index 0000000..312f4ba
--- /dev/null
+++ b/libs/usb/tests/accessorytest/f_accessory.h
@@ -0,0 +1,148 @@
+/*
+ * Gadget Function Driver for Android USB accessories
+ *
+ * Copyright (C) 2011 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __LINUX_USB_F_ACCESSORY_H
+#define __LINUX_USB_F_ACCESSORY_H
+
+/* Use Google Vendor ID when in accessory mode */
+#define USB_ACCESSORY_VENDOR_ID 0x18D1
+
+
+/* Product ID to use when in accessory mode */
+#define USB_ACCESSORY_PRODUCT_ID 0x2D00
+
+/* Product ID to use when in accessory mode and adb is enabled */
+#define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01
+
+/* Indexes for strings sent by the host via ACCESSORY_SEND_STRING */
+#define ACCESSORY_STRING_MANUFACTURER 0
+#define ACCESSORY_STRING_MODEL 1
+#define ACCESSORY_STRING_DESCRIPTION 2
+#define ACCESSORY_STRING_VERSION 3
+#define ACCESSORY_STRING_URI 4
+#define ACCESSORY_STRING_SERIAL 5
+
+/* Control request for retrieving device's protocol version
+ *
+ * requestType: USB_DIR_IN | USB_TYPE_VENDOR
+ * request: ACCESSORY_GET_PROTOCOL
+ * value: 0
+ * index: 0
+ * data version number (16 bits little endian)
+ * 1 for original accessory support
+ * 2 adds audio and HID support
+ */
+#define ACCESSORY_GET_PROTOCOL 51
+
+/* Control request for host to send a string to the device
+ *
+ * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
+ * request: ACCESSORY_SEND_STRING
+ * value: 0
+ * index: string ID
+ * data zero terminated UTF8 string
+ *
+ * The device can later retrieve these strings via the
+ * ACCESSORY_GET_STRING_* ioctls
+ */
+#define ACCESSORY_SEND_STRING 52
+
+/* Control request for starting device in accessory mode.
+ * The host sends this after setting all its strings to the device.
+ *
+ * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
+ * request: ACCESSORY_START
+ * value: 0
+ * index: 0
+ * data none
+ */
+#define ACCESSORY_START 53
+
+/* Control request for registering a HID device.
+ * Upon registering, a unique ID is sent by the accessory in the
+ * value parameter. This ID will be used for future commands for
+ * the device
+ *
+ * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
+ * request: ACCESSORY_REGISTER_HID_DEVICE
+ * value: Accessory assigned ID for the HID device
+ * index: total length of the HID report descriptor
+ * data none
+ */
+#define ACCESSORY_REGISTER_HID 54
+
+/* Control request for unregistering a HID device.
+ *
+ * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
+ * request: ACCESSORY_REGISTER_HID
+ * value: Accessory assigned ID for the HID device
+ * index: 0
+ * data none
+ */
+#define ACCESSORY_UNREGISTER_HID 55
+
+/* Control request for sending the HID report descriptor.
+ * If the HID descriptor is longer than the endpoint zero max packet size,
+ * the descriptor will be sent in multiple ACCESSORY_SET_HID_REPORT_DESC
+ * commands. The data for the descriptor must be sent sequentially
+ * if multiple packets are needed.
+ *
+ * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
+ * request: ACCESSORY_SET_HID_REPORT_DESC
+ * value: Accessory assigned ID for the HID device
+ * index: offset of data in descriptor
+ * (needed when HID descriptor is too big for one packet)
+ * data the HID report descriptor
+ */
+#define ACCESSORY_SET_HID_REPORT_DESC 56
+
+/* Control request for sending HID events.
+ *
+ * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
+ * request: ACCESSORY_SEND_HID_EVENT
+ * value: Accessory assigned ID for the HID device
+ * index: 0
+ * data the HID report for the event
+ */
+#define ACCESSORY_SEND_HID_EVENT 57
+
+/* Control request for setting the audio mode.
+ *
+ * requestType: USB_DIR_OUT | USB_TYPE_VENDOR
+ * request: ACCESSORY_SET_AUDIO_MODE
+ * value: 0 - no audio
+ * 1 - device to host, 44100 16-bit stereo PCM
+ * index: 0
+ * data the HID report for the event
+ */
+#define ACCESSORY_SET_AUDIO_MODE 58
+
+
+
+/* ioctls for retrieving strings set by the host */
+#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256])
+#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256])
+#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256])
+#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256])
+#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256])
+#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256])
+/* returns 1 if there is a start request pending */
+#define ACCESSORY_IS_START_REQUESTED _IO('M', 7)
+/* returns audio mode (set via the ACCESSORY_SET_AUDIO_MODE control request) */
+#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8)
+
+#endif /* __LINUX_USB_F_ACCESSORY_H */
diff --git a/libs/usb/tests/accessorytest/hid.c b/libs/usb/tests/accessorytest/hid.c
new file mode 100644
index 0000000..b70d678
--- /dev/null
+++ b/libs/usb/tests/accessorytest/hid.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2012 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 <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <pthread.h>
+#include <time.h>
+
+#include <sys/inotify.h>
+#include <linux/hidraw.h>
+#include <usbhost/usbhost.h>
+
+#include "f_accessory.h"
+#include "accessory.h"
+
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static int next_id = 1;
+
+static void milli_sleep(int millis) {
+ struct timespec tm;
+
+ tm.tv_sec = 0;
+ tm.tv_nsec = millis * 1000000;
+ nanosleep(&tm, NULL);
+}
+
+static void* hid_thread(void* arg) {
+ int fd = (int)arg;
+ char buffer[4096];
+ int id, ret, offset;
+ struct usb_device *device;
+ struct usb_device_descriptor *device_desc;
+ int max_packet;
+ struct hidraw_report_descriptor desc;
+ int desc_length;
+
+ fprintf(stderr, "hid_thread start fd: %d\n", fd);
+
+ if (ioctl(fd, HIDIOCGRDESCSIZE, &desc_length)) {
+ fprintf(stderr, "HIDIOCGRDESCSIZE failed\n");
+ close(fd);
+ goto err;
+ }
+
+ desc.size = HID_MAX_DESCRIPTOR_SIZE - 1;
+ if (ioctl(fd, HIDIOCGRDESC, &desc)) {
+ fprintf(stderr, "HIDIOCGRDESC failed\n");
+ close(fd);
+ goto err;
+ }
+
+wait_for_device:
+ fprintf(stderr, "waiting for device fd: %d\n", fd);
+ device = usb_wait_for_device();
+ max_packet = usb_device_get_device_descriptor(device)->bMaxPacketSize0;
+ // FIXME
+ max_packet--;
+
+ // FIXME
+ milli_sleep(500);
+
+ pthread_mutex_lock(&mutex);
+ id = next_id++;
+
+ ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR,
+ ACCESSORY_REGISTER_HID, id, desc_length, NULL, 0, 1000);
+ fprintf(stderr, "ACCESSORY_REGISTER_HID returned %d\n", ret);
+
+ // FIXME
+ milli_sleep(500);
+
+ for (offset = 0; offset < desc_length; ) {
+ int count = desc_length - offset;
+ if (count > max_packet) count = max_packet;
+
+ fprintf(stderr, "sending ACCESSORY_SET_HID_REPORT_DESC offset: %d count: %d desc_length: %d\n",
+ offset, count, desc_length);
+ ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR,
+ ACCESSORY_SET_HID_REPORT_DESC, id, offset, &desc.value[offset], count, 1000);
+ fprintf(stderr, "ACCESSORY_SET_HID_REPORT_DESC returned %d errno %d\n", ret, errno);
+ offset += count;
+ }
+
+ pthread_mutex_unlock(&mutex);
+
+ while (1) {
+ ret = read(fd, buffer, sizeof(buffer));
+ if (ret < 0) {
+fprintf(stderr, "read failed, errno: %d, fd: %d\n", errno, fd);
+ break;
+ }
+
+ ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR,
+ ACCESSORY_SEND_HID_EVENT, id, 0, buffer, ret, 1000);
+ if (ret < 0 && errno != EPIPE) {
+fprintf(stderr, "ACCESSORY_SEND_HID_EVENT returned %d errno: %d\n", ret, errno);
+ goto wait_for_device;
+ }
+ }
+
+fprintf(stderr, "ACCESSORY_UNREGISTER_HID\n");
+ ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR,
+ ACCESSORY_UNREGISTER_HID, id, 0, NULL, 0, 1000);
+
+fprintf(stderr, "hid thread exiting\n");
+err:
+ return NULL;
+}
+
+static void open_hid(const char* name)
+{
+ char path[100];
+
+ snprintf(path, sizeof(path), "/dev/%s", name);
+ int fd = open(path, O_RDWR);
+ if (fd < 0) return;
+
+ fprintf(stderr, "opened /dev/%s\n", name);
+ pthread_t th;
+ pthread_create(&th, NULL, hid_thread, (void *)fd);
+}
+
+static void* inotify_thread(void* arg)
+{
+ open_hid("hidraw0");
+ open_hid("hidraw1");
+ open_hid("hidraw2");
+ open_hid("hidraw3");
+ open_hid("hidraw4");
+ open_hid("hidraw5");
+ open_hid("hidraw6");
+ open_hid("hidraw7");
+ open_hid("hidraw8");
+ open_hid("hidraw9");
+
+ int inotify_fd = inotify_init();
+ inotify_add_watch(inotify_fd, "/dev", IN_DELETE | IN_CREATE);
+
+ while (1) {
+ char event_buf[512];
+ struct inotify_event *event;
+ int event_pos = 0;
+ int event_size;
+
+ int count = read(inotify_fd, event_buf, sizeof(event_buf));
+ if (count < (int)sizeof(*event)) {
+ if(errno == EINTR)
+ continue;
+ fprintf(stderr, "could not get event, %s\n", strerror(errno));
+ break;
+ }
+ while (count >= (int)sizeof(*event)) {
+ event = (struct inotify_event *)(event_buf + event_pos);
+ //fprintf(stderr, "%d: %08x \"%s\"\n", event->wd, event->mask,
+ // event->len ? event->name : "");
+ if (event->len) {
+ if(event->mask & IN_CREATE) {
+ fprintf(stderr, "created %s\n", event->name);
+ // FIXME
+ milli_sleep(50);
+ open_hid(event->name);
+ } else {
+ fprintf(stderr, "lost %s\n", event->name);
+ }
+ }
+ event_size = sizeof(*event) + event->len;
+ count -= event_size;
+ event_pos += event_size;
+ }
+ }
+
+ close(inotify_fd);
+ return NULL;
+}
+
+void init_hid()
+{
+ pthread_t th;
+ pthread_create(&th, NULL, inotify_thread, NULL);
+}
diff --git a/libs/usb/tests/accessorytest/usb.c b/libs/usb/tests/accessorytest/usb.c
new file mode 100644
index 0000000..ac72b35
--- /dev/null
+++ b/libs/usb/tests/accessorytest/usb.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2012 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 <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <signal.h>
+#include <pthread.h>
+#include <errno.h>
+#include <string.h>
+
+#include <usbhost/usbhost.h>
+#include "f_accessory.h"
+
+#include "accessory.h"
+
+static struct usb_device *current_device = NULL;
+static uint8_t read_ep;
+static uint8_t write_ep;
+
+static pthread_mutex_t device_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t device_cond = PTHREAD_COND_INITIALIZER;
+
+static void milli_sleep(int millis) {
+ struct timespec tm;
+
+ tm.tv_sec = 0;
+ tm.tv_nsec = millis * 1000000;
+ nanosleep(&tm, NULL);
+}
+
+static void* read_thread(void* arg) {
+ int ret = 0;
+
+ while (current_device && ret >= 0) {
+ char buffer[16384];
+
+ ret = usb_device_bulk_transfer(current_device, read_ep, buffer, sizeof(buffer), 1000);
+ if (ret < 0 && errno == ETIMEDOUT)
+ ret = 0;
+ if (ret > 0) {
+ fwrite(buffer, 1, ret, stdout);
+ fprintf(stderr, "\n");
+ fflush(stdout);
+ }
+ }
+
+ return NULL;
+}
+
+static void* write_thread(void* arg) {
+ int ret = 0;
+
+ while (ret >= 0) {
+ char buffer[16384];
+ char *line = fgets(buffer, sizeof(buffer), stdin);
+ if (!line || !current_device)
+ break;
+ ret = usb_device_bulk_transfer(current_device, write_ep, line, strlen(line), 1000);
+ }
+
+ return NULL;
+}
+
+static void send_string(struct usb_device *device, int index, const char* string) {
+ usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR,
+ ACCESSORY_SEND_STRING, 0, index, (void *)string, strlen(string) + 1, 0);
+
+ // some devices can't handle back-to-back requests, so delay a bit
+ milli_sleep(10);
+}
+
+static int usb_device_added(const char *devname, void* client_data) {
+ uint16_t vendorId, productId;
+ int ret;
+ int enable_accessory = (int)client_data;
+
+ struct usb_device *device = usb_device_open(devname);
+ if (!device) {
+ fprintf(stderr, "usb_device_open failed\n");
+ return 0;
+ }
+
+ vendorId = usb_device_get_vendor_id(device);
+ productId = usb_device_get_product_id(device);
+
+ if (!current_device && vendorId == 0x18D1 && productId >= 0x2D00 && productId <= 0x2D05) {
+
+ pthread_mutex_lock(&device_mutex);
+ fprintf(stderr, "Found android device in accessory mode\n");
+ current_device = device;
+ pthread_cond_broadcast(&device_cond);
+ pthread_mutex_unlock(&device_mutex);
+
+ if (enable_accessory) {
+ struct usb_descriptor_header* desc;
+ struct usb_descriptor_iter iter;
+ struct usb_interface_descriptor *intf = NULL;
+ struct usb_endpoint_descriptor *ep1 = NULL;
+ struct usb_endpoint_descriptor *ep2 = NULL;
+ pthread_t th;
+
+ usb_descriptor_iter_init(device, &iter);
+ while ((desc = usb_descriptor_iter_next(&iter)) != NULL && (!intf || !ep1 || !ep2)) {
+ if (desc->bDescriptorType == USB_DT_INTERFACE) {
+ intf = (struct usb_interface_descriptor *)desc;
+ } else if (desc->bDescriptorType == USB_DT_ENDPOINT) {
+ if (ep1)
+ ep2 = (struct usb_endpoint_descriptor *)desc;
+ else
+ ep1 = (struct usb_endpoint_descriptor *)desc;
+ }
+ }
+
+ if (!intf) {
+ fprintf(stderr, "interface not found\n");
+ exit(1);
+ }
+ if (!ep1 || !ep2) {
+ fprintf(stderr, "endpoints not found\n");
+ exit(1);
+ }
+
+ if (usb_device_claim_interface(device, intf->bInterfaceNumber)) {
+ fprintf(stderr, "usb_device_claim_interface failed errno: %d\n", errno);
+ exit(1);
+ }
+
+ if ((ep1->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) {
+ read_ep = ep1->bEndpointAddress;
+ write_ep = ep2->bEndpointAddress;
+ } else {
+ read_ep = ep2->bEndpointAddress;
+ write_ep = ep1->bEndpointAddress;
+ }
+
+ pthread_create(&th, NULL, read_thread, NULL);
+ pthread_create(&th, NULL, write_thread, NULL);
+ }
+ } else {
+// fprintf(stderr, "Found new device - attempting to switch to accessory mode\n");
+
+ uint16_t protocol = -1;
+ ret = usb_device_control_transfer(device, USB_DIR_IN | USB_TYPE_VENDOR,
+ ACCESSORY_GET_PROTOCOL, 0, 0, &protocol, sizeof(protocol), 1000);
+ if (ret < 0) {
+ // fprintf(stderr, "ACCESSORY_GET_PROTOCOL returned %d errno: %d\n", ret, errno);
+ } else {
+ fprintf(stderr, "device supports protocol version %d\n", protocol);
+ if (protocol >= 2) {
+ if (enable_accessory) {
+ send_string(device, ACCESSORY_STRING_MANUFACTURER, "Google, Inc.");
+ send_string(device, ACCESSORY_STRING_MODEL, "AccessoryChat");
+ send_string(device, ACCESSORY_STRING_DESCRIPTION, "Accessory Chat");
+ send_string(device, ACCESSORY_STRING_VERSION, "1.0");
+ send_string(device, ACCESSORY_STRING_URI, "http://www.android.com");
+ send_string(device, ACCESSORY_STRING_SERIAL, "1234567890");
+ }
+
+ fprintf(stderr, "sending ACCESSORY_SET_AUDIO_MODE\n");
+ ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR,
+ ACCESSORY_SET_AUDIO_MODE, 1, 0, NULL, 0, 1000);
+ if (ret < 0)
+ fprintf(stderr, "ACCESSORY_SET_AUDIO_MODE returned %d errno: %d\n", ret, errno);
+
+ fprintf(stderr, "sending ACCESSORY_START\n");
+ ret = usb_device_control_transfer(device, USB_DIR_OUT | USB_TYPE_VENDOR,
+ ACCESSORY_START, 0, 0, NULL, 0, 1000);
+ fprintf(stderr, "did ACCESSORY_START\n");
+ if (ret < 0)
+ fprintf(stderr, "ACCESSORY_START returned %d errno: %d\n", ret, errno);
+ }
+ }
+
+ return 0;
+ }
+
+ if (device != current_device)
+ usb_device_close(device);
+
+ return 0;
+}
+
+static int usb_device_removed(const char *devname, void* client_data) {
+ pthread_mutex_lock(&device_mutex);
+
+ if (current_device && !strcmp(usb_device_get_name(current_device), devname)) {
+ fprintf(stderr, "current device disconnected\n");
+ usb_device_close(current_device);
+ current_device = NULL;
+ }
+
+ pthread_mutex_unlock(&device_mutex);
+ return 0;
+}
+
+struct usb_device* usb_wait_for_device() {
+ struct usb_device* device = NULL;
+
+ pthread_mutex_lock(&device_mutex);
+ while (!current_device)
+ pthread_cond_wait(&device_cond, &device_mutex);
+ device = current_device;
+ pthread_mutex_unlock(&device_mutex);
+
+ return device;
+}
+
+void usb_run(int enable_accessory) {
+ struct usb_host_context* context = usb_host_init();
+
+ usb_host_run(context, usb_device_added, usb_device_removed, NULL, (void *)enable_accessory);
+}
+