diff options
Diffstat (limited to 'libs')
91 files changed, 5698 insertions, 7635 deletions
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk index 3ed75a2..d80612b 100644 --- a/libs/androidfw/Android.mk +++ b/libs/androidfw/Android.mk @@ -14,47 +14,47 @@ LOCAL_PATH:= $(call my-dir) -# libandroidfw is partially built for the host (used by build time keymap validation tool) +# libandroidfw is partially built for the host (used by obbtool and others) # These files are common to host and target builds. -# formerly in libutils -commonUtilsSources:= \ +commonSources := \ Asset.cpp \ AssetDir.cpp \ AssetManager.cpp \ + misc.cpp \ ObbFile.cpp \ ResourceTypes.cpp \ - StreamingZipInflater.cpp - -# formerly in libui -commonUiSources:= \ - Input.cpp \ - InputDevice.cpp \ - Keyboard.cpp \ - KeyCharacterMap.cpp \ - KeyLayoutMap.cpp \ - VelocityControl.cpp \ - VelocityTracker.cpp \ - VirtualKeyMap.cpp - -commonSources:= \ - $(commonUtilsSources) \ - $(commonUiSources) + StreamingZipInflater.cpp \ + ZipFileRO.cpp \ + ZipUtils.cpp + +deviceSources := \ + $(commonSources) \ + BackupData.cpp \ + BackupHelpers.cpp \ + CursorWindow.cpp + +hostSources := \ + $(commonSources) # For the host # ===================================================== include $(CLEAR_VARS) -LOCAL_SRC_FILES:= $(commonSources) +LOCAL_SRC_FILES:= $(hostSources) LOCAL_MODULE:= libandroidfw LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS + LOCAL_C_INCLUDES := \ external/zlib +LOCAL_STATIC_LIBRARIES := liblog + include $(BUILD_HOST_STATIC_LIBRARY) @@ -63,23 +63,16 @@ include $(BUILD_HOST_STATIC_LIBRARY) include $(CLEAR_VARS) -LOCAL_SRC_FILES:= \ - $(commonSources) \ - BackupData.cpp \ - BackupHelpers.cpp \ - CursorWindow.cpp \ - InputTransport.cpp +LOCAL_SRC_FILES:= $(deviceSources) LOCAL_SHARED_LIBRARIES := \ + libbinder \ liblog \ libcutils \ libutils \ - libbinder \ - libskia \ libz LOCAL_C_INCLUDES := \ - external/skia/include/core \ external/icu4c/common \ external/zlib @@ -90,20 +83,6 @@ LOCAL_MODULE_TAGS := optional include $(BUILD_SHARED_LIBRARY) -ifeq ($(TARGET_OS),linux) -include $(CLEAR_VARS) -LOCAL_C_INCLUDES += \ - external/skia/include/core \ - external/zlib \ - external/icu4c/common \ - bionic/libc/private -LOCAL_LDLIBS := -lrt -ldl -lpthread -LOCAL_MODULE := libandroidfw -LOCAL_SRC_FILES := $(commonUtilsSources) BackupData.cpp BackupHelpers.cpp -include $(BUILD_STATIC_LIBRARY) -endif - - # Include subdirectory makefiles # ============================================================ diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp index fd5b3e4..cb7628d 100644 --- a/libs/androidfw/Asset.cpp +++ b/libs/androidfw/Asset.cpp @@ -23,11 +23,11 @@ #include <androidfw/Asset.h> #include <androidfw/StreamingZipInflater.h> +#include <androidfw/ZipFileRO.h> +#include <androidfw/ZipUtils.h> #include <utils/Atomic.h> #include <utils/FileMap.h> #include <utils/Log.h> -#include <utils/ZipFileRO.h> -#include <utils/ZipUtils.h> #include <utils/threads.h> #include <assert.h> diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 9156725..1066715 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -25,14 +25,15 @@ #include <androidfw/Asset.h> #include <androidfw/AssetDir.h> #include <androidfw/AssetManager.h> +#include <androidfw/misc.h> #include <androidfw/ResourceTypes.h> +#include <androidfw/ZipFileRO.h> #include <utils/Atomic.h> #include <utils/Log.h> #include <utils/String8.h> #include <utils/String8.h> #include <utils/threads.h> #include <utils/Timers.h> -#include <utils/ZipFileRO.h> #ifdef HAVE_ANDROID_OS #include <cutils/trace.h> #endif diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index 047a4c8..0f54edb 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -17,8 +17,9 @@ #undef LOG_TAG #define LOG_TAG "CursorWindow" -#include <utils/Log.h> #include <androidfw/CursorWindow.h> +#include <binder/Parcel.h> +#include <utils/Log.h> #include <cutils/ashmem.h> #include <sys/mman.h> diff --git a/libs/androidfw/Input.cpp b/libs/androidfw/Input.cpp deleted file mode 100644 index eca692a..0000000 --- a/libs/androidfw/Input.cpp +++ /dev/null @@ -1,634 +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. - */ - -#define LOG_TAG "Input" -//#define LOG_NDEBUG 0 - -#include <math.h> -#include <limits.h> - -#include <androidfw/Input.h> - -#ifdef HAVE_ANDROID_OS -#include <binder/Parcel.h> - -#include "SkPoint.h" -#include "SkMatrix.h" -#include "SkScalar.h" -#endif - -namespace android { - -// --- InputEvent --- - -void InputEvent::initialize(int32_t deviceId, int32_t source) { - mDeviceId = deviceId; - mSource = source; -} - -void InputEvent::initialize(const InputEvent& from) { - mDeviceId = from.mDeviceId; - mSource = from.mSource; -} - -// --- KeyEvent --- - -bool KeyEvent::hasDefaultAction(int32_t keyCode) { - switch (keyCode) { - case AKEYCODE_HOME: - case AKEYCODE_BACK: - case AKEYCODE_CALL: - case AKEYCODE_ENDCALL: - case AKEYCODE_VOLUME_UP: - case AKEYCODE_VOLUME_DOWN: - case AKEYCODE_VOLUME_MUTE: - case AKEYCODE_POWER: - case AKEYCODE_CAMERA: - case AKEYCODE_HEADSETHOOK: - case AKEYCODE_MENU: - case AKEYCODE_NOTIFICATION: - case AKEYCODE_FOCUS: - case AKEYCODE_SEARCH: - case AKEYCODE_MEDIA_PLAY: - case AKEYCODE_MEDIA_PAUSE: - case AKEYCODE_MEDIA_PLAY_PAUSE: - case AKEYCODE_MEDIA_STOP: - case AKEYCODE_MEDIA_NEXT: - case AKEYCODE_MEDIA_PREVIOUS: - case AKEYCODE_MEDIA_REWIND: - case AKEYCODE_MEDIA_RECORD: - case AKEYCODE_MEDIA_FAST_FORWARD: - case AKEYCODE_MUTE: - case AKEYCODE_BRIGHTNESS_DOWN: - case AKEYCODE_BRIGHTNESS_UP: - return true; - } - - return false; -} - -bool KeyEvent::hasDefaultAction() const { - return hasDefaultAction(getKeyCode()); -} - -bool KeyEvent::isSystemKey(int32_t keyCode) { - switch (keyCode) { - case AKEYCODE_MENU: - case AKEYCODE_SOFT_RIGHT: - case AKEYCODE_HOME: - case AKEYCODE_BACK: - case AKEYCODE_CALL: - case AKEYCODE_ENDCALL: - case AKEYCODE_VOLUME_UP: - case AKEYCODE_VOLUME_DOWN: - case AKEYCODE_VOLUME_MUTE: - case AKEYCODE_MUTE: - case AKEYCODE_POWER: - case AKEYCODE_HEADSETHOOK: - case AKEYCODE_MEDIA_PLAY: - case AKEYCODE_MEDIA_PAUSE: - case AKEYCODE_MEDIA_PLAY_PAUSE: - case AKEYCODE_MEDIA_STOP: - case AKEYCODE_MEDIA_NEXT: - case AKEYCODE_MEDIA_PREVIOUS: - case AKEYCODE_MEDIA_REWIND: - case AKEYCODE_MEDIA_RECORD: - case AKEYCODE_MEDIA_FAST_FORWARD: - case AKEYCODE_CAMERA: - case AKEYCODE_FOCUS: - case AKEYCODE_SEARCH: - case AKEYCODE_BRIGHTNESS_DOWN: - case AKEYCODE_BRIGHTNESS_UP: - return true; - } - - return false; -} - -bool KeyEvent::isSystemKey() const { - return isSystemKey(getKeyCode()); -} - -void KeyEvent::initialize( - int32_t deviceId, - int32_t source, - int32_t action, - int32_t flags, - int32_t keyCode, - int32_t scanCode, - int32_t metaState, - int32_t repeatCount, - nsecs_t downTime, - nsecs_t eventTime) { - InputEvent::initialize(deviceId, source); - mAction = action; - mFlags = flags; - mKeyCode = keyCode; - mScanCode = scanCode; - mMetaState = metaState; - mRepeatCount = repeatCount; - mDownTime = downTime; - mEventTime = eventTime; -} - -void KeyEvent::initialize(const KeyEvent& from) { - InputEvent::initialize(from); - mAction = from.mAction; - mFlags = from.mFlags; - mKeyCode = from.mKeyCode; - mScanCode = from.mScanCode; - mMetaState = from.mMetaState; - mRepeatCount = from.mRepeatCount; - mDownTime = from.mDownTime; - mEventTime = from.mEventTime; -} - - -// --- PointerCoords --- - -float PointerCoords::getAxisValue(int32_t axis) const { - if (axis < 0 || axis > 63) { - return 0; - } - - uint64_t axisBit = 1LL << axis; - if (!(bits & axisBit)) { - return 0; - } - uint32_t index = __builtin_popcountll(bits & (axisBit - 1LL)); - return values[index]; -} - -status_t PointerCoords::setAxisValue(int32_t axis, float value) { - if (axis < 0 || axis > 63) { - return NAME_NOT_FOUND; - } - - uint64_t axisBit = 1LL << axis; - uint32_t index = __builtin_popcountll(bits & (axisBit - 1LL)); - if (!(bits & axisBit)) { - if (value == 0) { - return OK; // axes with value 0 do not need to be stored - } - uint32_t count = __builtin_popcountll(bits); - if (count >= MAX_AXES) { - tooManyAxes(axis); - return NO_MEMORY; - } - bits |= axisBit; - for (uint32_t i = count; i > index; i--) { - values[i] = values[i - 1]; - } - } - values[index] = value; - return OK; -} - -static inline void scaleAxisValue(PointerCoords& c, int axis, float scaleFactor) { - float value = c.getAxisValue(axis); - if (value != 0) { - c.setAxisValue(axis, value * scaleFactor); - } -} - -void PointerCoords::scale(float scaleFactor) { - // No need to scale pressure or size since they are normalized. - // No need to scale orientation since it is meaningless to do so. - scaleAxisValue(*this, AMOTION_EVENT_AXIS_X, scaleFactor); - scaleAxisValue(*this, AMOTION_EVENT_AXIS_Y, scaleFactor); - scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOUCH_MAJOR, scaleFactor); - scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOUCH_MINOR, scaleFactor); - scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MAJOR, scaleFactor); - scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MINOR, scaleFactor); -} - -#ifdef HAVE_ANDROID_OS -status_t PointerCoords::readFromParcel(Parcel* parcel) { - bits = parcel->readInt64(); - - uint32_t count = __builtin_popcountll(bits); - if (count > MAX_AXES) { - return BAD_VALUE; - } - - for (uint32_t i = 0; i < count; i++) { - values[i] = parcel->readFloat(); - } - return OK; -} - -status_t PointerCoords::writeToParcel(Parcel* parcel) const { - parcel->writeInt64(bits); - - uint32_t count = __builtin_popcountll(bits); - for (uint32_t i = 0; i < count; i++) { - parcel->writeFloat(values[i]); - } - return OK; -} -#endif - -void PointerCoords::tooManyAxes(int axis) { - ALOGW("Could not set value for axis %d because the PointerCoords structure is full and " - "cannot contain more than %d axis values.", axis, int(MAX_AXES)); -} - -bool PointerCoords::operator==(const PointerCoords& other) const { - if (bits != other.bits) { - return false; - } - uint32_t count = __builtin_popcountll(bits); - for (uint32_t i = 0; i < count; i++) { - if (values[i] != other.values[i]) { - return false; - } - } - return true; -} - -void PointerCoords::copyFrom(const PointerCoords& other) { - bits = other.bits; - uint32_t count = __builtin_popcountll(bits); - for (uint32_t i = 0; i < count; i++) { - values[i] = other.values[i]; - } -} - - -// --- PointerProperties --- - -bool PointerProperties::operator==(const PointerProperties& other) const { - return id == other.id - && toolType == other.toolType; -} - -void PointerProperties::copyFrom(const PointerProperties& other) { - id = other.id; - toolType = other.toolType; -} - - -// --- MotionEvent --- - -void MotionEvent::initialize( - int32_t deviceId, - int32_t source, - int32_t action, - int32_t flags, - int32_t edgeFlags, - int32_t metaState, - int32_t buttonState, - float xOffset, - float yOffset, - float xPrecision, - float yPrecision, - nsecs_t downTime, - nsecs_t eventTime, - size_t pointerCount, - const PointerProperties* pointerProperties, - const PointerCoords* pointerCoords) { - InputEvent::initialize(deviceId, source); - mAction = action; - mFlags = flags; - mEdgeFlags = edgeFlags; - mMetaState = metaState; - mButtonState = buttonState; - mXOffset = xOffset; - mYOffset = yOffset; - mXPrecision = xPrecision; - mYPrecision = yPrecision; - mDownTime = downTime; - mPointerProperties.clear(); - mPointerProperties.appendArray(pointerProperties, pointerCount); - mSampleEventTimes.clear(); - mSamplePointerCoords.clear(); - addSample(eventTime, pointerCoords); -} - -void MotionEvent::copyFrom(const MotionEvent* other, bool keepHistory) { - InputEvent::initialize(other->mDeviceId, other->mSource); - mAction = other->mAction; - mFlags = other->mFlags; - mEdgeFlags = other->mEdgeFlags; - mMetaState = other->mMetaState; - mButtonState = other->mButtonState; - mXOffset = other->mXOffset; - mYOffset = other->mYOffset; - mXPrecision = other->mXPrecision; - mYPrecision = other->mYPrecision; - mDownTime = other->mDownTime; - mPointerProperties = other->mPointerProperties; - - if (keepHistory) { - mSampleEventTimes = other->mSampleEventTimes; - mSamplePointerCoords = other->mSamplePointerCoords; - } else { - mSampleEventTimes.clear(); - mSampleEventTimes.push(other->getEventTime()); - mSamplePointerCoords.clear(); - size_t pointerCount = other->getPointerCount(); - size_t historySize = other->getHistorySize(); - mSamplePointerCoords.appendArray(other->mSamplePointerCoords.array() - + (historySize * pointerCount), pointerCount); - } -} - -void MotionEvent::addSample( - int64_t eventTime, - const PointerCoords* pointerCoords) { - mSampleEventTimes.push(eventTime); - mSamplePointerCoords.appendArray(pointerCoords, getPointerCount()); -} - -const PointerCoords* MotionEvent::getRawPointerCoords(size_t pointerIndex) const { - return &mSamplePointerCoords[getHistorySize() * getPointerCount() + pointerIndex]; -} - -float MotionEvent::getRawAxisValue(int32_t axis, size_t pointerIndex) const { - return getRawPointerCoords(pointerIndex)->getAxisValue(axis); -} - -float MotionEvent::getAxisValue(int32_t axis, size_t pointerIndex) const { - float value = getRawPointerCoords(pointerIndex)->getAxisValue(axis); - switch (axis) { - case AMOTION_EVENT_AXIS_X: - return value + mXOffset; - case AMOTION_EVENT_AXIS_Y: - return value + mYOffset; - } - return value; -} - -const PointerCoords* MotionEvent::getHistoricalRawPointerCoords( - size_t pointerIndex, size_t historicalIndex) const { - return &mSamplePointerCoords[historicalIndex * getPointerCount() + pointerIndex]; -} - -float MotionEvent::getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex, - size_t historicalIndex) const { - return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis); -} - -float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex, - size_t historicalIndex) const { - float value = getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis); - switch (axis) { - case AMOTION_EVENT_AXIS_X: - return value + mXOffset; - case AMOTION_EVENT_AXIS_Y: - return value + mYOffset; - } - return value; -} - -ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const { - size_t pointerCount = mPointerProperties.size(); - for (size_t i = 0; i < pointerCount; i++) { - if (mPointerProperties.itemAt(i).id == pointerId) { - return i; - } - } - return -1; -} - -void MotionEvent::offsetLocation(float xOffset, float yOffset) { - mXOffset += xOffset; - mYOffset += yOffset; -} - -void MotionEvent::scale(float scaleFactor) { - mXOffset *= scaleFactor; - mYOffset *= scaleFactor; - mXPrecision *= scaleFactor; - mYPrecision *= scaleFactor; - - size_t numSamples = mSamplePointerCoords.size(); - for (size_t i = 0; i < numSamples; i++) { - mSamplePointerCoords.editItemAt(i).scale(scaleFactor); - } -} - -#ifdef HAVE_ANDROID_OS -static inline float transformAngle(const SkMatrix* matrix, float angleRadians) { - // Construct and transform a vector oriented at the specified clockwise angle from vertical. - // Coordinate system: down is increasing Y, right is increasing X. - SkPoint vector; - vector.fX = SkFloatToScalar(sinf(angleRadians)); - vector.fY = SkFloatToScalar(-cosf(angleRadians)); - matrix->mapVectors(& vector, 1); - - // Derive the transformed vector's clockwise angle from vertical. - float result = atan2f(SkScalarToFloat(vector.fX), SkScalarToFloat(-vector.fY)); - if (result < - M_PI_2) { - result += M_PI; - } else if (result > M_PI_2) { - result -= M_PI; - } - return result; -} - -void MotionEvent::transform(const SkMatrix* matrix) { - float oldXOffset = mXOffset; - float oldYOffset = mYOffset; - - // The tricky part of this implementation is to preserve the value of - // rawX and rawY. So we apply the transformation to the first point - // then derive an appropriate new X/Y offset that will preserve rawX and rawY. - SkPoint point; - float rawX = getRawX(0); - float rawY = getRawY(0); - matrix->mapXY(SkFloatToScalar(rawX + oldXOffset), SkFloatToScalar(rawY + oldYOffset), - & point); - float newX = SkScalarToFloat(point.fX); - float newY = SkScalarToFloat(point.fY); - float newXOffset = newX - rawX; - float newYOffset = newY - rawY; - - mXOffset = newXOffset; - mYOffset = newYOffset; - - // Apply the transformation to all samples. - size_t numSamples = mSamplePointerCoords.size(); - for (size_t i = 0; i < numSamples; i++) { - PointerCoords& c = mSamplePointerCoords.editItemAt(i); - float x = c.getAxisValue(AMOTION_EVENT_AXIS_X) + oldXOffset; - float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y) + oldYOffset; - matrix->mapXY(SkFloatToScalar(x), SkFloatToScalar(y), &point); - c.setAxisValue(AMOTION_EVENT_AXIS_X, SkScalarToFloat(point.fX) - newXOffset); - c.setAxisValue(AMOTION_EVENT_AXIS_Y, SkScalarToFloat(point.fY) - newYOffset); - - float orientation = c.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); - c.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, transformAngle(matrix, orientation)); - } -} - -status_t MotionEvent::readFromParcel(Parcel* parcel) { - size_t pointerCount = parcel->readInt32(); - size_t sampleCount = parcel->readInt32(); - if (pointerCount == 0 || pointerCount > MAX_POINTERS || sampleCount == 0) { - return BAD_VALUE; - } - - mDeviceId = parcel->readInt32(); - mSource = parcel->readInt32(); - mAction = parcel->readInt32(); - mFlags = parcel->readInt32(); - mEdgeFlags = parcel->readInt32(); - mMetaState = parcel->readInt32(); - mButtonState = parcel->readInt32(); - mXOffset = parcel->readFloat(); - mYOffset = parcel->readFloat(); - mXPrecision = parcel->readFloat(); - mYPrecision = parcel->readFloat(); - mDownTime = parcel->readInt64(); - - mPointerProperties.clear(); - mPointerProperties.setCapacity(pointerCount); - mSampleEventTimes.clear(); - mSampleEventTimes.setCapacity(sampleCount); - mSamplePointerCoords.clear(); - mSamplePointerCoords.setCapacity(sampleCount * pointerCount); - - for (size_t i = 0; i < pointerCount; i++) { - mPointerProperties.push(); - PointerProperties& properties = mPointerProperties.editTop(); - properties.id = parcel->readInt32(); - properties.toolType = parcel->readInt32(); - } - - while (sampleCount-- > 0) { - mSampleEventTimes.push(parcel->readInt64()); - for (size_t i = 0; i < pointerCount; i++) { - mSamplePointerCoords.push(); - status_t status = mSamplePointerCoords.editTop().readFromParcel(parcel); - if (status) { - return status; - } - } - } - return OK; -} - -status_t MotionEvent::writeToParcel(Parcel* parcel) const { - size_t pointerCount = mPointerProperties.size(); - size_t sampleCount = mSampleEventTimes.size(); - - parcel->writeInt32(pointerCount); - parcel->writeInt32(sampleCount); - - parcel->writeInt32(mDeviceId); - parcel->writeInt32(mSource); - parcel->writeInt32(mAction); - parcel->writeInt32(mFlags); - parcel->writeInt32(mEdgeFlags); - parcel->writeInt32(mMetaState); - parcel->writeInt32(mButtonState); - parcel->writeFloat(mXOffset); - parcel->writeFloat(mYOffset); - parcel->writeFloat(mXPrecision); - parcel->writeFloat(mYPrecision); - parcel->writeInt64(mDownTime); - - for (size_t i = 0; i < pointerCount; i++) { - const PointerProperties& properties = mPointerProperties.itemAt(i); - parcel->writeInt32(properties.id); - parcel->writeInt32(properties.toolType); - } - - const PointerCoords* pc = mSamplePointerCoords.array(); - for (size_t h = 0; h < sampleCount; h++) { - parcel->writeInt64(mSampleEventTimes.itemAt(h)); - for (size_t i = 0; i < pointerCount; i++) { - status_t status = (pc++)->writeToParcel(parcel); - if (status) { - return status; - } - } - } - return OK; -} -#endif - -bool MotionEvent::isTouchEvent(int32_t source, int32_t action) { - if (source & AINPUT_SOURCE_CLASS_POINTER) { - // Specifically excludes HOVER_MOVE and SCROLL. - switch (action & AMOTION_EVENT_ACTION_MASK) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_MOVE: - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_POINTER_DOWN: - case AMOTION_EVENT_ACTION_POINTER_UP: - case AMOTION_EVENT_ACTION_CANCEL: - case AMOTION_EVENT_ACTION_OUTSIDE: - return true; - } - } - return false; -} - - -// --- PooledInputEventFactory --- - -PooledInputEventFactory::PooledInputEventFactory(size_t maxPoolSize) : - mMaxPoolSize(maxPoolSize) { -} - -PooledInputEventFactory::~PooledInputEventFactory() { - for (size_t i = 0; i < mKeyEventPool.size(); i++) { - delete mKeyEventPool.itemAt(i); - } - for (size_t i = 0; i < mMotionEventPool.size(); i++) { - delete mMotionEventPool.itemAt(i); - } -} - -KeyEvent* PooledInputEventFactory::createKeyEvent() { - if (!mKeyEventPool.isEmpty()) { - KeyEvent* event = mKeyEventPool.top(); - mKeyEventPool.pop(); - return event; - } - return new KeyEvent(); -} - -MotionEvent* PooledInputEventFactory::createMotionEvent() { - if (!mMotionEventPool.isEmpty()) { - MotionEvent* event = mMotionEventPool.top(); - mMotionEventPool.pop(); - return event; - } - return new MotionEvent(); -} - -void PooledInputEventFactory::recycle(InputEvent* event) { - switch (event->getType()) { - case AINPUT_EVENT_TYPE_KEY: - if (mKeyEventPool.size() < mMaxPoolSize) { - mKeyEventPool.push(static_cast<KeyEvent*>(event)); - return; - } - break; - case AINPUT_EVENT_TYPE_MOTION: - if (mMotionEventPool.size() < mMaxPoolSize) { - mMotionEventPool.push(static_cast<MotionEvent*>(event)); - return; - } - break; - } - delete event; -} - -} // namespace android diff --git a/libs/androidfw/InputDevice.cpp b/libs/androidfw/InputDevice.cpp deleted file mode 100644 index f742052..0000000 --- a/libs/androidfw/InputDevice.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/* - * 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 "InputDevice" - -#include <stdlib.h> -#include <unistd.h> -#include <ctype.h> - -#include <androidfw/InputDevice.h> - -namespace android { - -static const char* CONFIGURATION_FILE_DIR[] = { - "idc/", - "keylayout/", - "keychars/", -}; - -static const char* CONFIGURATION_FILE_EXTENSION[] = { - ".idc", - ".kl", - ".kcm", -}; - -static bool isValidNameChar(char ch) { - return isascii(ch) && (isdigit(ch) || isalpha(ch) || ch == '-' || ch == '_'); -} - -static void appendInputDeviceConfigurationFileRelativePath(String8& path, - const String8& name, InputDeviceConfigurationFileType type) { - path.append(CONFIGURATION_FILE_DIR[type]); - for (size_t i = 0; i < name.length(); i++) { - char ch = name[i]; - if (!isValidNameChar(ch)) { - ch = '_'; - } - path.append(&ch, 1); - } - path.append(CONFIGURATION_FILE_EXTENSION[type]); -} - -String8 getInputDeviceConfigurationFilePathByDeviceIdentifier( - const InputDeviceIdentifier& deviceIdentifier, - InputDeviceConfigurationFileType type) { - if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) { - if (deviceIdentifier.version != 0) { - // Try vendor product version. - String8 versionPath(getInputDeviceConfigurationFilePathByName( - String8::format("Vendor_%04x_Product_%04x_Version_%04x", - deviceIdentifier.vendor, deviceIdentifier.product, - deviceIdentifier.version), - type)); - if (!versionPath.isEmpty()) { - return versionPath; - } - } - - // Try vendor product. - String8 productPath(getInputDeviceConfigurationFilePathByName( - String8::format("Vendor_%04x_Product_%04x", - deviceIdentifier.vendor, deviceIdentifier.product), - type)); - if (!productPath.isEmpty()) { - return productPath; - } - } - - // Try device name. - return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type); -} - -String8 getInputDeviceConfigurationFilePathByName( - const String8& name, InputDeviceConfigurationFileType type) { - // Search system repository. - String8 path; - path.setTo(getenv("ANDROID_ROOT")); - path.append("/usr/"); - appendInputDeviceConfigurationFileRelativePath(path, name, type); -#if DEBUG_PROBE - ALOGD("Probing for system provided input device configuration file: path='%s'", path.string()); -#endif - if (!access(path.string(), R_OK)) { -#if DEBUG_PROBE - ALOGD("Found"); -#endif - return path; - } - - // Search user repository. - // TODO Should only look here if not in safe mode. - path.setTo(getenv("ANDROID_DATA")); - path.append("/system/devices/"); - appendInputDeviceConfigurationFileRelativePath(path, name, type); -#if DEBUG_PROBE - ALOGD("Probing for system user input device configuration file: path='%s'", path.string()); -#endif - if (!access(path.string(), R_OK)) { -#if DEBUG_PROBE - ALOGD("Found"); -#endif - return path; - } - - // Not found. -#if DEBUG_PROBE - ALOGD("Probe failed to find input device configuration file: name='%s', type=%d", - name.string(), type); -#endif - return String8(); -} - - -// --- InputDeviceInfo --- - -InputDeviceInfo::InputDeviceInfo() { - initialize(-1, -1, InputDeviceIdentifier(), String8(), false); -} - -InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) : - mId(other.mId), mGeneration(other.mGeneration), mIdentifier(other.mIdentifier), - mAlias(other.mAlias), mIsExternal(other.mIsExternal), mSources(other.mSources), - mKeyboardType(other.mKeyboardType), - mKeyCharacterMap(other.mKeyCharacterMap), - mHasVibrator(other.mHasVibrator), - mMotionRanges(other.mMotionRanges) { -} - -InputDeviceInfo::~InputDeviceInfo() { -} - -void InputDeviceInfo::initialize(int32_t id, int32_t generation, - const InputDeviceIdentifier& identifier, const String8& alias, bool isExternal) { - mId = id; - mGeneration = generation; - mIdentifier = identifier; - mAlias = alias; - mIsExternal = isExternal; - mSources = 0; - mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE; - mHasVibrator = false; - mMotionRanges.clear(); -} - -const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange( - int32_t axis, uint32_t source) const { - size_t numRanges = mMotionRanges.size(); - for (size_t i = 0; i < numRanges; i++) { - const MotionRange& range = mMotionRanges.itemAt(i); - if (range.axis == axis && range.source == source) { - return ⦥ - } - } - return NULL; -} - -void InputDeviceInfo::addSource(uint32_t source) { - mSources |= source; -} - -void InputDeviceInfo::addMotionRange(int32_t axis, uint32_t source, float min, float max, - float flat, float fuzz, float resolution) { - MotionRange range = { axis, source, min, max, flat, fuzz, resolution }; - mMotionRanges.add(range); -} - -void InputDeviceInfo::addMotionRange(const MotionRange& range) { - mMotionRanges.add(range); -} - -} // namespace android diff --git a/libs/androidfw/InputTransport.cpp b/libs/androidfw/InputTransport.cpp deleted file mode 100644 index 498389e..0000000 --- a/libs/androidfw/InputTransport.cpp +++ /dev/null @@ -1,957 +0,0 @@ -// -// Copyright 2010 The Android Open Source Project -// -// Provides a shared memory transport for input events. -// -#define LOG_TAG "InputTransport" - -//#define LOG_NDEBUG 0 - -// Log debug messages about channel messages (send message, receive message) -#define DEBUG_CHANNEL_MESSAGES 0 - -// Log debug messages whenever InputChannel objects are created/destroyed -#define DEBUG_CHANNEL_LIFECYCLE 0 - -// Log debug messages about transport actions -#define DEBUG_TRANSPORT_ACTIONS 0 - -// Log debug messages about touch event resampling -#define DEBUG_RESAMPLING 0 - - -#include <cutils/log.h> -#include <cutils/properties.h> -#include <errno.h> -#include <fcntl.h> -#include <androidfw/InputTransport.h> -#include <unistd.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <math.h> - - -namespace android { - -// Socket buffer size. The default is typically about 128KB, which is much larger than -// we really need. So we make it smaller. It just needs to be big enough to hold -// a few dozen large multi-finger motion events in the case where an application gets -// behind processing touches. -static const size_t SOCKET_BUFFER_SIZE = 32 * 1024; - -// Nanoseconds per milliseconds. -static const nsecs_t NANOS_PER_MS = 1000000; - -// Latency added during resampling. A few milliseconds doesn't hurt much but -// reduces the impact of mispredicted touch positions. -static const nsecs_t RESAMPLE_LATENCY = 5 * NANOS_PER_MS; - -// Minimum time difference between consecutive samples before attempting to resample. -static const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS; - -// Maximum time to predict forward from the last known state, to avoid predicting too -// far into the future. This time is further bounded by 50% of the last time delta. -static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS; - -template<typename T> -inline static T min(const T& a, const T& b) { - return a < b ? a : b; -} - -inline static float lerp(float a, float b, float alpha) { - return a + alpha * (b - a); -} - -// --- InputMessage --- - -bool InputMessage::isValid(size_t actualSize) const { - if (size() == actualSize) { - switch (header.type) { - case TYPE_KEY: - return true; - case TYPE_MOTION: - return body.motion.pointerCount > 0 - && body.motion.pointerCount <= MAX_POINTERS; - case TYPE_FINISHED: - return true; - } - } - return false; -} - -size_t InputMessage::size() const { - switch (header.type) { - case TYPE_KEY: - return sizeof(Header) + body.key.size(); - case TYPE_MOTION: - return sizeof(Header) + body.motion.size(); - case TYPE_FINISHED: - return sizeof(Header) + body.finished.size(); - } - return sizeof(Header); -} - - -// --- InputChannel --- - -InputChannel::InputChannel(const String8& name, int fd) : - mName(name), mFd(fd) { -#if DEBUG_CHANNEL_LIFECYCLE - ALOGD("Input channel constructed: name='%s', fd=%d", - mName.string(), fd); -#endif - - int result = fcntl(mFd, F_SETFL, O_NONBLOCK); - LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make socket " - "non-blocking. errno=%d", mName.string(), errno); -} - -InputChannel::~InputChannel() { -#if DEBUG_CHANNEL_LIFECYCLE - ALOGD("Input channel destroyed: name='%s', fd=%d", - mName.string(), mFd); -#endif - - ::close(mFd); -} - -status_t InputChannel::openInputChannelPair(const String8& name, - sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) { - int sockets[2]; - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) { - status_t result = -errno; - ALOGE("channel '%s' ~ Could not create socket pair. errno=%d", - name.string(), errno); - outServerChannel.clear(); - outClientChannel.clear(); - return result; - } - - int bufferSize = SOCKET_BUFFER_SIZE; - setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)); - setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); - setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)); - setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)); - - String8 serverChannelName = name; - serverChannelName.append(" (server)"); - outServerChannel = new InputChannel(serverChannelName, sockets[0]); - - String8 clientChannelName = name; - clientChannelName.append(" (client)"); - outClientChannel = new InputChannel(clientChannelName, sockets[1]); - return OK; -} - -status_t InputChannel::sendMessage(const InputMessage* msg) { - size_t msgLength = msg->size(); - ssize_t nWrite; - do { - nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL); - } while (nWrite == -1 && errno == EINTR); - - if (nWrite < 0) { - int error = errno; -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ error sending message of type %d, errno=%d", mName.string(), - msg->header.type, error); -#endif - if (error == EAGAIN || error == EWOULDBLOCK) { - return WOULD_BLOCK; - } - if (error == EPIPE || error == ENOTCONN) { - return DEAD_OBJECT; - } - return -error; - } - - if (size_t(nWrite) != msgLength) { -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ error sending message type %d, send was incomplete", - mName.string(), msg->header.type); -#endif - return DEAD_OBJECT; - } - -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ sent message of type %d", mName.string(), msg->header.type); -#endif - return OK; -} - -status_t InputChannel::receiveMessage(InputMessage* msg) { - ssize_t nRead; - do { - nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT); - } while (nRead == -1 && errno == EINTR); - - if (nRead < 0) { - int error = errno; -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ receive message failed, errno=%d", mName.string(), errno); -#endif - if (error == EAGAIN || error == EWOULDBLOCK) { - return WOULD_BLOCK; - } - if (error == EPIPE || error == ENOTCONN) { - return DEAD_OBJECT; - } - return -error; - } - - if (nRead == 0) { // check for EOF -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ receive message failed because peer was closed", mName.string()); -#endif - return DEAD_OBJECT; - } - - if (!msg->isValid(nRead)) { -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ received invalid message", mName.string()); -#endif - return BAD_VALUE; - } - -#if DEBUG_CHANNEL_MESSAGES - ALOGD("channel '%s' ~ received message of type %d", mName.string(), msg->header.type); -#endif - return OK; -} - -sp<InputChannel> InputChannel::dup() const { - int fd = ::dup(getFd()); - return fd >= 0 ? new InputChannel(getName(), fd) : NULL; -} - - -// --- InputPublisher --- - -InputPublisher::InputPublisher(const sp<InputChannel>& channel) : - mChannel(channel) { -} - -InputPublisher::~InputPublisher() { -} - -status_t InputPublisher::publishKeyEvent( - uint32_t seq, - int32_t deviceId, - int32_t source, - int32_t action, - int32_t flags, - int32_t keyCode, - int32_t scanCode, - int32_t metaState, - int32_t repeatCount, - nsecs_t downTime, - nsecs_t eventTime) { -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' publisher ~ publishKeyEvent: seq=%u, deviceId=%d, source=0x%x, " - "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d," - "downTime=%lld, eventTime=%lld", - mChannel->getName().string(), seq, - deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount, - downTime, eventTime); -#endif - - if (!seq) { - ALOGE("Attempted to publish a key event with sequence number 0."); - return BAD_VALUE; - } - - InputMessage msg; - msg.header.type = InputMessage::TYPE_KEY; - msg.body.key.seq = seq; - msg.body.key.deviceId = deviceId; - msg.body.key.source = source; - msg.body.key.action = action; - msg.body.key.flags = flags; - msg.body.key.keyCode = keyCode; - msg.body.key.scanCode = scanCode; - msg.body.key.metaState = metaState; - msg.body.key.repeatCount = repeatCount; - msg.body.key.downTime = downTime; - msg.body.key.eventTime = eventTime; - return mChannel->sendMessage(&msg); -} - -status_t InputPublisher::publishMotionEvent( - uint32_t seq, - int32_t deviceId, - int32_t source, - int32_t action, - int32_t flags, - int32_t edgeFlags, - int32_t metaState, - int32_t buttonState, - float xOffset, - float yOffset, - float xPrecision, - float yPrecision, - nsecs_t downTime, - nsecs_t eventTime, - size_t pointerCount, - const PointerProperties* pointerProperties, - const PointerCoords* pointerCoords) { -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' publisher ~ publishMotionEvent: seq=%u, deviceId=%d, source=0x%x, " - "action=0x%x, flags=0x%x, edgeFlags=0x%x, metaState=0x%x, buttonState=0x%x, " - "xOffset=%f, yOffset=%f, " - "xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, " - "pointerCount=%d", - mChannel->getName().string(), seq, - deviceId, source, action, flags, edgeFlags, metaState, buttonState, - xOffset, yOffset, xPrecision, yPrecision, downTime, eventTime, pointerCount); -#endif - - if (!seq) { - ALOGE("Attempted to publish a motion event with sequence number 0."); - return BAD_VALUE; - } - - if (pointerCount > MAX_POINTERS || pointerCount < 1) { - ALOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.", - mChannel->getName().string(), pointerCount); - return BAD_VALUE; - } - - InputMessage msg; - msg.header.type = InputMessage::TYPE_MOTION; - msg.body.motion.seq = seq; - msg.body.motion.deviceId = deviceId; - msg.body.motion.source = source; - msg.body.motion.action = action; - msg.body.motion.flags = flags; - msg.body.motion.edgeFlags = edgeFlags; - msg.body.motion.metaState = metaState; - msg.body.motion.buttonState = buttonState; - msg.body.motion.xOffset = xOffset; - msg.body.motion.yOffset = yOffset; - msg.body.motion.xPrecision = xPrecision; - msg.body.motion.yPrecision = yPrecision; - msg.body.motion.downTime = downTime; - msg.body.motion.eventTime = eventTime; - msg.body.motion.pointerCount = pointerCount; - for (size_t i = 0; i < pointerCount; i++) { - msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]); - msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]); - } - return mChannel->sendMessage(&msg); -} - -status_t InputPublisher::receiveFinishedSignal(uint32_t* outSeq, bool* outHandled) { -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' publisher ~ receiveFinishedSignal", - mChannel->getName().string()); -#endif - - InputMessage msg; - status_t result = mChannel->receiveMessage(&msg); - if (result) { - *outSeq = 0; - *outHandled = false; - return result; - } - if (msg.header.type != InputMessage::TYPE_FINISHED) { - ALOGE("channel '%s' publisher ~ Received unexpected message of type %d from consumer", - mChannel->getName().string(), msg.header.type); - return UNKNOWN_ERROR; - } - *outSeq = msg.body.finished.seq; - *outHandled = msg.body.finished.handled; - return OK; -} - -// --- InputConsumer --- - -InputConsumer::InputConsumer(const sp<InputChannel>& channel) : - mResampleTouch(isTouchResamplingEnabled()), - mChannel(channel), mMsgDeferred(false) { -} - -InputConsumer::~InputConsumer() { -} - -bool InputConsumer::isTouchResamplingEnabled() { - char value[PROPERTY_VALUE_MAX]; - int length = property_get("debug.inputconsumer.resample", value, NULL); - if (length > 0) { - if (!strcmp("0", value)) { - return false; - } - if (strcmp("1", value)) { - ALOGD("Unrecognized property value for 'debug.inputconsumer.resample'. " - "Use '1' or '0'."); - } - } - return true; -} - -status_t InputConsumer::consume(InputEventFactoryInterface* factory, - bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) { -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%lld", - mChannel->getName().string(), consumeBatches ? "true" : "false", frameTime); -#endif - - *outSeq = 0; - *outEvent = NULL; - - // Fetch the next input message. - // Loop until an event can be returned or no additional events are received. - while (!*outEvent) { - if (mMsgDeferred) { - // mMsg contains a valid input message from the previous call to consume - // that has not yet been processed. - mMsgDeferred = false; - } else { - // Receive a fresh message. - status_t result = mChannel->receiveMessage(&mMsg); - if (result) { - // Consume the next batched event unless batches are being held for later. - if (consumeBatches || result != WOULD_BLOCK) { - result = consumeBatch(factory, frameTime, outSeq, outEvent); - if (*outEvent) { -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u", - mChannel->getName().string(), *outSeq); -#endif - break; - } - } - return result; - } - } - - switch (mMsg.header.type) { - case InputMessage::TYPE_KEY: { - KeyEvent* keyEvent = factory->createKeyEvent(); - if (!keyEvent) return NO_MEMORY; - - initializeKeyEvent(keyEvent, &mMsg); - *outSeq = mMsg.body.key.seq; - *outEvent = keyEvent; -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' consumer ~ consumed key event, seq=%u", - mChannel->getName().string(), *outSeq); -#endif - break; - } - - case AINPUT_EVENT_TYPE_MOTION: { - ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source); - if (batchIndex >= 0) { - Batch& batch = mBatches.editItemAt(batchIndex); - if (canAddSample(batch, &mMsg)) { - batch.samples.push(mMsg); -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' consumer ~ appended to batch event", - mChannel->getName().string()); -#endif - break; - } else { - // We cannot append to the batch in progress, so we need to consume - // the previous batch right now and defer the new message until later. - mMsgDeferred = true; - status_t result = consumeSamples(factory, - batch, batch.samples.size(), outSeq, outEvent); - mBatches.removeAt(batchIndex); - if (result) { - return result; - } -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' consumer ~ consumed batch event and " - "deferred current event, seq=%u", - mChannel->getName().string(), *outSeq); -#endif - break; - } - } - - // Start a new batch if needed. - if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE - || mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) { - mBatches.push(); - Batch& batch = mBatches.editTop(); - batch.samples.push(mMsg); -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' consumer ~ started batch event", - mChannel->getName().string()); -#endif - break; - } - - MotionEvent* motionEvent = factory->createMotionEvent(); - if (! motionEvent) return NO_MEMORY; - - updateTouchState(&mMsg); - initializeMotionEvent(motionEvent, &mMsg); - *outSeq = mMsg.body.motion.seq; - *outEvent = motionEvent; -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' consumer ~ consumed motion event, seq=%u", - mChannel->getName().string(), *outSeq); -#endif - break; - } - - default: - ALOGE("channel '%s' consumer ~ Received unexpected message of type %d", - mChannel->getName().string(), mMsg.header.type); - return UNKNOWN_ERROR; - } - } - return OK; -} - -status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory, - nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) { - status_t result; - for (size_t i = mBatches.size(); i-- > 0; ) { - Batch& batch = mBatches.editItemAt(i); - if (frameTime < 0) { - result = consumeSamples(factory, batch, batch.samples.size(), - outSeq, outEvent); - mBatches.removeAt(i); - return result; - } - - nsecs_t sampleTime = frameTime - RESAMPLE_LATENCY; - ssize_t split = findSampleNoLaterThan(batch, sampleTime); - if (split < 0) { - continue; - } - - result = consumeSamples(factory, batch, split + 1, outSeq, outEvent); - const InputMessage* next; - if (batch.samples.isEmpty()) { - mBatches.removeAt(i); - next = NULL; - } else { - next = &batch.samples.itemAt(0); - } - if (!result) { - resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next); - } - return result; - } - - return WOULD_BLOCK; -} - -status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory, - Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent) { - MotionEvent* motionEvent = factory->createMotionEvent(); - if (! motionEvent) return NO_MEMORY; - - uint32_t chain = 0; - for (size_t i = 0; i < count; i++) { - InputMessage& msg = batch.samples.editItemAt(i); - updateTouchState(&msg); - if (i) { - SeqChain seqChain; - seqChain.seq = msg.body.motion.seq; - seqChain.chain = chain; - mSeqChains.push(seqChain); - addSample(motionEvent, &msg); - } else { - initializeMotionEvent(motionEvent, &msg); - } - chain = msg.body.motion.seq; - } - batch.samples.removeItemsAt(0, count); - - *outSeq = chain; - *outEvent = motionEvent; - return OK; -} - -void InputConsumer::updateTouchState(InputMessage* msg) { - if (!mResampleTouch || - !(msg->body.motion.source & AINPUT_SOURCE_CLASS_POINTER)) { - return; - } - - int32_t deviceId = msg->body.motion.deviceId; - int32_t source = msg->body.motion.source; - nsecs_t eventTime = msg->body.motion.eventTime; - - // Update the touch state history to incorporate the new input message. - // If the message is in the past relative to the most recently produced resampled - // touch, then use the resampled time and coordinates instead. - switch (msg->body.motion.action & AMOTION_EVENT_ACTION_MASK) { - case AMOTION_EVENT_ACTION_DOWN: { - ssize_t index = findTouchState(deviceId, source); - if (index < 0) { - mTouchStates.push(); - index = mTouchStates.size() - 1; - } - TouchState& touchState = mTouchStates.editItemAt(index); - touchState.initialize(deviceId, source); - touchState.addHistory(msg); - break; - } - - case AMOTION_EVENT_ACTION_MOVE: { - ssize_t index = findTouchState(deviceId, source); - if (index >= 0) { - TouchState& touchState = mTouchStates.editItemAt(index); - touchState.addHistory(msg); - if (eventTime < touchState.lastResample.eventTime) { - rewriteMessage(touchState, msg); - } else { - touchState.lastResample.idBits.clear(); - } - } - break; - } - - case AMOTION_EVENT_ACTION_POINTER_DOWN: { - ssize_t index = findTouchState(deviceId, source); - if (index >= 0) { - TouchState& touchState = mTouchStates.editItemAt(index); - touchState.lastResample.idBits.clearBit(msg->body.motion.getActionId()); - rewriteMessage(touchState, msg); - } - break; - } - - case AMOTION_EVENT_ACTION_POINTER_UP: { - ssize_t index = findTouchState(deviceId, source); - if (index >= 0) { - TouchState& touchState = mTouchStates.editItemAt(index); - rewriteMessage(touchState, msg); - touchState.lastResample.idBits.clearBit(msg->body.motion.getActionId()); - } - break; - } - - case AMOTION_EVENT_ACTION_SCROLL: { - ssize_t index = findTouchState(deviceId, source); - if (index >= 0) { - const TouchState& touchState = mTouchStates.itemAt(index); - rewriteMessage(touchState, msg); - } - break; - } - - case AMOTION_EVENT_ACTION_UP: - case AMOTION_EVENT_ACTION_CANCEL: { - ssize_t index = findTouchState(deviceId, source); - if (index >= 0) { - const TouchState& touchState = mTouchStates.itemAt(index); - rewriteMessage(touchState, msg); - mTouchStates.removeAt(index); - } - break; - } - } -} - -void InputConsumer::rewriteMessage(const TouchState& state, InputMessage* msg) { - for (size_t i = 0; i < msg->body.motion.pointerCount; i++) { - uint32_t id = msg->body.motion.pointers[i].properties.id; - if (state.lastResample.idBits.hasBit(id)) { - PointerCoords& msgCoords = msg->body.motion.pointers[i].coords; - const PointerCoords& resampleCoords = state.lastResample.getPointerById(id); -#if DEBUG_RESAMPLING - ALOGD("[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id, - resampleCoords.getAxisValue(AMOTION_EVENT_AXIS_X), - resampleCoords.getAxisValue(AMOTION_EVENT_AXIS_Y), - msgCoords.getAxisValue(AMOTION_EVENT_AXIS_X), - msgCoords.getAxisValue(AMOTION_EVENT_AXIS_Y)); -#endif - msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX()); - msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY()); - } - } -} - -void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event, - const InputMessage* next) { - if (!mResampleTouch - || !(event->getSource() & AINPUT_SOURCE_CLASS_POINTER) - || event->getAction() != AMOTION_EVENT_ACTION_MOVE) { - return; - } - - ssize_t index = findTouchState(event->getDeviceId(), event->getSource()); - if (index < 0) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, no touch state for device."); -#endif - return; - } - - TouchState& touchState = mTouchStates.editItemAt(index); - if (touchState.historySize < 1) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, no history for device."); -#endif - return; - } - - // Ensure that the current sample has all of the pointers that need to be reported. - const History* current = touchState.getHistory(0); - size_t pointerCount = event->getPointerCount(); - for (size_t i = 0; i < pointerCount; i++) { - uint32_t id = event->getPointerId(i); - if (!current->idBits.hasBit(id)) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, missing id %d", id); -#endif - return; - } - } - - // Find the data to use for resampling. - const History* other; - History future; - float alpha; - if (next) { - // Interpolate between current sample and future sample. - // So current->eventTime <= sampleTime <= future.eventTime. - future.initializeFrom(next); - other = &future; - nsecs_t delta = future.eventTime - current->eventTime; - if (delta < RESAMPLE_MIN_DELTA) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, delta time is %lld ns.", delta); -#endif - return; - } - alpha = float(sampleTime - current->eventTime) / delta; - } else if (touchState.historySize >= 2) { - // Extrapolate future sample using current sample and past sample. - // So other->eventTime <= current->eventTime <= sampleTime. - other = touchState.getHistory(1); - nsecs_t delta = current->eventTime - other->eventTime; - if (delta < RESAMPLE_MIN_DELTA) { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, delta time is %lld ns.", delta); -#endif - return; - } - nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION); - if (sampleTime > maxPredict) { -#if DEBUG_RESAMPLING - ALOGD("Sample time is too far in the future, adjusting prediction " - "from %lld to %lld ns.", - sampleTime - current->eventTime, maxPredict - current->eventTime); -#endif - sampleTime = maxPredict; - } - alpha = float(current->eventTime - sampleTime) / delta; - } else { -#if DEBUG_RESAMPLING - ALOGD("Not resampled, insufficient data."); -#endif - return; - } - - // Resample touch coordinates. - touchState.lastResample.eventTime = sampleTime; - touchState.lastResample.idBits.clear(); - for (size_t i = 0; i < pointerCount; i++) { - uint32_t id = event->getPointerId(i); - touchState.lastResample.idToIndex[id] = i; - touchState.lastResample.idBits.markBit(id); - PointerCoords& resampledCoords = touchState.lastResample.pointers[i]; - const PointerCoords& currentCoords = current->getPointerById(id); - if (other->idBits.hasBit(id) - && shouldResampleTool(event->getToolType(i))) { - const PointerCoords& otherCoords = other->getPointerById(id); - resampledCoords.copyFrom(currentCoords); - resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, - lerp(currentCoords.getX(), otherCoords.getX(), alpha)); - resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, - lerp(currentCoords.getY(), otherCoords.getY(), alpha)); -#if DEBUG_RESAMPLING - ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), " - "other (%0.3f, %0.3f), alpha %0.3f", - id, resampledCoords.getX(), resampledCoords.getY(), - currentCoords.getX(), currentCoords.getY(), - otherCoords.getX(), otherCoords.getY(), - alpha); -#endif - } else { - resampledCoords.copyFrom(currentCoords); -#if DEBUG_RESAMPLING - ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", - id, resampledCoords.getX(), resampledCoords.getY(), - currentCoords.getX(), currentCoords.getY()); -#endif - } - } - - event->addSample(sampleTime, touchState.lastResample.pointers); -} - -bool InputConsumer::shouldResampleTool(int32_t toolType) { - return toolType == AMOTION_EVENT_TOOL_TYPE_FINGER - || toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN; -} - -status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) { -#if DEBUG_TRANSPORT_ACTIONS - ALOGD("channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s", - mChannel->getName().string(), seq, handled ? "true" : "false"); -#endif - - if (!seq) { - ALOGE("Attempted to send a finished signal with sequence number 0."); - return BAD_VALUE; - } - - // Send finished signals for the batch sequence chain first. - size_t seqChainCount = mSeqChains.size(); - if (seqChainCount) { - uint32_t currentSeq = seq; - uint32_t chainSeqs[seqChainCount]; - size_t chainIndex = 0; - for (size_t i = seqChainCount; i-- > 0; ) { - const SeqChain& seqChain = mSeqChains.itemAt(i); - if (seqChain.seq == currentSeq) { - currentSeq = seqChain.chain; - chainSeqs[chainIndex++] = currentSeq; - mSeqChains.removeAt(i); - } - } - status_t status = OK; - while (!status && chainIndex-- > 0) { - status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled); - } - if (status) { - // An error occurred so at least one signal was not sent, reconstruct the chain. - do { - SeqChain seqChain; - seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq; - seqChain.chain = chainSeqs[chainIndex]; - mSeqChains.push(seqChain); - } while (chainIndex-- > 0); - return status; - } - } - - // Send finished signal for the last message in the batch. - return sendUnchainedFinishedSignal(seq, handled); -} - -status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) { - InputMessage msg; - msg.header.type = InputMessage::TYPE_FINISHED; - msg.body.finished.seq = seq; - msg.body.finished.handled = handled; - return mChannel->sendMessage(&msg); -} - -bool InputConsumer::hasDeferredEvent() const { - return mMsgDeferred; -} - -bool InputConsumer::hasPendingBatch() const { - return !mBatches.isEmpty(); -} - -ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const { - for (size_t i = 0; i < mBatches.size(); i++) { - const Batch& batch = mBatches.itemAt(i); - const InputMessage& head = batch.samples.itemAt(0); - if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) { - return i; - } - } - return -1; -} - -ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const { - for (size_t i = 0; i < mTouchStates.size(); i++) { - const TouchState& touchState = mTouchStates.itemAt(i); - if (touchState.deviceId == deviceId && touchState.source == source) { - return i; - } - } - return -1; -} - -void InputConsumer::initializeKeyEvent(KeyEvent* event, const InputMessage* msg) { - event->initialize( - msg->body.key.deviceId, - msg->body.key.source, - msg->body.key.action, - msg->body.key.flags, - msg->body.key.keyCode, - msg->body.key.scanCode, - msg->body.key.metaState, - msg->body.key.repeatCount, - msg->body.key.downTime, - msg->body.key.eventTime); -} - -void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) { - size_t pointerCount = msg->body.motion.pointerCount; - PointerProperties pointerProperties[pointerCount]; - PointerCoords pointerCoords[pointerCount]; - for (size_t i = 0; i < pointerCount; i++) { - pointerProperties[i].copyFrom(msg->body.motion.pointers[i].properties); - pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords); - } - - event->initialize( - msg->body.motion.deviceId, - msg->body.motion.source, - msg->body.motion.action, - msg->body.motion.flags, - msg->body.motion.edgeFlags, - msg->body.motion.metaState, - msg->body.motion.buttonState, - msg->body.motion.xOffset, - msg->body.motion.yOffset, - msg->body.motion.xPrecision, - msg->body.motion.yPrecision, - msg->body.motion.downTime, - msg->body.motion.eventTime, - pointerCount, - pointerProperties, - pointerCoords); -} - -void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) { - size_t pointerCount = msg->body.motion.pointerCount; - PointerCoords pointerCoords[pointerCount]; - for (size_t i = 0; i < pointerCount; i++) { - pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords); - } - - event->setMetaState(event->getMetaState() | msg->body.motion.metaState); - event->addSample(msg->body.motion.eventTime, pointerCoords); -} - -bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) { - const InputMessage& head = batch.samples.itemAt(0); - size_t pointerCount = msg->body.motion.pointerCount; - if (head.body.motion.pointerCount != pointerCount - || head.body.motion.action != msg->body.motion.action) { - return false; - } - for (size_t i = 0; i < pointerCount; i++) { - if (head.body.motion.pointers[i].properties - != msg->body.motion.pointers[i].properties) { - return false; - } - } - return true; -} - -ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) { - size_t numSamples = batch.samples.size(); - size_t index = 0; - while (index < numSamples - && batch.samples.itemAt(index).body.motion.eventTime <= time) { - index += 1; - } - return ssize_t(index) - 1; -} - -} // namespace android diff --git a/libs/androidfw/KeyCharacterMap.cpp b/libs/androidfw/KeyCharacterMap.cpp deleted file mode 100644 index 36cb6e1..0000000 --- a/libs/androidfw/KeyCharacterMap.cpp +++ /dev/null @@ -1,1153 +0,0 @@ -/* - * Copyright (C) 2008 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 "KeyCharacterMap" - -#include <stdlib.h> -#include <string.h> -#include <android/keycodes.h> -#include <androidfw/Keyboard.h> -#include <androidfw/KeyCharacterMap.h> - -#if HAVE_ANDROID_OS -#include <binder/Parcel.h> -#endif - -#include <utils/Log.h> -#include <utils/Errors.h> -#include <utils/Tokenizer.h> -#include <utils/Timers.h> - -// Enables debug output for the parser. -#define DEBUG_PARSER 0 - -// Enables debug output for parser performance. -#define DEBUG_PARSER_PERFORMANCE 0 - -// Enables debug output for mapping. -#define DEBUG_MAPPING 0 - - -namespace android { - -static const char* WHITESPACE = " \t\r"; -static const char* WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r,:"; - -struct Modifier { - const char* label; - int32_t metaState; -}; -static const Modifier modifiers[] = { - { "shift", AMETA_SHIFT_ON }, - { "lshift", AMETA_SHIFT_LEFT_ON }, - { "rshift", AMETA_SHIFT_RIGHT_ON }, - { "alt", AMETA_ALT_ON }, - { "lalt", AMETA_ALT_LEFT_ON }, - { "ralt", AMETA_ALT_RIGHT_ON }, - { "ctrl", AMETA_CTRL_ON }, - { "lctrl", AMETA_CTRL_LEFT_ON }, - { "rctrl", AMETA_CTRL_RIGHT_ON }, - { "meta", AMETA_META_ON }, - { "lmeta", AMETA_META_LEFT_ON }, - { "rmeta", AMETA_META_RIGHT_ON }, - { "sym", AMETA_SYM_ON }, - { "fn", AMETA_FUNCTION_ON }, - { "capslock", AMETA_CAPS_LOCK_ON }, - { "numlock", AMETA_NUM_LOCK_ON }, - { "scrolllock", AMETA_SCROLL_LOCK_ON }, -}; - -#if DEBUG_MAPPING -static String8 toString(const char16_t* chars, size_t numChars) { - String8 result; - for (size_t i = 0; i < numChars; i++) { - result.appendFormat(i == 0 ? "%d" : ", %d", chars[i]); - } - return result; -} -#endif - - -// --- KeyCharacterMap --- - -sp<KeyCharacterMap> KeyCharacterMap::sEmpty = new KeyCharacterMap(); - -KeyCharacterMap::KeyCharacterMap() : - mType(KEYBOARD_TYPE_UNKNOWN) { -} - -KeyCharacterMap::KeyCharacterMap(const KeyCharacterMap& other) : - RefBase(), mType(other.mType), mKeysByScanCode(other.mKeysByScanCode), - mKeysByUsageCode(other.mKeysByUsageCode) { - for (size_t i = 0; i < other.mKeys.size(); i++) { - mKeys.add(other.mKeys.keyAt(i), new Key(*other.mKeys.valueAt(i))); - } -} - -KeyCharacterMap::~KeyCharacterMap() { - for (size_t i = 0; i < mKeys.size(); i++) { - Key* key = mKeys.editValueAt(i); - delete key; - } -} - -status_t KeyCharacterMap::load(const String8& filename, - Format format, sp<KeyCharacterMap>* outMap) { - outMap->clear(); - - Tokenizer* tokenizer; - status_t status = Tokenizer::open(filename, &tokenizer); - if (status) { - ALOGE("Error %d opening key character map file %s.", status, filename.string()); - } else { - status = load(tokenizer, format, outMap); - delete tokenizer; - } - return status; -} - -status_t KeyCharacterMap::loadContents(const String8& filename, const char* contents, - Format format, sp<KeyCharacterMap>* outMap) { - outMap->clear(); - - Tokenizer* tokenizer; - status_t status = Tokenizer::fromContents(filename, contents, &tokenizer); - if (status) { - ALOGE("Error %d opening key character map.", status); - } else { - status = load(tokenizer, format, outMap); - delete tokenizer; - } - return status; -} - -status_t KeyCharacterMap::load(Tokenizer* tokenizer, - Format format, sp<KeyCharacterMap>* outMap) { - status_t status = OK; - sp<KeyCharacterMap> map = new KeyCharacterMap(); - if (!map.get()) { - ALOGE("Error allocating key character map."); - status = NO_MEMORY; - } else { -#if DEBUG_PARSER_PERFORMANCE - nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); -#endif - Parser parser(map.get(), tokenizer, format); - status = parser.parse(); -#if DEBUG_PARSER_PERFORMANCE - nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; - ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.", - tokenizer->getFilename().string(), tokenizer->getLineNumber(), - elapsedTime / 1000000.0); -#endif - if (!status) { - *outMap = map; - } - } - return status; -} - -sp<KeyCharacterMap> KeyCharacterMap::combine(const sp<KeyCharacterMap>& base, - const sp<KeyCharacterMap>& overlay) { - if (overlay == NULL) { - return base; - } - if (base == NULL) { - return overlay; - } - - sp<KeyCharacterMap> map = new KeyCharacterMap(*base.get()); - for (size_t i = 0; i < overlay->mKeys.size(); i++) { - int32_t keyCode = overlay->mKeys.keyAt(i); - Key* key = overlay->mKeys.valueAt(i); - ssize_t oldIndex = map->mKeys.indexOfKey(keyCode); - if (oldIndex >= 0) { - delete map->mKeys.valueAt(oldIndex); - map->mKeys.editValueAt(oldIndex) = new Key(*key); - } else { - map->mKeys.add(keyCode, new Key(*key)); - } - } - - for (size_t i = 0; i < overlay->mKeysByScanCode.size(); i++) { - map->mKeysByScanCode.replaceValueFor(overlay->mKeysByScanCode.keyAt(i), - overlay->mKeysByScanCode.valueAt(i)); - } - - for (size_t i = 0; i < overlay->mKeysByUsageCode.size(); i++) { - map->mKeysByUsageCode.replaceValueFor(overlay->mKeysByUsageCode.keyAt(i), - overlay->mKeysByUsageCode.valueAt(i)); - } - return map; -} - -sp<KeyCharacterMap> KeyCharacterMap::empty() { - return sEmpty; -} - -int32_t KeyCharacterMap::getKeyboardType() const { - return mType; -} - -char16_t KeyCharacterMap::getDisplayLabel(int32_t keyCode) const { - char16_t result = 0; - const Key* key; - if (getKey(keyCode, &key)) { - result = key->label; - } -#if DEBUG_MAPPING - ALOGD("getDisplayLabel: keyCode=%d ~ Result %d.", keyCode, result); -#endif - return result; -} - -char16_t KeyCharacterMap::getNumber(int32_t keyCode) const { - char16_t result = 0; - const Key* key; - if (getKey(keyCode, &key)) { - result = key->number; - } -#if DEBUG_MAPPING - ALOGD("getNumber: keyCode=%d ~ Result %d.", keyCode, result); -#endif - return result; -} - -char16_t KeyCharacterMap::getCharacter(int32_t keyCode, int32_t metaState) const { - char16_t result = 0; - const Key* key; - const Behavior* behavior; - if (getKeyBehavior(keyCode, metaState, &key, &behavior)) { - result = behavior->character; - } -#if DEBUG_MAPPING - ALOGD("getCharacter: keyCode=%d, metaState=0x%08x ~ Result %d.", keyCode, metaState, result); -#endif - return result; -} - -bool KeyCharacterMap::getFallbackAction(int32_t keyCode, int32_t metaState, - FallbackAction* outFallbackAction) const { - outFallbackAction->keyCode = 0; - outFallbackAction->metaState = 0; - - bool result = false; - const Key* key; - const Behavior* behavior; - if (getKeyBehavior(keyCode, metaState, &key, &behavior)) { - if (behavior->fallbackKeyCode) { - outFallbackAction->keyCode = behavior->fallbackKeyCode; - outFallbackAction->metaState = metaState & ~behavior->metaState; - result = true; - } - } -#if DEBUG_MAPPING - ALOGD("getFallbackKeyCode: keyCode=%d, metaState=0x%08x ~ Result %s, " - "fallback keyCode=%d, fallback metaState=0x%08x.", - keyCode, metaState, result ? "true" : "false", - outFallbackAction->keyCode, outFallbackAction->metaState); -#endif - return result; -} - -char16_t KeyCharacterMap::getMatch(int32_t keyCode, const char16_t* chars, size_t numChars, - int32_t metaState) const { - char16_t result = 0; - const Key* key; - if (getKey(keyCode, &key)) { - // Try to find the most general behavior that maps to this character. - // For example, the base key behavior will usually be last in the list. - // However, if we find a perfect meta state match for one behavior then use that one. - for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) { - if (behavior->character) { - for (size_t i = 0; i < numChars; i++) { - if (behavior->character == chars[i]) { - result = behavior->character; - if ((behavior->metaState & metaState) == behavior->metaState) { - goto ExactMatch; - } - break; - } - } - } - } - ExactMatch: ; - } -#if DEBUG_MAPPING - ALOGD("getMatch: keyCode=%d, chars=[%s], metaState=0x%08x ~ Result %d.", - keyCode, toString(chars, numChars).string(), metaState, result); -#endif - return result; -} - -bool KeyCharacterMap::getEvents(int32_t deviceId, const char16_t* chars, size_t numChars, - Vector<KeyEvent>& outEvents) const { - nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - - for (size_t i = 0; i < numChars; i++) { - int32_t keyCode, metaState; - char16_t ch = chars[i]; - if (!findKey(ch, &keyCode, &metaState)) { -#if DEBUG_MAPPING - ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Failed to find mapping for character %d.", - deviceId, toString(chars, numChars).string(), ch); -#endif - return false; - } - - int32_t currentMetaState = 0; - addMetaKeys(outEvents, deviceId, metaState, true, now, ¤tMetaState); - addKey(outEvents, deviceId, keyCode, currentMetaState, true, now); - addKey(outEvents, deviceId, keyCode, currentMetaState, false, now); - addMetaKeys(outEvents, deviceId, metaState, false, now, ¤tMetaState); - } -#if DEBUG_MAPPING - ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Generated %d events.", - deviceId, toString(chars, numChars).string(), int32_t(outEvents.size())); - for (size_t i = 0; i < outEvents.size(); i++) { - ALOGD(" Key: keyCode=%d, metaState=0x%08x, %s.", - outEvents[i].getKeyCode(), outEvents[i].getMetaState(), - outEvents[i].getAction() == AKEY_EVENT_ACTION_DOWN ? "down" : "up"); - } -#endif - return true; -} - -status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const { - if (usageCode) { - ssize_t index = mKeysByUsageCode.indexOfKey(usageCode); - if (index >= 0) { -#if DEBUG_MAPPING - ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.", - scanCode, usageCode, *outKeyCode); -#endif - *outKeyCode = mKeysByUsageCode.valueAt(index); - return OK; - } - } - if (scanCode) { - ssize_t index = mKeysByScanCode.indexOfKey(scanCode); - if (index >= 0) { -#if DEBUG_MAPPING - ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.", - scanCode, usageCode, *outKeyCode); -#endif - *outKeyCode = mKeysByScanCode.valueAt(index); - return OK; - } - } - -#if DEBUG_MAPPING - ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode); -#endif - *outKeyCode = AKEYCODE_UNKNOWN; - return NAME_NOT_FOUND; -} - -bool KeyCharacterMap::getKey(int32_t keyCode, const Key** outKey) const { - ssize_t index = mKeys.indexOfKey(keyCode); - if (index >= 0) { - *outKey = mKeys.valueAt(index); - return true; - } - return false; -} - -bool KeyCharacterMap::getKeyBehavior(int32_t keyCode, int32_t metaState, - const Key** outKey, const Behavior** outBehavior) const { - const Key* key; - if (getKey(keyCode, &key)) { - const Behavior* behavior = key->firstBehavior; - while (behavior) { - if (matchesMetaState(metaState, behavior->metaState)) { - *outKey = key; - *outBehavior = behavior; - return true; - } - behavior = behavior->next; - } - } - return false; -} - -bool KeyCharacterMap::matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState) { - // Behavior must have at least the set of meta states specified. - // And if the key event has CTRL, ALT or META then the behavior must exactly - // match those, taking into account that a behavior can specify that it handles - // one, both or either of a left/right modifier pair. - if ((eventMetaState & behaviorMetaState) == behaviorMetaState) { - const int32_t EXACT_META_STATES = - AMETA_CTRL_ON | AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON - | AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON - | AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON; - int32_t unmatchedMetaState = eventMetaState & ~behaviorMetaState & EXACT_META_STATES; - if (behaviorMetaState & AMETA_CTRL_ON) { - unmatchedMetaState &= ~(AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON); - } else if (behaviorMetaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) { - unmatchedMetaState &= ~AMETA_CTRL_ON; - } - if (behaviorMetaState & AMETA_ALT_ON) { - unmatchedMetaState &= ~(AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON); - } else if (behaviorMetaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { - unmatchedMetaState &= ~AMETA_ALT_ON; - } - if (behaviorMetaState & AMETA_META_ON) { - unmatchedMetaState &= ~(AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON); - } else if (behaviorMetaState & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) { - unmatchedMetaState &= ~AMETA_META_ON; - } - return !unmatchedMetaState; - } - return false; -} - -bool KeyCharacterMap::findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMetaState) const { - if (!ch) { - return false; - } - - for (size_t i = 0; i < mKeys.size(); i++) { - const Key* key = mKeys.valueAt(i); - - // Try to find the most general behavior that maps to this character. - // For example, the base key behavior will usually be last in the list. - const Behavior* found = NULL; - for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) { - if (behavior->character == ch) { - found = behavior; - } - } - if (found) { - *outKeyCode = mKeys.keyAt(i); - *outMetaState = found->metaState; - return true; - } - } - return false; -} - -void KeyCharacterMap::addKey(Vector<KeyEvent>& outEvents, - int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time) { - outEvents.push(); - KeyEvent& event = outEvents.editTop(); - event.initialize(deviceId, AINPUT_SOURCE_KEYBOARD, - down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, - 0, keyCode, 0, metaState, 0, time, time); -} - -void KeyCharacterMap::addMetaKeys(Vector<KeyEvent>& outEvents, - int32_t deviceId, int32_t metaState, bool down, nsecs_t time, - int32_t* currentMetaState) { - // Add and remove meta keys symmetrically. - if (down) { - addLockedMetaKey(outEvents, deviceId, metaState, time, - AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON, currentMetaState); - addLockedMetaKey(outEvents, deviceId, metaState, time, - AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON, currentMetaState); - addLockedMetaKey(outEvents, deviceId, metaState, time, - AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, currentMetaState); - - addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, - AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON, - AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON, - AMETA_SHIFT_ON, currentMetaState); - addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, - AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON, - AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON, - AMETA_ALT_ON, currentMetaState); - addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, - AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON, - AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON, - AMETA_CTRL_ON, currentMetaState); - addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, - AKEYCODE_META_LEFT, AMETA_META_LEFT_ON, - AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON, - AMETA_META_ON, currentMetaState); - - addSingleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, - AKEYCODE_SYM, AMETA_SYM_ON, currentMetaState); - addSingleEphemeralMetaKey(outEvents, deviceId, metaState, true, time, - AKEYCODE_FUNCTION, AMETA_FUNCTION_ON, currentMetaState); - } else { - addSingleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, - AKEYCODE_FUNCTION, AMETA_FUNCTION_ON, currentMetaState); - addSingleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, - AKEYCODE_SYM, AMETA_SYM_ON, currentMetaState); - - addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, - AKEYCODE_META_LEFT, AMETA_META_LEFT_ON, - AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON, - AMETA_META_ON, currentMetaState); - addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, - AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON, - AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON, - AMETA_CTRL_ON, currentMetaState); - addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, - AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON, - AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON, - AMETA_ALT_ON, currentMetaState); - addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time, - AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON, - AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON, - AMETA_SHIFT_ON, currentMetaState); - - addLockedMetaKey(outEvents, deviceId, metaState, time, - AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, currentMetaState); - addLockedMetaKey(outEvents, deviceId, metaState, time, - AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON, currentMetaState); - addLockedMetaKey(outEvents, deviceId, metaState, time, - AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON, currentMetaState); - } -} - -bool KeyCharacterMap::addSingleEphemeralMetaKey(Vector<KeyEvent>& outEvents, - int32_t deviceId, int32_t metaState, bool down, nsecs_t time, - int32_t keyCode, int32_t keyMetaState, - int32_t* currentMetaState) { - if ((metaState & keyMetaState) == keyMetaState) { - *currentMetaState = updateMetaState(keyCode, down, *currentMetaState); - addKey(outEvents, deviceId, keyCode, *currentMetaState, down, time); - return true; - } - return false; -} - -void KeyCharacterMap::addDoubleEphemeralMetaKey(Vector<KeyEvent>& outEvents, - int32_t deviceId, int32_t metaState, bool down, nsecs_t time, - int32_t leftKeyCode, int32_t leftKeyMetaState, - int32_t rightKeyCode, int32_t rightKeyMetaState, - int32_t eitherKeyMetaState, - int32_t* currentMetaState) { - bool specific = false; - specific |= addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time, - leftKeyCode, leftKeyMetaState, currentMetaState); - specific |= addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time, - rightKeyCode, rightKeyMetaState, currentMetaState); - - if (!specific) { - addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time, - leftKeyCode, eitherKeyMetaState, currentMetaState); - } -} - -void KeyCharacterMap::addLockedMetaKey(Vector<KeyEvent>& outEvents, - int32_t deviceId, int32_t metaState, nsecs_t time, - int32_t keyCode, int32_t keyMetaState, - int32_t* currentMetaState) { - if ((metaState & keyMetaState) == keyMetaState) { - *currentMetaState = updateMetaState(keyCode, true, *currentMetaState); - addKey(outEvents, deviceId, keyCode, *currentMetaState, true, time); - *currentMetaState = updateMetaState(keyCode, false, *currentMetaState); - addKey(outEvents, deviceId, keyCode, *currentMetaState, false, time); - } -} - -#if HAVE_ANDROID_OS -sp<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) { - sp<KeyCharacterMap> map = new KeyCharacterMap(); - map->mType = parcel->readInt32(); - size_t numKeys = parcel->readInt32(); - if (parcel->errorCheck()) { - return NULL; - } - - for (size_t i = 0; i < numKeys; i++) { - int32_t keyCode = parcel->readInt32(); - char16_t label = parcel->readInt32(); - char16_t number = parcel->readInt32(); - if (parcel->errorCheck()) { - return NULL; - } - - Key* key = new Key(); - key->label = label; - key->number = number; - map->mKeys.add(keyCode, key); - - Behavior* lastBehavior = NULL; - while (parcel->readInt32()) { - int32_t metaState = parcel->readInt32(); - char16_t character = parcel->readInt32(); - int32_t fallbackKeyCode = parcel->readInt32(); - if (parcel->errorCheck()) { - return NULL; - } - - Behavior* behavior = new Behavior(); - behavior->metaState = metaState; - behavior->character = character; - behavior->fallbackKeyCode = fallbackKeyCode; - if (lastBehavior) { - lastBehavior->next = behavior; - } else { - key->firstBehavior = behavior; - } - lastBehavior = behavior; - } - - if (parcel->errorCheck()) { - return NULL; - } - } - return map; -} - -void KeyCharacterMap::writeToParcel(Parcel* parcel) const { - parcel->writeInt32(mType); - - size_t numKeys = mKeys.size(); - parcel->writeInt32(numKeys); - for (size_t i = 0; i < numKeys; i++) { - int32_t keyCode = mKeys.keyAt(i); - const Key* key = mKeys.valueAt(i); - parcel->writeInt32(keyCode); - parcel->writeInt32(key->label); - parcel->writeInt32(key->number); - for (const Behavior* behavior = key->firstBehavior; behavior != NULL; - behavior = behavior->next) { - parcel->writeInt32(1); - parcel->writeInt32(behavior->metaState); - parcel->writeInt32(behavior->character); - parcel->writeInt32(behavior->fallbackKeyCode); - } - parcel->writeInt32(0); - } -} -#endif - - -// --- KeyCharacterMap::Key --- - -KeyCharacterMap::Key::Key() : - label(0), number(0), firstBehavior(NULL) { -} - -KeyCharacterMap::Key::Key(const Key& other) : - label(other.label), number(other.number), - firstBehavior(other.firstBehavior ? new Behavior(*other.firstBehavior) : NULL) { -} - -KeyCharacterMap::Key::~Key() { - Behavior* behavior = firstBehavior; - while (behavior) { - Behavior* next = behavior->next; - delete behavior; - behavior = next; - } -} - - -// --- KeyCharacterMap::Behavior --- - -KeyCharacterMap::Behavior::Behavior() : - next(NULL), metaState(0), character(0), fallbackKeyCode(0) { -} - -KeyCharacterMap::Behavior::Behavior(const Behavior& other) : - next(other.next ? new Behavior(*other.next) : NULL), - metaState(other.metaState), character(other.character), - fallbackKeyCode(other.fallbackKeyCode) { -} - - -// --- KeyCharacterMap::Parser --- - -KeyCharacterMap::Parser::Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format) : - mMap(map), mTokenizer(tokenizer), mFormat(format), mState(STATE_TOP) { -} - -KeyCharacterMap::Parser::~Parser() { -} - -status_t KeyCharacterMap::Parser::parse() { - while (!mTokenizer->isEof()) { -#if DEBUG_PARSER - ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(), - mTokenizer->peekRemainderOfLine().string()); -#endif - - mTokenizer->skipDelimiters(WHITESPACE); - - if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') { - switch (mState) { - case STATE_TOP: { - String8 keywordToken = mTokenizer->nextToken(WHITESPACE); - if (keywordToken == "type") { - mTokenizer->skipDelimiters(WHITESPACE); - status_t status = parseType(); - if (status) return status; - } else if (keywordToken == "map") { - mTokenizer->skipDelimiters(WHITESPACE); - status_t status = parseMap(); - if (status) return status; - } else if (keywordToken == "key") { - mTokenizer->skipDelimiters(WHITESPACE); - status_t status = parseKey(); - if (status) return status; - } else { - ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(), - keywordToken.string()); - return BAD_VALUE; - } - break; - } - - case STATE_KEY: { - status_t status = parseKeyProperty(); - if (status) return status; - break; - } - } - - mTokenizer->skipDelimiters(WHITESPACE); - if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') { - ALOGE("%s: Expected end of line or trailing comment, got '%s'.", - mTokenizer->getLocation().string(), - mTokenizer->peekRemainderOfLine().string()); - return BAD_VALUE; - } - } - - mTokenizer->nextLine(); - } - - if (mState != STATE_TOP) { - ALOGE("%s: Unterminated key description at end of file.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - - if (mMap->mType == KEYBOARD_TYPE_UNKNOWN) { - ALOGE("%s: Keyboard layout missing required keyboard 'type' declaration.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - - if (mFormat == FORMAT_BASE) { - if (mMap->mType == KEYBOARD_TYPE_OVERLAY) { - ALOGE("%s: Base keyboard layout must specify a keyboard 'type' other than 'OVERLAY'.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - } else if (mFormat == FORMAT_OVERLAY) { - if (mMap->mType != KEYBOARD_TYPE_OVERLAY) { - ALOGE("%s: Overlay keyboard layout missing required keyboard " - "'type OVERLAY' declaration.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - } - - return NO_ERROR; -} - -status_t KeyCharacterMap::Parser::parseType() { - if (mMap->mType != KEYBOARD_TYPE_UNKNOWN) { - ALOGE("%s: Duplicate keyboard 'type' declaration.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - - KeyboardType type; - String8 typeToken = mTokenizer->nextToken(WHITESPACE); - if (typeToken == "NUMERIC") { - type = KEYBOARD_TYPE_NUMERIC; - } else if (typeToken == "PREDICTIVE") { - type = KEYBOARD_TYPE_PREDICTIVE; - } else if (typeToken == "ALPHA") { - type = KEYBOARD_TYPE_ALPHA; - } else if (typeToken == "FULL") { - type = KEYBOARD_TYPE_FULL; - } else if (typeToken == "SPECIAL_FUNCTION") { - type = KEYBOARD_TYPE_SPECIAL_FUNCTION; - } else if (typeToken == "OVERLAY") { - type = KEYBOARD_TYPE_OVERLAY; - } else { - ALOGE("%s: Expected keyboard type label, got '%s'.", mTokenizer->getLocation().string(), - typeToken.string()); - return BAD_VALUE; - } - -#if DEBUG_PARSER - ALOGD("Parsed type: type=%d.", type); -#endif - mMap->mType = type; - return NO_ERROR; -} - -status_t KeyCharacterMap::Parser::parseMap() { - String8 keywordToken = mTokenizer->nextToken(WHITESPACE); - if (keywordToken == "key") { - mTokenizer->skipDelimiters(WHITESPACE); - return parseMapKey(); - } - ALOGE("%s: Expected keyword after 'map', got '%s'.", mTokenizer->getLocation().string(), - keywordToken.string()); - return BAD_VALUE; -} - -status_t KeyCharacterMap::Parser::parseMapKey() { - String8 codeToken = mTokenizer->nextToken(WHITESPACE); - bool mapUsage = false; - if (codeToken == "usage") { - mapUsage = true; - mTokenizer->skipDelimiters(WHITESPACE); - codeToken = mTokenizer->nextToken(WHITESPACE); - } - - char* end; - int32_t code = int32_t(strtol(codeToken.string(), &end, 0)); - if (*end) { - ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(), - mapUsage ? "usage" : "scan code", codeToken.string()); - return BAD_VALUE; - } - KeyedVector<int32_t, int32_t>& map = - mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode; - if (map.indexOfKey(code) >= 0) { - ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(), - mapUsage ? "usage" : "scan code", codeToken.string()); - return BAD_VALUE; - } - - mTokenizer->skipDelimiters(WHITESPACE); - String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE); - int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string()); - if (!keyCode) { - ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(), - keyCodeToken.string()); - return BAD_VALUE; - } - -#if DEBUG_PARSER - ALOGD("Parsed map key %s: code=%d, keyCode=%d.", - mapUsage ? "usage" : "scan code", code, keyCode); -#endif - map.add(code, keyCode); - return NO_ERROR; -} - -status_t KeyCharacterMap::Parser::parseKey() { - String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE); - int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string()); - if (!keyCode) { - ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(), - keyCodeToken.string()); - return BAD_VALUE; - } - if (mMap->mKeys.indexOfKey(keyCode) >= 0) { - ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(), - keyCodeToken.string()); - return BAD_VALUE; - } - - mTokenizer->skipDelimiters(WHITESPACE); - String8 openBraceToken = mTokenizer->nextToken(WHITESPACE); - if (openBraceToken != "{") { - ALOGE("%s: Expected '{' after key code label, got '%s'.", - mTokenizer->getLocation().string(), openBraceToken.string()); - return BAD_VALUE; - } - -#if DEBUG_PARSER - ALOGD("Parsed beginning of key: keyCode=%d.", keyCode); -#endif - mKeyCode = keyCode; - mMap->mKeys.add(keyCode, new Key()); - mState = STATE_KEY; - return NO_ERROR; -} - -status_t KeyCharacterMap::Parser::parseKeyProperty() { - Key* key = mMap->mKeys.valueFor(mKeyCode); - String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER); - if (token == "}") { - mState = STATE_TOP; - return finishKey(key); - } - - Vector<Property> properties; - - // Parse all comma-delimited property names up to the first colon. - for (;;) { - if (token == "label") { - properties.add(Property(PROPERTY_LABEL)); - } else if (token == "number") { - properties.add(Property(PROPERTY_NUMBER)); - } else { - int32_t metaState; - status_t status = parseModifier(token, &metaState); - if (status) { - ALOGE("%s: Expected a property name or modifier, got '%s'.", - mTokenizer->getLocation().string(), token.string()); - return status; - } - properties.add(Property(PROPERTY_META, metaState)); - } - - mTokenizer->skipDelimiters(WHITESPACE); - if (!mTokenizer->isEol()) { - char ch = mTokenizer->nextChar(); - if (ch == ':') { - break; - } else if (ch == ',') { - mTokenizer->skipDelimiters(WHITESPACE); - token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER); - continue; - } - } - - ALOGE("%s: Expected ',' or ':' after property name.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - - // Parse behavior after the colon. - mTokenizer->skipDelimiters(WHITESPACE); - - Behavior behavior; - bool haveCharacter = false; - bool haveFallback = false; - - do { - char ch = mTokenizer->peekChar(); - if (ch == '\'') { - char16_t character; - status_t status = parseCharacterLiteral(&character); - if (status || !character) { - ALOGE("%s: Invalid character literal for key.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - if (haveCharacter) { - ALOGE("%s: Cannot combine multiple character literals or 'none'.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - behavior.character = character; - haveCharacter = true; - } else { - token = mTokenizer->nextToken(WHITESPACE); - if (token == "none") { - if (haveCharacter) { - ALOGE("%s: Cannot combine multiple character literals or 'none'.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - haveCharacter = true; - } else if (token == "fallback") { - mTokenizer->skipDelimiters(WHITESPACE); - token = mTokenizer->nextToken(WHITESPACE); - int32_t keyCode = getKeyCodeByLabel(token.string()); - if (!keyCode) { - ALOGE("%s: Invalid key code label for fallback behavior, got '%s'.", - mTokenizer->getLocation().string(), - token.string()); - return BAD_VALUE; - } - if (haveFallback) { - ALOGE("%s: Cannot combine multiple fallback key codes.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - behavior.fallbackKeyCode = keyCode; - haveFallback = true; - } else { - ALOGE("%s: Expected a key behavior after ':'.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - } - - mTokenizer->skipDelimiters(WHITESPACE); - } while (!mTokenizer->isEol() && mTokenizer->peekChar() != '#'); - - // Add the behavior. - for (size_t i = 0; i < properties.size(); i++) { - const Property& property = properties.itemAt(i); - switch (property.property) { - case PROPERTY_LABEL: - if (key->label) { - ALOGE("%s: Duplicate label for key.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - key->label = behavior.character; -#if DEBUG_PARSER - ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key->label); -#endif - break; - case PROPERTY_NUMBER: - if (key->number) { - ALOGE("%s: Duplicate number for key.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - key->number = behavior.character; -#if DEBUG_PARSER - ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key->number); -#endif - break; - case PROPERTY_META: { - for (Behavior* b = key->firstBehavior; b; b = b->next) { - if (b->metaState == property.metaState) { - ALOGE("%s: Duplicate key behavior for modifier.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - } - Behavior* newBehavior = new Behavior(behavior); - newBehavior->metaState = property.metaState; - newBehavior->next = key->firstBehavior; - key->firstBehavior = newBehavior; -#if DEBUG_PARSER - ALOGD("Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d.", mKeyCode, - newBehavior->metaState, newBehavior->character, newBehavior->fallbackKeyCode); -#endif - break; - } - } - } - return NO_ERROR; -} - -status_t KeyCharacterMap::Parser::finishKey(Key* key) { - // Fill in default number property. - if (!key->number) { - char16_t digit = 0; - char16_t symbol = 0; - for (Behavior* b = key->firstBehavior; b; b = b->next) { - char16_t ch = b->character; - if (ch) { - if (ch >= '0' && ch <= '9') { - digit = ch; - } else if (ch == '(' || ch == ')' || ch == '#' || ch == '*' - || ch == '-' || ch == '+' || ch == ',' || ch == '.' - || ch == '\'' || ch == ':' || ch == ';' || ch == '/') { - symbol = ch; - } - } - } - key->number = digit ? digit : symbol; - } - return NO_ERROR; -} - -status_t KeyCharacterMap::Parser::parseModifier(const String8& token, int32_t* outMetaState) { - if (token == "base") { - *outMetaState = 0; - return NO_ERROR; - } - - int32_t combinedMeta = 0; - - const char* str = token.string(); - const char* start = str; - for (const char* cur = str; ; cur++) { - char ch = *cur; - if (ch == '+' || ch == '\0') { - size_t len = cur - start; - int32_t metaState = 0; - for (size_t i = 0; i < sizeof(modifiers) / sizeof(Modifier); i++) { - if (strlen(modifiers[i].label) == len - && strncmp(modifiers[i].label, start, len) == 0) { - metaState = modifiers[i].metaState; - break; - } - } - if (!metaState) { - return BAD_VALUE; - } - if (combinedMeta & metaState) { - ALOGE("%s: Duplicate modifier combination '%s'.", - mTokenizer->getLocation().string(), token.string()); - return BAD_VALUE; - } - - combinedMeta |= metaState; - start = cur + 1; - - if (ch == '\0') { - break; - } - } - } - *outMetaState = combinedMeta; - return NO_ERROR; -} - -status_t KeyCharacterMap::Parser::parseCharacterLiteral(char16_t* outCharacter) { - char ch = mTokenizer->nextChar(); - if (ch != '\'') { - goto Error; - } - - ch = mTokenizer->nextChar(); - if (ch == '\\') { - // Escape sequence. - ch = mTokenizer->nextChar(); - if (ch == 'n') { - *outCharacter = '\n'; - } else if (ch == 't') { - *outCharacter = '\t'; - } else if (ch == '\\') { - *outCharacter = '\\'; - } else if (ch == '\'') { - *outCharacter = '\''; - } else if (ch == '"') { - *outCharacter = '"'; - } else if (ch == 'u') { - *outCharacter = 0; - for (int i = 0; i < 4; i++) { - ch = mTokenizer->nextChar(); - int digit; - if (ch >= '0' && ch <= '9') { - digit = ch - '0'; - } else if (ch >= 'A' && ch <= 'F') { - digit = ch - 'A' + 10; - } else if (ch >= 'a' && ch <= 'f') { - digit = ch - 'a' + 10; - } else { - goto Error; - } - *outCharacter = (*outCharacter << 4) | digit; - } - } else { - goto Error; - } - } else if (ch >= 32 && ch <= 126 && ch != '\'') { - // ASCII literal character. - *outCharacter = ch; - } else { - goto Error; - } - - ch = mTokenizer->nextChar(); - if (ch != '\'') { - goto Error; - } - - // Ensure that we consumed the entire token. - if (mTokenizer->nextToken(WHITESPACE).isEmpty()) { - return NO_ERROR; - } - -Error: - ALOGE("%s: Malformed character literal.", mTokenizer->getLocation().string()); - return BAD_VALUE; -} - -} // namespace android diff --git a/libs/androidfw/KeyLayoutMap.cpp b/libs/androidfw/KeyLayoutMap.cpp deleted file mode 100644 index ae14f23..0000000 --- a/libs/androidfw/KeyLayoutMap.cpp +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright (C) 2008 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 "KeyLayoutMap" - -#include <stdlib.h> -#include <android/keycodes.h> -#include <androidfw/Keyboard.h> -#include <androidfw/KeyLayoutMap.h> -#include <utils/Log.h> -#include <utils/Errors.h> -#include <utils/Tokenizer.h> -#include <utils/Timers.h> - -// Enables debug output for the parser. -#define DEBUG_PARSER 0 - -// Enables debug output for parser performance. -#define DEBUG_PARSER_PERFORMANCE 0 - -// Enables debug output for mapping. -#define DEBUG_MAPPING 0 - - -namespace android { - -static const char* WHITESPACE = " \t\r"; - -// --- KeyLayoutMap --- - -KeyLayoutMap::KeyLayoutMap() { -} - -KeyLayoutMap::~KeyLayoutMap() { -} - -status_t KeyLayoutMap::load(const String8& filename, sp<KeyLayoutMap>* outMap) { - outMap->clear(); - - Tokenizer* tokenizer; - status_t status = Tokenizer::open(filename, &tokenizer); - if (status) { - ALOGE("Error %d opening key layout map file %s.", status, filename.string()); - } else { - sp<KeyLayoutMap> map = new KeyLayoutMap(); - if (!map.get()) { - ALOGE("Error allocating key layout map."); - status = NO_MEMORY; - } else { -#if DEBUG_PARSER_PERFORMANCE - nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); -#endif - Parser parser(map.get(), tokenizer); - status = parser.parse(); -#if DEBUG_PARSER_PERFORMANCE - nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; - ALOGD("Parsed key layout map file '%s' %d lines in %0.3fms.", - tokenizer->getFilename().string(), tokenizer->getLineNumber(), - elapsedTime / 1000000.0); -#endif - if (!status) { - *outMap = map; - } - } - delete tokenizer; - } - return status; -} - -status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t usageCode, - int32_t* outKeyCode, uint32_t* outFlags) const { - const Key* key = getKey(scanCode, usageCode); - if (!key) { -#if DEBUG_MAPPING - ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode); -#endif - *outKeyCode = AKEYCODE_UNKNOWN; - *outFlags = 0; - return NAME_NOT_FOUND; - } - - *outKeyCode = key->keyCode; - *outFlags = key->flags; - -#if DEBUG_MAPPING - ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d, outFlags=0x%08x.", - scanCode, usageCode, *outKeyCode, *outFlags); -#endif - return NO_ERROR; -} - -const KeyLayoutMap::Key* KeyLayoutMap::getKey(int32_t scanCode, int32_t usageCode) const { - if (usageCode) { - ssize_t index = mKeysByUsageCode.indexOfKey(usageCode); - if (index >= 0) { - return &mKeysByUsageCode.valueAt(index); - } - } - if (scanCode) { - ssize_t index = mKeysByScanCode.indexOfKey(scanCode); - if (index >= 0) { - return &mKeysByScanCode.valueAt(index); - } - } - return NULL; -} - -status_t KeyLayoutMap::findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const { - const size_t N = mKeysByScanCode.size(); - for (size_t i=0; i<N; i++) { - if (mKeysByScanCode.valueAt(i).keyCode == keyCode) { - outScanCodes->add(mKeysByScanCode.keyAt(i)); - } - } - return NO_ERROR; -} - -status_t KeyLayoutMap::mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const { - ssize_t index = mAxes.indexOfKey(scanCode); - if (index < 0) { -#if DEBUG_MAPPING - ALOGD("mapAxis: scanCode=%d ~ Failed.", scanCode); -#endif - return NAME_NOT_FOUND; - } - - *outAxisInfo = mAxes.valueAt(index); - -#if DEBUG_MAPPING - ALOGD("mapAxis: scanCode=%d ~ Result mode=%d, axis=%d, highAxis=%d, " - "splitValue=%d, flatOverride=%d.", - scanCode, - outAxisInfo->mode, outAxisInfo->axis, outAxisInfo->highAxis, - outAxisInfo->splitValue, outAxisInfo->flatOverride); -#endif - return NO_ERROR; -} - - -// --- KeyLayoutMap::Parser --- - -KeyLayoutMap::Parser::Parser(KeyLayoutMap* map, Tokenizer* tokenizer) : - mMap(map), mTokenizer(tokenizer) { -} - -KeyLayoutMap::Parser::~Parser() { -} - -status_t KeyLayoutMap::Parser::parse() { - while (!mTokenizer->isEof()) { -#if DEBUG_PARSER - ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(), - mTokenizer->peekRemainderOfLine().string()); -#endif - - mTokenizer->skipDelimiters(WHITESPACE); - - if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') { - String8 keywordToken = mTokenizer->nextToken(WHITESPACE); - if (keywordToken == "key") { - mTokenizer->skipDelimiters(WHITESPACE); - status_t status = parseKey(); - if (status) return status; - } else if (keywordToken == "axis") { - mTokenizer->skipDelimiters(WHITESPACE); - status_t status = parseAxis(); - if (status) return status; - } else { - ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(), - keywordToken.string()); - return BAD_VALUE; - } - - mTokenizer->skipDelimiters(WHITESPACE); - if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') { - ALOGE("%s: Expected end of line or trailing comment, got '%s'.", - mTokenizer->getLocation().string(), - mTokenizer->peekRemainderOfLine().string()); - return BAD_VALUE; - } - } - - mTokenizer->nextLine(); - } - return NO_ERROR; -} - -status_t KeyLayoutMap::Parser::parseKey() { - String8 codeToken = mTokenizer->nextToken(WHITESPACE); - bool mapUsage = false; - if (codeToken == "usage") { - mapUsage = true; - mTokenizer->skipDelimiters(WHITESPACE); - codeToken = mTokenizer->nextToken(WHITESPACE); - } - - char* end; - int32_t code = int32_t(strtol(codeToken.string(), &end, 0)); - if (*end) { - ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(), - mapUsage ? "usage" : "scan code", codeToken.string()); - return BAD_VALUE; - } - KeyedVector<int32_t, Key>& map = - mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode; - if (map.indexOfKey(code) >= 0) { - ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(), - mapUsage ? "usage" : "scan code", codeToken.string()); - return BAD_VALUE; - } - - mTokenizer->skipDelimiters(WHITESPACE); - String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE); - int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string()); - if (!keyCode) { - ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(), - keyCodeToken.string()); - return BAD_VALUE; - } - - uint32_t flags = 0; - for (;;) { - mTokenizer->skipDelimiters(WHITESPACE); - if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break; - - String8 flagToken = mTokenizer->nextToken(WHITESPACE); - uint32_t flag = getKeyFlagByLabel(flagToken.string()); - if (!flag) { - ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().string(), - flagToken.string()); - return BAD_VALUE; - } - if (flags & flag) { - ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().string(), - flagToken.string()); - return BAD_VALUE; - } - flags |= flag; - } - -#if DEBUG_PARSER - ALOGD("Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.", - mapUsage ? "usage" : "scan code", code, keyCode, flags); -#endif - Key key; - key.keyCode = keyCode; - key.flags = flags; - map.add(code, key); - return NO_ERROR; -} - -status_t KeyLayoutMap::Parser::parseAxis() { - String8 scanCodeToken = mTokenizer->nextToken(WHITESPACE); - char* end; - int32_t scanCode = int32_t(strtol(scanCodeToken.string(), &end, 0)); - if (*end) { - ALOGE("%s: Expected axis scan code number, got '%s'.", mTokenizer->getLocation().string(), - scanCodeToken.string()); - return BAD_VALUE; - } - if (mMap->mAxes.indexOfKey(scanCode) >= 0) { - ALOGE("%s: Duplicate entry for axis scan code '%s'.", mTokenizer->getLocation().string(), - scanCodeToken.string()); - return BAD_VALUE; - } - - AxisInfo axisInfo; - - mTokenizer->skipDelimiters(WHITESPACE); - String8 token = mTokenizer->nextToken(WHITESPACE); - if (token == "invert") { - axisInfo.mode = AxisInfo::MODE_INVERT; - - mTokenizer->skipDelimiters(WHITESPACE); - String8 axisToken = mTokenizer->nextToken(WHITESPACE); - axisInfo.axis = getAxisByLabel(axisToken.string()); - if (axisInfo.axis < 0) { - ALOGE("%s: Expected inverted axis label, got '%s'.", - mTokenizer->getLocation().string(), axisToken.string()); - return BAD_VALUE; - } - } else if (token == "split") { - axisInfo.mode = AxisInfo::MODE_SPLIT; - - mTokenizer->skipDelimiters(WHITESPACE); - String8 splitToken = mTokenizer->nextToken(WHITESPACE); - axisInfo.splitValue = int32_t(strtol(splitToken.string(), &end, 0)); - if (*end) { - ALOGE("%s: Expected split value, got '%s'.", - mTokenizer->getLocation().string(), splitToken.string()); - return BAD_VALUE; - } - - mTokenizer->skipDelimiters(WHITESPACE); - String8 lowAxisToken = mTokenizer->nextToken(WHITESPACE); - axisInfo.axis = getAxisByLabel(lowAxisToken.string()); - if (axisInfo.axis < 0) { - ALOGE("%s: Expected low axis label, got '%s'.", - mTokenizer->getLocation().string(), lowAxisToken.string()); - return BAD_VALUE; - } - - mTokenizer->skipDelimiters(WHITESPACE); - String8 highAxisToken = mTokenizer->nextToken(WHITESPACE); - axisInfo.highAxis = getAxisByLabel(highAxisToken.string()); - if (axisInfo.highAxis < 0) { - ALOGE("%s: Expected high axis label, got '%s'.", - mTokenizer->getLocation().string(), highAxisToken.string()); - return BAD_VALUE; - } - } else { - axisInfo.axis = getAxisByLabel(token.string()); - if (axisInfo.axis < 0) { - ALOGE("%s: Expected axis label, 'split' or 'invert', got '%s'.", - mTokenizer->getLocation().string(), token.string()); - return BAD_VALUE; - } - } - - for (;;) { - mTokenizer->skipDelimiters(WHITESPACE); - if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') { - break; - } - String8 keywordToken = mTokenizer->nextToken(WHITESPACE); - if (keywordToken == "flat") { - mTokenizer->skipDelimiters(WHITESPACE); - String8 flatToken = mTokenizer->nextToken(WHITESPACE); - axisInfo.flatOverride = int32_t(strtol(flatToken.string(), &end, 0)); - if (*end) { - ALOGE("%s: Expected flat value, got '%s'.", - mTokenizer->getLocation().string(), flatToken.string()); - return BAD_VALUE; - } - } else { - ALOGE("%s: Expected keyword 'flat', got '%s'.", - mTokenizer->getLocation().string(), keywordToken.string()); - return BAD_VALUE; - } - } - -#if DEBUG_PARSER - ALOGD("Parsed axis: scanCode=%d, mode=%d, axis=%d, highAxis=%d, " - "splitValue=%d, flatOverride=%d.", - scanCode, - axisInfo.mode, axisInfo.axis, axisInfo.highAxis, - axisInfo.splitValue, axisInfo.flatOverride); -#endif - mMap->mAxes.add(scanCode, axisInfo); - return NO_ERROR; -} - -}; diff --git a/libs/androidfw/Keyboard.cpp b/libs/androidfw/Keyboard.cpp deleted file mode 100644 index 4ddbeab..0000000 --- a/libs/androidfw/Keyboard.cpp +++ /dev/null @@ -1,298 +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. - */ - -#define LOG_TAG "Keyboard" - -#include <stdlib.h> -#include <unistd.h> -#include <limits.h> - -#include <androidfw/Keyboard.h> -#include <androidfw/KeycodeLabels.h> -#include <androidfw/KeyLayoutMap.h> -#include <androidfw/KeyCharacterMap.h> -#include <androidfw/InputDevice.h> -#include <utils/Errors.h> -#include <utils/Log.h> -#include <cutils/properties.h> - -namespace android { - -// --- KeyMap --- - -KeyMap::KeyMap() { -} - -KeyMap::~KeyMap() { -} - -status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier, - const PropertyMap* deviceConfiguration) { - // Use the configured key layout if available. - if (deviceConfiguration) { - String8 keyLayoutName; - if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"), - keyLayoutName)) { - status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName); - if (status == NAME_NOT_FOUND) { - ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but " - "it was not found.", - deviceIdenfifier.name.string(), keyLayoutName.string()); - } - } - - String8 keyCharacterMapName; - if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"), - keyCharacterMapName)) { - status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName); - if (status == NAME_NOT_FOUND) { - ALOGE("Configuration for keyboard device '%s' requested keyboard character " - "map '%s' but it was not found.", - deviceIdenfifier.name.string(), keyLayoutName.string()); - } - } - - if (isComplete()) { - return OK; - } - } - - // Try searching by device identifier. - if (probeKeyMap(deviceIdenfifier, String8::empty())) { - return OK; - } - - // Fall back on the Generic key map. - // TODO Apply some additional heuristics here to figure out what kind of - // generic key map to use (US English, etc.) for typical external keyboards. - if (probeKeyMap(deviceIdenfifier, String8("Generic"))) { - return OK; - } - - // Try the Virtual key map as a last resort. - if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) { - return OK; - } - - // Give up! - ALOGE("Could not determine key map for device '%s' and no default key maps were found!", - deviceIdenfifier.name.string()); - return NAME_NOT_FOUND; -} - -bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier, - const String8& keyMapName) { - if (!haveKeyLayout()) { - loadKeyLayout(deviceIdentifier, keyMapName); - } - if (!haveKeyCharacterMap()) { - loadKeyCharacterMap(deviceIdentifier, keyMapName); - } - return isComplete(); -} - -status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier, - const String8& name) { - String8 path(getPath(deviceIdentifier, name, - INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT)); - if (path.isEmpty()) { - return NAME_NOT_FOUND; - } - - status_t status = KeyLayoutMap::load(path, &keyLayoutMap); - if (status) { - return status; - } - - keyLayoutFile.setTo(path); - return OK; -} - -status_t KeyMap::loadKeyCharacterMap(const InputDeviceIdentifier& deviceIdentifier, - const String8& name) { - String8 path(getPath(deviceIdentifier, name, - INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP)); - if (path.isEmpty()) { - return NAME_NOT_FOUND; - } - - status_t status = KeyCharacterMap::load(path, - KeyCharacterMap::FORMAT_BASE, &keyCharacterMap); - if (status) { - return status; - } - - keyCharacterMapFile.setTo(path); - return OK; -} - -String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier, - const String8& name, InputDeviceConfigurationFileType type) { - return name.isEmpty() - ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type) - : getInputDeviceConfigurationFilePathByName(name, type); -} - - -// --- Global functions --- - -bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier, - const PropertyMap* deviceConfiguration, const KeyMap* keyMap) { - if (!keyMap->haveKeyCharacterMap() - || keyMap->keyCharacterMap->getKeyboardType() - == KeyCharacterMap::KEYBOARD_TYPE_SPECIAL_FUNCTION) { - return false; - } - - if (deviceConfiguration) { - bool builtIn = false; - if (deviceConfiguration->tryGetProperty(String8("keyboard.builtIn"), builtIn) - && builtIn) { - return true; - } - } - - return strstr(deviceIdentifier.name.string(), "-keypad"); -} - -static int lookupValueByLabel(const char* literal, const KeycodeLabel *list) { - while (list->literal) { - if (strcmp(literal, list->literal) == 0) { - return list->value; - } - list++; - } - return list->value; -} - -static const char* lookupLabelByValue(int value, const KeycodeLabel *list) { - while (list->literal) { - if (list->value == value) { - return list->literal; - } - list++; - } - return NULL; -} - -int32_t getKeyCodeByLabel(const char* label) { - return int32_t(lookupValueByLabel(label, KEYCODES)); -} - -uint32_t getKeyFlagByLabel(const char* label) { - return uint32_t(lookupValueByLabel(label, FLAGS)); -} - -int32_t getAxisByLabel(const char* label) { - return int32_t(lookupValueByLabel(label, AXES)); -} - -const char* getAxisLabel(int32_t axisId) { - return lookupLabelByValue(axisId, AXES); -} - -static int32_t setEphemeralMetaState(int32_t mask, bool down, int32_t oldMetaState) { - int32_t newMetaState; - if (down) { - newMetaState = oldMetaState | mask; - } else { - newMetaState = oldMetaState & - ~(mask | AMETA_ALT_ON | AMETA_SHIFT_ON | AMETA_CTRL_ON | AMETA_META_ON); - } - - if (newMetaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { - newMetaState |= AMETA_ALT_ON; - } - - if (newMetaState & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { - newMetaState |= AMETA_SHIFT_ON; - } - - if (newMetaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) { - newMetaState |= AMETA_CTRL_ON; - } - - if (newMetaState & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) { - newMetaState |= AMETA_META_ON; - } - return newMetaState; -} - -static int32_t toggleLockedMetaState(int32_t mask, bool down, int32_t oldMetaState) { - if (down) { - return oldMetaState; - } else { - return oldMetaState ^ mask; - } -} - -int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) { - int32_t mask; - switch (keyCode) { - case AKEYCODE_ALT_LEFT: - return setEphemeralMetaState(AMETA_ALT_LEFT_ON, down, oldMetaState); - case AKEYCODE_ALT_RIGHT: - return setEphemeralMetaState(AMETA_ALT_RIGHT_ON, down, oldMetaState); - case AKEYCODE_SHIFT_LEFT: - return setEphemeralMetaState(AMETA_SHIFT_LEFT_ON, down, oldMetaState); - case AKEYCODE_SHIFT_RIGHT: - return setEphemeralMetaState(AMETA_SHIFT_RIGHT_ON, down, oldMetaState); - case AKEYCODE_SYM: - return setEphemeralMetaState(AMETA_SYM_ON, down, oldMetaState); - case AKEYCODE_FUNCTION: - return setEphemeralMetaState(AMETA_FUNCTION_ON, down, oldMetaState); - case AKEYCODE_CTRL_LEFT: - return setEphemeralMetaState(AMETA_CTRL_LEFT_ON, down, oldMetaState); - case AKEYCODE_CTRL_RIGHT: - return setEphemeralMetaState(AMETA_CTRL_RIGHT_ON, down, oldMetaState); - case AKEYCODE_META_LEFT: - return setEphemeralMetaState(AMETA_META_LEFT_ON, down, oldMetaState); - case AKEYCODE_META_RIGHT: - return setEphemeralMetaState(AMETA_META_RIGHT_ON, down, oldMetaState); - case AKEYCODE_CAPS_LOCK: - return toggleLockedMetaState(AMETA_CAPS_LOCK_ON, down, oldMetaState); - case AKEYCODE_NUM_LOCK: - return toggleLockedMetaState(AMETA_NUM_LOCK_ON, down, oldMetaState); - case AKEYCODE_SCROLL_LOCK: - return toggleLockedMetaState(AMETA_SCROLL_LOCK_ON, down, oldMetaState); - default: - return oldMetaState; - } -} - -bool isMetaKey(int32_t keyCode) { - switch (keyCode) { - case AKEYCODE_ALT_LEFT: - case AKEYCODE_ALT_RIGHT: - case AKEYCODE_SHIFT_LEFT: - case AKEYCODE_SHIFT_RIGHT: - case AKEYCODE_SYM: - case AKEYCODE_FUNCTION: - case AKEYCODE_CTRL_LEFT: - case AKEYCODE_CTRL_RIGHT: - case AKEYCODE_META_LEFT: - case AKEYCODE_META_RIGHT: - case AKEYCODE_CAPS_LOCK: - case AKEYCODE_NUM_LOCK: - case AKEYCODE_SCROLL_LOCK: - return true; - default: - return false; - } -} - - -} // namespace android diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index a730065..1cc3563 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -24,7 +24,6 @@ #include <utils/Log.h> #include <utils/String16.h> #include <utils/String8.h> -#include <utils/TextOutput.h> #include <stdlib.h> #include <string.h> @@ -36,7 +35,7 @@ #define INT32_MAX ((int32_t)(2147483647)) #endif -#define POOL_NOISY(x) //x +#define STRING_POOL_NOISY(x) //x #define XML_NOISY(x) //x #define TABLE_NOISY(x) //x #define TABLE_GETENTRY(x) //x @@ -379,7 +378,6 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) size_t charSize; if (mHeader->flags&ResStringPool_header::UTF8_FLAG) { charSize = sizeof(uint8_t); - mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t**)); } else { charSize = sizeof(char16_t); } @@ -594,6 +592,23 @@ const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) { AutoMutex lock(mDecodeLock); + if (mCache == NULL) { +#ifndef HAVE_ANDROID_OS + STRING_POOL_NOISY(ALOGI("CREATING STRING CACHE OF %d bytes", + mHeader->stringCount*sizeof(char16_t**))); +#else + // We do not want to be in this case when actually running Android. + ALOGW("CREATING STRING CACHE OF %d bytes", + mHeader->stringCount*sizeof(char16_t**)); +#endif + mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t**)); + if (mCache == NULL) { + ALOGW("No memory trying to allocate decode cache table of %d bytes\n", + (int)(mHeader->stringCount*sizeof(char16_t**))); + return NULL; + } + } + if (mCache[idx] != NULL) { return mCache[idx]; } @@ -613,6 +628,7 @@ const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const return NULL; } + STRING_POOL_NOISY(ALOGI("Caching UTF8 string: %s", u8str)); utf8_to_utf16(u8str, u8len, u16str); mCache[idx] = u16str; return u16str; @@ -634,20 +650,20 @@ const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const const char* ResStringPool::string8At(size_t idx, size_t* outLen) const { if (mError == NO_ERROR && idx < mHeader->stringCount) { - const bool isUTF8 = (mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0; - const uint32_t off = mEntries[idx]/(isUTF8?sizeof(char):sizeof(char16_t)); + if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) == 0) { + return NULL; + } + const uint32_t off = mEntries[idx]/sizeof(char); if (off < (mStringPoolSize-1)) { - if (isUTF8) { - const uint8_t* strings = (uint8_t*)mStrings; - const uint8_t* str = strings+off; - *outLen = decodeLength(&str); - size_t encLen = decodeLength(&str); - if ((uint32_t)(str+encLen-strings) < mStringPoolSize) { - return (const char*)str; - } else { - ALOGW("Bad string block: string #%d extends to %d, past end at %d\n", - (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize); - } + const uint8_t* strings = (uint8_t*)mStrings; + const uint8_t* str = strings+off; + *outLen = decodeLength(&str); + size_t encLen = decodeLength(&str); + if ((uint32_t)(str+encLen-strings) < mStringPoolSize) { + return (const char*)str; + } else { + ALOGW("Bad string block: string #%d extends to %d, past end at %d\n", + (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize); } } else { ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n", @@ -696,45 +712,104 @@ ssize_t ResStringPool::indexOfString(const char16_t* str, size_t strLen) const size_t len; - // TODO optimize searching for UTF-8 strings taking into account - // the cache fill to determine when to convert the searched-for - // string key to UTF-8. - - if (mHeader->flags&ResStringPool_header::SORTED_FLAG) { - // Do a binary search for the string... - ssize_t l = 0; - ssize_t h = mHeader->stringCount-1; - - ssize_t mid; - while (l <= h) { - mid = l + (h - l)/2; - const char16_t* s = stringAt(mid, &len); - int c = s ? strzcmp16(s, len, str, strLen) : -1; - POOL_NOISY(printf("Looking for %s, at %s, cmp=%d, l/mid/h=%d/%d/%d\n", - String8(str).string(), - String8(s).string(), - c, (int)l, (int)mid, (int)h)); - if (c == 0) { - return mid; - } else if (c < 0) { - l = mid + 1; - } else { - h = mid - 1; + if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0) { + STRING_POOL_NOISY(ALOGI("indexOfString UTF-8: %s", String8(str, strLen).string())); + + // The string pool contains UTF 8 strings; we don't want to cause + // temporary UTF-16 strings to be created as we search. + if (mHeader->flags&ResStringPool_header::SORTED_FLAG) { + // Do a binary search for the string... this is a little tricky, + // because the strings are sorted with strzcmp16(). So to match + // the ordering, we need to convert strings in the pool to UTF-16. + // But we don't want to hit the cache, so instead we will have a + // local temporary allocation for the conversions. + char16_t* convBuffer = (char16_t*)malloc(strLen+4); + ssize_t l = 0; + ssize_t h = mHeader->stringCount-1; + + ssize_t mid; + while (l <= h) { + mid = l + (h - l)/2; + const uint8_t* s = (const uint8_t*)string8At(mid, &len); + int c; + if (s != NULL) { + char16_t* end = utf8_to_utf16_n(s, len, convBuffer, strLen+3); + *end = 0; + c = strzcmp16(convBuffer, end-convBuffer, str, strLen); + } else { + c = -1; + } + STRING_POOL_NOISY(ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n", + (const char*)s, c, (int)l, (int)mid, (int)h)); + if (c == 0) { + STRING_POOL_NOISY(ALOGI("MATCH!")); + free(convBuffer); + return mid; + } else if (c < 0) { + l = mid + 1; + } else { + h = mid - 1; + } + } + free(convBuffer); + } else { + // It is unusual to get the ID from an unsorted string block... + // most often this happens because we want to get IDs for style + // span tags; since those always appear at the end of the string + // block, start searching at the back. + String8 str8(str, strLen); + const size_t str8Len = str8.size(); + for (int i=mHeader->stringCount-1; i>=0; i--) { + const char* s = string8At(i, &len); + STRING_POOL_NOISY(ALOGI("Looking at %s, i=%d\n", + String8(s).string(), + i)); + if (s && str8Len == len && memcmp(s, str8.string(), str8Len) == 0) { + STRING_POOL_NOISY(ALOGI("MATCH!")); + return i; + } } } + } else { - // It is unusual to get the ID from an unsorted string block... - // most often this happens because we want to get IDs for style - // span tags; since those always appear at the end of the string - // block, start searching at the back. - for (int i=mHeader->stringCount-1; i>=0; i--) { - const char16_t* s = stringAt(i, &len); - POOL_NOISY(printf("Looking for %s, at %s, i=%d\n", - String8(str, strLen).string(), - String8(s).string(), - i)); - if (s && strzcmp16(s, len, str, strLen) == 0) { - return i; + STRING_POOL_NOISY(ALOGI("indexOfString UTF-16: %s", String8(str, strLen).string())); + + if (mHeader->flags&ResStringPool_header::SORTED_FLAG) { + // Do a binary search for the string... + ssize_t l = 0; + ssize_t h = mHeader->stringCount-1; + + ssize_t mid; + while (l <= h) { + mid = l + (h - l)/2; + const char16_t* s = stringAt(mid, &len); + int c = s ? strzcmp16(s, len, str, strLen) : -1; + STRING_POOL_NOISY(ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n", + String8(s).string(), + c, (int)l, (int)mid, (int)h)); + if (c == 0) { + STRING_POOL_NOISY(ALOGI("MATCH!")); + return mid; + } else if (c < 0) { + l = mid + 1; + } else { + h = mid - 1; + } + } + } else { + // It is unusual to get the ID from an unsorted string block... + // most often this happens because we want to get IDs for style + // span tags; since those always appear at the end of the string + // block, start searching at the back. + for (int i=mHeader->stringCount-1; i>=0; i--) { + const char16_t* s = stringAt(i, &len); + STRING_POOL_NOISY(ALOGI("Looking at %s, i=%d\n", + String8(s).string(), + i)); + if (s && strLen == len && strzcmp16(s, len, str, strLen) == 0) { + STRING_POOL_NOISY(ALOGI("MATCH!")); + return i; + } } } } @@ -937,6 +1012,14 @@ const uint16_t* ResXMLParser::getAttributeNamespace(size_t idx, size_t* outLen) return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; } +const char* ResXMLParser::getAttributeNamespace8(size_t idx, size_t* outLen) const +{ + int32_t id = getAttributeNamespaceID(idx); + //printf("attribute namespace=%d idx=%d event=%p\n", id, idx, mEventCode); + //XML_NOISY(printf("getAttributeNamespace 0x%x=0x%x\n", idx, id)); + return id >= 0 ? mTree.mStrings.string8At(id, outLen) : NULL; +} + int32_t ResXMLParser::getAttributeNameID(size_t idx) const { if (mEventCode == START_TAG) { @@ -960,6 +1043,14 @@ const uint16_t* ResXMLParser::getAttributeName(size_t idx, size_t* outLen) const return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL; } +const char* ResXMLParser::getAttributeName8(size_t idx, size_t* outLen) const +{ + int32_t id = getAttributeNameID(idx); + //printf("attribute name=%d idx=%d event=%p\n", id, idx, mEventCode); + //XML_NOISY(printf("getAttributeName 0x%x=0x%x\n", idx, id)); + return id >= 0 ? mTree.mStrings.string8At(id, outLen) : NULL; +} + uint32_t ResXMLParser::getAttributeNameResID(size_t idx) const { int32_t id = getAttributeNameID(idx); @@ -1049,22 +1140,67 @@ ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen, const char16_t* attr, size_t attrLen) const { if (mEventCode == START_TAG) { + if (attr == NULL) { + return NAME_NOT_FOUND; + } const size_t N = getAttributeCount(); - for (size_t i=0; i<N; i++) { - size_t curNsLen, curAttrLen; - const char16_t* curNs = getAttributeNamespace(i, &curNsLen); - const char16_t* curAttr = getAttributeName(i, &curAttrLen); - //printf("%d: ns=%p attr=%p curNs=%p curAttr=%p\n", - // i, ns, attr, curNs, curAttr); - //printf(" --> attr=%s, curAttr=%s\n", - // String8(attr).string(), String8(curAttr).string()); - if (attr && curAttr && (strzcmp16(attr, attrLen, curAttr, curAttrLen) == 0)) { - if (ns == NULL) { - if (curNs == NULL) return i; - } else if (curNs != NULL) { - //printf(" --> ns=%s, curNs=%s\n", - // String8(ns).string(), String8(curNs).string()); - if (strzcmp16(ns, nsLen, curNs, curNsLen) == 0) return i; + if (mTree.mStrings.isUTF8()) { + String8 ns8, attr8; + if (ns != NULL) { + ns8 = String8(ns, nsLen); + } + attr8 = String8(attr, attrLen); + STRING_POOL_NOISY(ALOGI("indexOfAttribute UTF8 %s (%d) / %s (%d)", ns8.string(), nsLen, + attr8.string(), attrLen)); + for (size_t i=0; i<N; i++) { + size_t curNsLen = 0, curAttrLen = 0; + const char* curNs = getAttributeNamespace8(i, &curNsLen); + const char* curAttr = getAttributeName8(i, &curAttrLen); + STRING_POOL_NOISY(ALOGI(" curNs=%s (%d), curAttr=%s (%d)", curNs, curNsLen, + curAttr, curAttrLen)); + if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen + && memcmp(attr8.string(), curAttr, attrLen) == 0) { + if (ns == NULL) { + if (curNs == NULL) { + STRING_POOL_NOISY(ALOGI(" FOUND!")); + return i; + } + } else if (curNs != NULL) { + //printf(" --> ns=%s, curNs=%s\n", + // String8(ns).string(), String8(curNs).string()); + if (memcmp(ns8.string(), curNs, nsLen) == 0) { + STRING_POOL_NOISY(ALOGI(" FOUND!")); + return i; + } + } + } + } + } else { + STRING_POOL_NOISY(ALOGI("indexOfAttribute UTF16 %s (%d) / %s (%d)", + String8(ns, nsLen).string(), nsLen, + String8(attr, attrLen).string(), attrLen)); + for (size_t i=0; i<N; i++) { + size_t curNsLen = 0, curAttrLen = 0; + const char16_t* curNs = getAttributeNamespace(i, &curNsLen); + const char16_t* curAttr = getAttributeName(i, &curAttrLen); + STRING_POOL_NOISY(ALOGI(" curNs=%s (%d), curAttr=%s (%d)", + String8(curNs, curNsLen).string(), curNsLen, + String8(curAttr, curAttrLen).string(), curAttrLen)); + if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen + && (memcmp(attr, curAttr, attrLen*sizeof(char16_t)) == 0)) { + if (ns == NULL) { + if (curNs == NULL) { + STRING_POOL_NOISY(ALOGI(" FOUND!")); + return i; + } + } else if (curNs != NULL) { + //printf(" --> ns=%s, curNs=%s\n", + // String8(ns).string(), String8(curNs).string()); + if (memcmp(ns, curNs, nsLen*sizeof(char16_t)) == 0) { + STRING_POOL_NOISY(ALOGI(" FOUND!")); + return i; + } + } } } } @@ -2941,7 +3077,7 @@ void ResTable::uninit() mHeaders.clear(); } -bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const +bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const { if (mError != NO_ERROR) { return false; @@ -2981,13 +3117,28 @@ bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const outName->package = grp->name.string(); outName->packageLen = grp->name.size(); - outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen); - 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->type == NULL || outName->name == NULL) { - return false; + 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; + } + } + 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; + } } return true; @@ -4486,7 +4637,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, while (cnt > 0) { if (!Res_INTERNALID(bag->map.name.ident)) { //printf("Trying attr #%08x\n", bag->map.name.ident); - if (getResourceName(bag->map.name.ident, &rname)) { + if (getResourceName(bag->map.name.ident, false, &rname)) { #if 0 printf("Matching %s against %s (0x%08x)\n", String8(s, len).string(), @@ -4539,7 +4690,7 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, for (i=0; i<cnt; i++, bagi++) { if (!Res_INTERNALID(bagi->map.name.ident)) { //printf("Trying attr #%08x\n", bagi->map.name.ident); - if (getResourceName(bagi->map.name.ident, &rname)) { + if (getResourceName(bagi->map.name.ident, false, &rname)) { #if 0 printf("Matching %s against %s (0x%08x)\n", String8(start,pos-start).string(), @@ -5217,7 +5368,7 @@ status_t ResTable::createIdmap(const ResTable& overlay, uint32_t originalCrc, ui | (0x00ff0000 & ((typeIndex+1)<<16)) | (0x0000ffff & (entryIndex)); resource_name resName; - if (!this->getResourceName(resID, &resName)) { + if (!this->getResourceName(resID, true, &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); @@ -5324,13 +5475,12 @@ bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes, } -#ifndef HAVE_ANDROID_OS #define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string()) #define CHAR16_ARRAY_EQ(constant, var, len) \ ((len == (sizeof(constant)/sizeof(constant[0]))) && (0 == memcmp((var), (constant), (len)))) -void print_complex(uint32_t complex, bool isFraction) +static void print_complex(uint32_t complex, bool isFraction) { const float MANTISSA_MULT = 1.0f / (1<<Res_value::COMPLEX_MANTISSA_SHIFT); @@ -5485,12 +5635,23 @@ void ResTable::print(bool inclValues) const | (0x00ff0000 & ((typeIndex+1)<<16)) | (0x0000ffff & (entryIndex)); resource_name resName; - if (this->getResourceName(resID, &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), - CHAR16_TO_CSTR(resName.type, resName.typeLen), - CHAR16_TO_CSTR(resName.name, resName.nameLen), + type8.string(), name8.string(), dtohl(typeConfigs->typeSpecFlags[entryIndex])); } else { printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID); @@ -5533,11 +5694,22 @@ void ResTable::print(bool inclValues) const | (0x00ff0000 & ((typeIndex+1)<<16)) | (0x0000ffff & (entryIndex)); resource_name resName; - if (this->getResourceName(resID, &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(" resource 0x%08x %s:%s/%s: ", resID, CHAR16_TO_CSTR(resName.package, resName.packageLen), - CHAR16_TO_CSTR(resName.type, resName.typeLen), - CHAR16_TO_CSTR(resName.name, resName.nameLen)); + type8.string(), name8.string()); } else { printf(" INVALID RESOURCE 0x%08x: ", resID); } @@ -5621,6 +5793,4 @@ void ResTable::print(bool inclValues) const } } -#endif // HAVE_ANDROID_OS - } // namespace android diff --git a/libs/androidfw/VelocityControl.cpp b/libs/androidfw/VelocityControl.cpp deleted file mode 100644 index cde2b76..0000000 --- a/libs/androidfw/VelocityControl.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 "VelocityControl" -//#define LOG_NDEBUG 0 - -// Log debug messages about acceleration. -#define DEBUG_ACCELERATION 0 - -#include <math.h> -#include <limits.h> - -#include <androidfw/VelocityControl.h> -#include <utils/BitSet.h> -#include <utils/Timers.h> - -namespace android { - -// --- VelocityControl --- - -const nsecs_t VelocityControl::STOP_TIME; - -VelocityControl::VelocityControl() { - reset(); -} - -void VelocityControl::setParameters(const VelocityControlParameters& parameters) { - mParameters = parameters; - reset(); -} - -void VelocityControl::reset() { - mLastMovementTime = LLONG_MIN; - mRawPosition.x = 0; - mRawPosition.y = 0; - mVelocityTracker.clear(); -} - -void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { - if ((deltaX && *deltaX) || (deltaY && *deltaY)) { - if (eventTime >= mLastMovementTime + STOP_TIME) { -#if DEBUG_ACCELERATION - ALOGD("VelocityControl: stopped, last movement was %0.3fms ago", - (eventTime - mLastMovementTime) * 0.000001f); -#endif - reset(); - } - - mLastMovementTime = eventTime; - if (deltaX) { - mRawPosition.x += *deltaX; - } - if (deltaY) { - mRawPosition.y += *deltaY; - } - mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), &mRawPosition); - - float vx, vy; - float scale = mParameters.scale; - if (mVelocityTracker.getVelocity(0, &vx, &vy)) { - float speed = hypotf(vx, vy) * scale; - if (speed >= mParameters.highThreshold) { - // Apply full acceleration above the high speed threshold. - scale *= mParameters.acceleration; - } else if (speed > mParameters.lowThreshold) { - // Linearly interpolate the acceleration to apply between the low and high - // speed thresholds. - scale *= 1 + (speed - mParameters.lowThreshold) - / (mParameters.highThreshold - mParameters.lowThreshold) - * (mParameters.acceleration - 1); - } - -#if DEBUG_ACCELERATION - ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): " - "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f", - mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, - mParameters.acceleration, - vx, vy, speed, scale / mParameters.scale); -#endif - } else { -#if DEBUG_ACCELERATION - ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity", - mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, - mParameters.acceleration); -#endif - } - - if (deltaX) { - *deltaX *= scale; - } - if (deltaY) { - *deltaY *= scale; - } - } -} - -} // namespace android diff --git a/libs/androidfw/VelocityTracker.cpp b/libs/androidfw/VelocityTracker.cpp deleted file mode 100644 index f48ec62..0000000 --- a/libs/androidfw/VelocityTracker.cpp +++ /dev/null @@ -1,928 +0,0 @@ -/* - * 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 "VelocityTracker" -//#define LOG_NDEBUG 0 - -// Log debug messages about velocity tracking. -#define DEBUG_VELOCITY 0 - -// Log debug messages about the progress of the algorithm itself. -#define DEBUG_STRATEGY 0 - -#include <math.h> -#include <limits.h> - -#include <androidfw/VelocityTracker.h> -#include <utils/BitSet.h> -#include <utils/String8.h> -#include <utils/Timers.h> - -#include <cutils/properties.h> - -namespace android { - -// Nanoseconds per milliseconds. -static const nsecs_t NANOS_PER_MS = 1000000; - -// Threshold for determining that a pointer has stopped moving. -// Some input devices do not send ACTION_MOVE events in the case where a pointer has -// stopped. We need to detect this case so that we can accurately predict the -// velocity after the pointer starts moving again. -static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS; - - -static float vectorDot(const float* a, const float* b, uint32_t m) { - float r = 0; - while (m--) { - r += *(a++) * *(b++); - } - return r; -} - -static float vectorNorm(const float* a, uint32_t m) { - float r = 0; - while (m--) { - float t = *(a++); - r += t * t; - } - return sqrtf(r); -} - -#if DEBUG_STRATEGY || DEBUG_VELOCITY -static String8 vectorToString(const float* a, uint32_t m) { - String8 str; - str.append("["); - while (m--) { - str.appendFormat(" %f", *(a++)); - if (m) { - str.append(","); - } - } - str.append(" ]"); - return str; -} - -static String8 matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) { - String8 str; - str.append("["); - for (size_t i = 0; i < m; i++) { - if (i) { - str.append(","); - } - str.append(" ["); - for (size_t j = 0; j < n; j++) { - if (j) { - str.append(","); - } - str.appendFormat(" %f", a[rowMajor ? i * n + j : j * m + i]); - } - str.append(" ]"); - } - str.append(" ]"); - return str; -} -#endif - - -// --- VelocityTracker --- - -// The default velocity tracker strategy. -// Although other strategies are available for testing and comparison purposes, -// this is the strategy that applications will actually use. Be very careful -// when adjusting the default strategy because it can dramatically affect -// (often in a bad way) the user experience. -const char* VelocityTracker::DEFAULT_STRATEGY = "lsq2"; - -VelocityTracker::VelocityTracker(const char* strategy) : - mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) { - char value[PROPERTY_VALUE_MAX]; - - // Allow the default strategy to be overridden using a system property for debugging. - if (!strategy) { - int length = property_get("debug.velocitytracker.strategy", value, NULL); - if (length > 0) { - strategy = value; - } else { - strategy = DEFAULT_STRATEGY; - } - } - - // Configure the strategy. - if (!configureStrategy(strategy)) { - ALOGD("Unrecognized velocity tracker strategy name '%s'.", strategy); - if (!configureStrategy(DEFAULT_STRATEGY)) { - LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%s'!", - strategy); - } - } -} - -VelocityTracker::~VelocityTracker() { - delete mStrategy; -} - -bool VelocityTracker::configureStrategy(const char* strategy) { - mStrategy = createStrategy(strategy); - return mStrategy != NULL; -} - -VelocityTrackerStrategy* VelocityTracker::createStrategy(const char* strategy) { - if (!strcmp("lsq1", strategy)) { - // 1st order least squares. Quality: POOR. - // Frequently underfits the touch data especially when the finger accelerates - // or changes direction. Often underestimates velocity. The direction - // is overly influenced by historical touch points. - return new LeastSquaresVelocityTrackerStrategy(1); - } - if (!strcmp("lsq2", strategy)) { - // 2nd order least squares. Quality: VERY GOOD. - // Pretty much ideal, but can be confused by certain kinds of touch data, - // particularly if the panel has a tendency to generate delayed, - // duplicate or jittery touch coordinates when the finger is released. - return new LeastSquaresVelocityTrackerStrategy(2); - } - if (!strcmp("lsq3", strategy)) { - // 3rd order least squares. Quality: UNUSABLE. - // Frequently overfits the touch data yielding wildly divergent estimates - // of the velocity when the finger is released. - return new LeastSquaresVelocityTrackerStrategy(3); - } - if (!strcmp("wlsq2-delta", strategy)) { - // 2nd order weighted least squares, delta weighting. Quality: EXPERIMENTAL - return new LeastSquaresVelocityTrackerStrategy(2, - LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA); - } - if (!strcmp("wlsq2-central", strategy)) { - // 2nd order weighted least squares, central weighting. Quality: EXPERIMENTAL - return new LeastSquaresVelocityTrackerStrategy(2, - LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL); - } - if (!strcmp("wlsq2-recent", strategy)) { - // 2nd order weighted least squares, recent weighting. Quality: EXPERIMENTAL - return new LeastSquaresVelocityTrackerStrategy(2, - LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT); - } - if (!strcmp("int1", strategy)) { - // 1st order integrating filter. Quality: GOOD. - // Not as good as 'lsq2' because it cannot estimate acceleration but it is - // more tolerant of errors. Like 'lsq1', this strategy tends to underestimate - // the velocity of a fling but this strategy tends to respond to changes in - // direction more quickly and accurately. - return new IntegratingVelocityTrackerStrategy(1); - } - if (!strcmp("int2", strategy)) { - // 2nd order integrating filter. Quality: EXPERIMENTAL. - // For comparison purposes only. Unlike 'int1' this strategy can compensate - // for acceleration but it typically overestimates the effect. - return new IntegratingVelocityTrackerStrategy(2); - } - if (!strcmp("legacy", strategy)) { - // Legacy velocity tracker algorithm. Quality: POOR. - // For comparison purposes only. This algorithm is strongly influenced by - // old data points, consistently underestimates velocity and takes a very long - // time to adjust to changes in direction. - return new LegacyVelocityTrackerStrategy(); - } - return NULL; -} - -void VelocityTracker::clear() { - mCurrentPointerIdBits.clear(); - mActivePointerId = -1; - - mStrategy->clear(); -} - -void VelocityTracker::clearPointers(BitSet32 idBits) { - BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value); - mCurrentPointerIdBits = remainingIdBits; - - if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) { - mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1; - } - - mStrategy->clearPointers(idBits); -} - -void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) { - while (idBits.count() > MAX_POINTERS) { - idBits.clearLastMarkedBit(); - } - - if ((mCurrentPointerIdBits.value & idBits.value) - && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) { -#if DEBUG_VELOCITY - ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.", - (eventTime - mLastEventTime) * 0.000001f); -#endif - // We have not received any movements for too long. Assume that all pointers - // have stopped. - mStrategy->clear(); - } - mLastEventTime = eventTime; - - mCurrentPointerIdBits = idBits; - if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) { - mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit(); - } - - mStrategy->addMovement(eventTime, idBits, positions); - -#if DEBUG_VELOCITY - ALOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x, activePointerId=%d", - eventTime, idBits.value, mActivePointerId); - for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) { - uint32_t id = iterBits.firstMarkedBit(); - uint32_t index = idBits.getIndexOfBit(id); - iterBits.clearBit(id); - Estimator estimator; - getEstimator(id, &estimator); - ALOGD(" %d: position (%0.3f, %0.3f), " - "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)", - id, positions[index].x, positions[index].y, - int(estimator.degree), - vectorToString(estimator.xCoeff, estimator.degree + 1).string(), - vectorToString(estimator.yCoeff, estimator.degree + 1).string(), - estimator.confidence); - } -#endif -} - -void VelocityTracker::addMovement(const MotionEvent* event) { - int32_t actionMasked = event->getActionMasked(); - - switch (actionMasked) { - case AMOTION_EVENT_ACTION_DOWN: - case AMOTION_EVENT_ACTION_HOVER_ENTER: - // Clear all pointers on down before adding the new movement. - clear(); - break; - case AMOTION_EVENT_ACTION_POINTER_DOWN: { - // Start a new movement trace for a pointer that just went down. - // We do this on down instead of on up because the client may want to query the - // final velocity for a pointer that just went up. - BitSet32 downIdBits; - downIdBits.markBit(event->getPointerId(event->getActionIndex())); - clearPointers(downIdBits); - break; - } - case AMOTION_EVENT_ACTION_MOVE: - case AMOTION_EVENT_ACTION_HOVER_MOVE: - break; - default: - // Ignore all other actions because they do not convey any new information about - // pointer movement. We also want to preserve the last known velocity of the pointers. - // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position - // of the pointers that went up. ACTION_POINTER_UP does include the new position of - // pointers that remained down but we will also receive an ACTION_MOVE with this - // information if any of them actually moved. Since we don't know how many pointers - // will be going up at once it makes sense to just wait for the following ACTION_MOVE - // before adding the movement. - return; - } - - size_t pointerCount = event->getPointerCount(); - if (pointerCount > MAX_POINTERS) { - pointerCount = MAX_POINTERS; - } - - BitSet32 idBits; - for (size_t i = 0; i < pointerCount; i++) { - idBits.markBit(event->getPointerId(i)); - } - - uint32_t pointerIndex[MAX_POINTERS]; - for (size_t i = 0; i < pointerCount; i++) { - pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i)); - } - - nsecs_t eventTime; - Position positions[pointerCount]; - - size_t historySize = event->getHistorySize(); - for (size_t h = 0; h < historySize; h++) { - eventTime = event->getHistoricalEventTime(h); - for (size_t i = 0; i < pointerCount; i++) { - uint32_t index = pointerIndex[i]; - positions[index].x = event->getHistoricalX(i, h); - positions[index].y = event->getHistoricalY(i, h); - } - addMovement(eventTime, idBits, positions); - } - - eventTime = event->getEventTime(); - for (size_t i = 0; i < pointerCount; i++) { - uint32_t index = pointerIndex[i]; - positions[index].x = event->getX(i); - positions[index].y = event->getY(i); - } - addMovement(eventTime, idBits, positions); -} - -bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const { - Estimator estimator; - if (getEstimator(id, &estimator) && estimator.degree >= 1) { - *outVx = estimator.xCoeff[1]; - *outVy = estimator.yCoeff[1]; - return true; - } - *outVx = 0; - *outVy = 0; - return false; -} - -bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const { - return mStrategy->getEstimator(id, outEstimator); -} - - -// --- LeastSquaresVelocityTrackerStrategy --- - -const nsecs_t LeastSquaresVelocityTrackerStrategy::HORIZON; -const uint32_t LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE; - -LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy( - uint32_t degree, Weighting weighting) : - mDegree(degree), mWeighting(weighting) { - clear(); -} - -LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() { -} - -void LeastSquaresVelocityTrackerStrategy::clear() { - mIndex = 0; - mMovements[0].idBits.clear(); -} - -void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { - BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); - mMovements[mIndex].idBits = remainingIdBits; -} - -void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, - const VelocityTracker::Position* positions) { - if (++mIndex == HISTORY_SIZE) { - mIndex = 0; - } - - Movement& movement = mMovements[mIndex]; - movement.eventTime = eventTime; - movement.idBits = idBits; - uint32_t count = idBits.count(); - for (uint32_t i = 0; i < count; i++) { - movement.positions[i] = positions[i]; - } -} - -/** - * Solves a linear least squares problem to obtain a N degree polynomial that fits - * the specified input data as nearly as possible. - * - * Returns true if a solution is found, false otherwise. - * - * The input consists of two vectors of data points X and Y with indices 0..m-1 - * along with a weight vector W of the same size. - * - * The output is a vector B with indices 0..n that describes a polynomial - * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i] - * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized. - * - * Accordingly, the weight vector W should be initialized by the caller with the - * reciprocal square root of the variance of the error in each input data point. - * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]). - * The weights express the relative importance of each data point. If the weights are - * all 1, then the data points are considered to be of equal importance when fitting - * the polynomial. It is a good idea to choose weights that diminish the importance - * of data points that may have higher than usual error margins. - * - * Errors among data points are assumed to be independent. W is represented here - * as a vector although in the literature it is typically taken to be a diagonal matrix. - * - * That is to say, the function that generated the input data can be approximated - * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n. - * - * The coefficient of determination (R^2) is also returned to describe the goodness - * of fit of the model for the given data. It is a value between 0 and 1, where 1 - * indicates perfect correspondence. - * - * This function first expands the X vector to a m by n matrix A such that - * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then - * multiplies it by w[i]./ - * - * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q - * and an m by n upper triangular matrix R. Because R is upper triangular (lower - * part is all zeroes), we can simplify the decomposition into an m by n matrix - * Q1 and a n by n matrix R1 such that A = Q1 R1. - * - * Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y) - * to find B. - * - * For efficiency, we lay out A and Q column-wise in memory because we frequently - * operate on the column vectors. Conversely, we lay out R row-wise. - * - * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares - * http://en.wikipedia.org/wiki/Gram-Schmidt - */ -static bool solveLeastSquares(const float* x, const float* y, - const float* w, uint32_t m, uint32_t n, float* outB, float* outDet) { -#if DEBUG_STRATEGY - ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), - vectorToString(x, m).string(), vectorToString(y, m).string(), - vectorToString(w, m).string()); -#endif - - // Expand the X vector to a matrix A, pre-multiplied by the weights. - float a[n][m]; // column-major order - for (uint32_t h = 0; h < m; h++) { - a[0][h] = w[h]; - for (uint32_t i = 1; i < n; i++) { - a[i][h] = a[i - 1][h] * x[h]; - } - } -#if DEBUG_STRATEGY - ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).string()); -#endif - - // Apply the Gram-Schmidt process to A to obtain its QR decomposition. - float q[n][m]; // orthonormal basis, column-major order - float r[n][n]; // upper triangular matrix, row-major order - for (uint32_t j = 0; j < n; j++) { - for (uint32_t h = 0; h < m; h++) { - q[j][h] = a[j][h]; - } - for (uint32_t i = 0; i < j; i++) { - float dot = vectorDot(&q[j][0], &q[i][0], m); - for (uint32_t h = 0; h < m; h++) { - q[j][h] -= dot * q[i][h]; - } - } - - float norm = vectorNorm(&q[j][0], m); - if (norm < 0.000001f) { - // vectors are linearly dependent or zero so no solution -#if DEBUG_STRATEGY - ALOGD(" - no solution, norm=%f", norm); -#endif - return false; - } - - float invNorm = 1.0f / norm; - for (uint32_t h = 0; h < m; h++) { - q[j][h] *= invNorm; - } - for (uint32_t i = 0; i < n; i++) { - r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m); - } - } -#if DEBUG_STRATEGY - ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).string()); - ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).string()); - - // calculate QR, if we factored A correctly then QR should equal A - float qr[n][m]; - for (uint32_t h = 0; h < m; h++) { - for (uint32_t i = 0; i < n; i++) { - qr[i][h] = 0; - for (uint32_t j = 0; j < n; j++) { - qr[i][h] += q[j][h] * r[j][i]; - } - } - } - ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).string()); -#endif - - // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. - // We just work from bottom-right to top-left calculating B's coefficients. - float wy[m]; - for (uint32_t h = 0; h < m; h++) { - wy[h] = y[h] * w[h]; - } - for (uint32_t i = n; i-- != 0; ) { - outB[i] = vectorDot(&q[i][0], wy, m); - for (uint32_t j = n - 1; j > i; j--) { - outB[i] -= r[i][j] * outB[j]; - } - outB[i] /= r[i][i]; - } -#if DEBUG_STRATEGY - ALOGD(" - b=%s", vectorToString(outB, n).string()); -#endif - - // Calculate the coefficient of determination as 1 - (SSerr / SStot) where - // SSerr is the residual sum of squares (variance of the error), - // and SStot is the total sum of squares (variance of the data) where each - // has been weighted. - float ymean = 0; - for (uint32_t h = 0; h < m; h++) { - ymean += y[h]; - } - ymean /= m; - - float sserr = 0; - float sstot = 0; - for (uint32_t h = 0; h < m; h++) { - float err = y[h] - outB[0]; - float term = 1; - for (uint32_t i = 1; i < n; i++) { - term *= x[h]; - err -= term * outB[i]; - } - sserr += w[h] * w[h] * err * err; - float var = y[h] - ymean; - sstot += w[h] * w[h] * var * var; - } - *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1; -#if DEBUG_STRATEGY - ALOGD(" - sserr=%f", sserr); - ALOGD(" - sstot=%f", sstot); - ALOGD(" - det=%f", *outDet); -#endif - return true; -} - -bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, - VelocityTracker::Estimator* outEstimator) const { - outEstimator->clear(); - - // Iterate over movement samples in reverse time order and collect samples. - float x[HISTORY_SIZE]; - float y[HISTORY_SIZE]; - float w[HISTORY_SIZE]; - float time[HISTORY_SIZE]; - uint32_t m = 0; - uint32_t index = mIndex; - const Movement& newestMovement = mMovements[mIndex]; - do { - const Movement& movement = mMovements[index]; - if (!movement.idBits.hasBit(id)) { - break; - } - - nsecs_t age = newestMovement.eventTime - movement.eventTime; - if (age > HORIZON) { - break; - } - - const VelocityTracker::Position& position = movement.getPosition(id); - x[m] = position.x; - y[m] = position.y; - w[m] = chooseWeight(index); - time[m] = -age * 0.000000001f; - index = (index == 0 ? HISTORY_SIZE : index) - 1; - } while (++m < HISTORY_SIZE); - - if (m == 0) { - return false; // no data - } - - // Calculate a least squares polynomial fit. - uint32_t degree = mDegree; - if (degree > m - 1) { - degree = m - 1; - } - if (degree >= 1) { - float xdet, ydet; - uint32_t n = degree + 1; - if (solveLeastSquares(time, x, w, m, n, outEstimator->xCoeff, &xdet) - && solveLeastSquares(time, y, w, m, n, outEstimator->yCoeff, &ydet)) { - outEstimator->time = newestMovement.eventTime; - outEstimator->degree = degree; - outEstimator->confidence = xdet * ydet; -#if DEBUG_STRATEGY - ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", - int(outEstimator->degree), - vectorToString(outEstimator->xCoeff, n).string(), - vectorToString(outEstimator->yCoeff, n).string(), - outEstimator->confidence); -#endif - return true; - } - } - - // No velocity data available for this pointer, but we do have its current position. - outEstimator->xCoeff[0] = x[0]; - outEstimator->yCoeff[0] = y[0]; - outEstimator->time = newestMovement.eventTime; - outEstimator->degree = 0; - outEstimator->confidence = 1; - return true; -} - -float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const { - switch (mWeighting) { - case WEIGHTING_DELTA: { - // Weight points based on how much time elapsed between them and the next - // point so that points that "cover" a shorter time span are weighed less. - // delta 0ms: 0.5 - // delta 10ms: 1.0 - if (index == mIndex) { - return 1.0f; - } - uint32_t nextIndex = (index + 1) % HISTORY_SIZE; - float deltaMillis = (mMovements[nextIndex].eventTime- mMovements[index].eventTime) - * 0.000001f; - if (deltaMillis < 0) { - return 0.5f; - } - if (deltaMillis < 10) { - return 0.5f + deltaMillis * 0.05; - } - return 1.0f; - } - - case WEIGHTING_CENTRAL: { - // Weight points based on their age, weighing very recent and very old points less. - // age 0ms: 0.5 - // age 10ms: 1.0 - // age 50ms: 1.0 - // age 60ms: 0.5 - float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime) - * 0.000001f; - if (ageMillis < 0) { - return 0.5f; - } - if (ageMillis < 10) { - return 0.5f + ageMillis * 0.05; - } - if (ageMillis < 50) { - return 1.0f; - } - if (ageMillis < 60) { - return 0.5f + (60 - ageMillis) * 0.05; - } - return 0.5f; - } - - case WEIGHTING_RECENT: { - // Weight points based on their age, weighing older points less. - // age 0ms: 1.0 - // age 50ms: 1.0 - // age 100ms: 0.5 - float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime) - * 0.000001f; - if (ageMillis < 50) { - return 1.0f; - } - if (ageMillis < 100) { - return 0.5f + (100 - ageMillis) * 0.01f; - } - return 0.5f; - } - - case WEIGHTING_NONE: - default: - return 1.0f; - } -} - - -// --- IntegratingVelocityTrackerStrategy --- - -IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) : - mDegree(degree) { -} - -IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() { -} - -void IntegratingVelocityTrackerStrategy::clear() { - mPointerIdBits.clear(); -} - -void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { - mPointerIdBits.value &= ~idBits.value; -} - -void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, - const VelocityTracker::Position* positions) { - uint32_t index = 0; - for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) { - uint32_t id = iterIdBits.clearFirstMarkedBit(); - State& state = mPointerState[id]; - const VelocityTracker::Position& position = positions[index++]; - if (mPointerIdBits.hasBit(id)) { - updateState(state, eventTime, position.x, position.y); - } else { - initState(state, eventTime, position.x, position.y); - } - } - - mPointerIdBits = idBits; -} - -bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id, - VelocityTracker::Estimator* outEstimator) const { - outEstimator->clear(); - - if (mPointerIdBits.hasBit(id)) { - const State& state = mPointerState[id]; - populateEstimator(state, outEstimator); - return true; - } - - return false; -} - -void IntegratingVelocityTrackerStrategy::initState(State& state, - nsecs_t eventTime, float xpos, float ypos) const { - state.updateTime = eventTime; - state.degree = 0; - - state.xpos = xpos; - state.xvel = 0; - state.xaccel = 0; - state.ypos = ypos; - state.yvel = 0; - state.yaccel = 0; -} - -void IntegratingVelocityTrackerStrategy::updateState(State& state, - nsecs_t eventTime, float xpos, float ypos) const { - const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS; - const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds - - if (eventTime <= state.updateTime + MIN_TIME_DELTA) { - return; - } - - float dt = (eventTime - state.updateTime) * 0.000000001f; - state.updateTime = eventTime; - - float xvel = (xpos - state.xpos) / dt; - float yvel = (ypos - state.ypos) / dt; - if (state.degree == 0) { - state.xvel = xvel; - state.yvel = yvel; - state.degree = 1; - } else { - float alpha = dt / (FILTER_TIME_CONSTANT + dt); - if (mDegree == 1) { - state.xvel += (xvel - state.xvel) * alpha; - state.yvel += (yvel - state.yvel) * alpha; - } else { - float xaccel = (xvel - state.xvel) / dt; - float yaccel = (yvel - state.yvel) / dt; - if (state.degree == 1) { - state.xaccel = xaccel; - state.yaccel = yaccel; - state.degree = 2; - } else { - state.xaccel += (xaccel - state.xaccel) * alpha; - state.yaccel += (yaccel - state.yaccel) * alpha; - } - state.xvel += (state.xaccel * dt) * alpha; - state.yvel += (state.yaccel * dt) * alpha; - } - } - state.xpos = xpos; - state.ypos = ypos; -} - -void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, - VelocityTracker::Estimator* outEstimator) const { - outEstimator->time = state.updateTime; - outEstimator->confidence = 1.0f; - outEstimator->degree = state.degree; - outEstimator->xCoeff[0] = state.xpos; - outEstimator->xCoeff[1] = state.xvel; - outEstimator->xCoeff[2] = state.xaccel / 2; - outEstimator->yCoeff[0] = state.ypos; - outEstimator->yCoeff[1] = state.yvel; - outEstimator->yCoeff[2] = state.yaccel / 2; -} - - -// --- LegacyVelocityTrackerStrategy --- - -const nsecs_t LegacyVelocityTrackerStrategy::HORIZON; -const uint32_t LegacyVelocityTrackerStrategy::HISTORY_SIZE; -const nsecs_t LegacyVelocityTrackerStrategy::MIN_DURATION; - -LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() { - clear(); -} - -LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() { -} - -void LegacyVelocityTrackerStrategy::clear() { - mIndex = 0; - mMovements[0].idBits.clear(); -} - -void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { - BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); - mMovements[mIndex].idBits = remainingIdBits; -} - -void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, - const VelocityTracker::Position* positions) { - if (++mIndex == HISTORY_SIZE) { - mIndex = 0; - } - - Movement& movement = mMovements[mIndex]; - movement.eventTime = eventTime; - movement.idBits = idBits; - uint32_t count = idBits.count(); - for (uint32_t i = 0; i < count; i++) { - movement.positions[i] = positions[i]; - } -} - -bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, - VelocityTracker::Estimator* outEstimator) const { - outEstimator->clear(); - - const Movement& newestMovement = mMovements[mIndex]; - if (!newestMovement.idBits.hasBit(id)) { - return false; // no data - } - - // Find the oldest sample that contains the pointer and that is not older than HORIZON. - nsecs_t minTime = newestMovement.eventTime - HORIZON; - uint32_t oldestIndex = mIndex; - uint32_t numTouches = 1; - do { - uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1; - const Movement& nextOldestMovement = mMovements[nextOldestIndex]; - if (!nextOldestMovement.idBits.hasBit(id) - || nextOldestMovement.eventTime < minTime) { - break; - } - oldestIndex = nextOldestIndex; - } while (++numTouches < HISTORY_SIZE); - - // Calculate an exponentially weighted moving average of the velocity estimate - // at different points in time measured relative to the oldest sample. - // This is essentially an IIR filter. Newer samples are weighted more heavily - // than older samples. Samples at equal time points are weighted more or less - // equally. - // - // One tricky problem is that the sample data may be poorly conditioned. - // Sometimes samples arrive very close together in time which can cause us to - // overestimate the velocity at that time point. Most samples might be measured - // 16ms apart but some consecutive samples could be only 0.5sm apart because - // the hardware or driver reports them irregularly or in bursts. - float accumVx = 0; - float accumVy = 0; - uint32_t index = oldestIndex; - uint32_t samplesUsed = 0; - const Movement& oldestMovement = mMovements[oldestIndex]; - const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id); - nsecs_t lastDuration = 0; - - while (numTouches-- > 1) { - if (++index == HISTORY_SIZE) { - index = 0; - } - const Movement& movement = mMovements[index]; - nsecs_t duration = movement.eventTime - oldestMovement.eventTime; - - // If the duration between samples is small, we may significantly overestimate - // the velocity. Consequently, we impose a minimum duration constraint on the - // samples that we include in the calculation. - if (duration >= MIN_DURATION) { - const VelocityTracker::Position& position = movement.getPosition(id); - float scale = 1000000000.0f / duration; // one over time delta in seconds - float vx = (position.x - oldestPosition.x) * scale; - float vy = (position.y - oldestPosition.y) * scale; - accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration); - accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration); - lastDuration = duration; - samplesUsed += 1; - } - } - - // Report velocity. - const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id); - outEstimator->time = newestMovement.eventTime; - outEstimator->confidence = 1; - outEstimator->xCoeff[0] = newestPosition.x; - outEstimator->yCoeff[0] = newestPosition.y; - if (samplesUsed) { - outEstimator->xCoeff[1] = accumVx; - outEstimator->yCoeff[1] = accumVy; - outEstimator->degree = 1; - } else { - outEstimator->degree = 0; - } - return true; -} - -} // namespace android diff --git a/libs/androidfw/VirtualKeyMap.cpp b/libs/androidfw/VirtualKeyMap.cpp deleted file mode 100644 index 2ba1673..0000000 --- a/libs/androidfw/VirtualKeyMap.cpp +++ /dev/null @@ -1,171 +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. - */ - -#define LOG_TAG "VirtualKeyMap" - -#include <stdlib.h> -#include <string.h> -#include <androidfw/VirtualKeyMap.h> -#include <utils/Log.h> -#include <utils/Errors.h> -#include <utils/Tokenizer.h> -#include <utils/Timers.h> - -// Enables debug output for the parser. -#define DEBUG_PARSER 0 - -// Enables debug output for parser performance. -#define DEBUG_PARSER_PERFORMANCE 0 - - -namespace android { - -static const char* WHITESPACE = " \t\r"; -static const char* WHITESPACE_OR_FIELD_DELIMITER = " \t\r:"; - - -// --- VirtualKeyMap --- - -VirtualKeyMap::VirtualKeyMap() { -} - -VirtualKeyMap::~VirtualKeyMap() { -} - -status_t VirtualKeyMap::load(const String8& filename, VirtualKeyMap** outMap) { - *outMap = NULL; - - Tokenizer* tokenizer; - status_t status = Tokenizer::open(filename, &tokenizer); - if (status) { - ALOGE("Error %d opening virtual key map file %s.", status, filename.string()); - } else { - VirtualKeyMap* map = new VirtualKeyMap(); - if (!map) { - ALOGE("Error allocating virtual key map."); - status = NO_MEMORY; - } else { -#if DEBUG_PARSER_PERFORMANCE - nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); -#endif - Parser parser(map, tokenizer); - status = parser.parse(); -#if DEBUG_PARSER_PERFORMANCE - nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; - ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.", - tokenizer->getFilename().string(), tokenizer->getLineNumber(), - elapsedTime / 1000000.0); -#endif - if (status) { - delete map; - } else { - *outMap = map; - } - } - delete tokenizer; - } - return status; -} - - -// --- VirtualKeyMap::Parser --- - -VirtualKeyMap::Parser::Parser(VirtualKeyMap* map, Tokenizer* tokenizer) : - mMap(map), mTokenizer(tokenizer) { -} - -VirtualKeyMap::Parser::~Parser() { -} - -status_t VirtualKeyMap::Parser::parse() { - while (!mTokenizer->isEof()) { -#if DEBUG_PARSER - ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(), - mTokenizer->peekRemainderOfLine().string()); -#endif - - mTokenizer->skipDelimiters(WHITESPACE); - - if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') { - // Multiple keys can appear on one line or they can be broken up across multiple lines. - do { - String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER); - if (token != "0x01") { - ALOGE("%s: Unknown virtual key type, expected 0x01.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - - VirtualKeyDefinition defn; - bool success = parseNextIntField(&defn.scanCode) - && parseNextIntField(&defn.centerX) - && parseNextIntField(&defn.centerY) - && parseNextIntField(&defn.width) - && parseNextIntField(&defn.height); - if (!success) { - ALOGE("%s: Expected 5 colon-delimited integers in virtual key definition.", - mTokenizer->getLocation().string()); - return BAD_VALUE; - } - -#if DEBUG_PARSER - ALOGD("Parsed virtual key: scanCode=%d, centerX=%d, centerY=%d, " - "width=%d, height=%d", - defn.scanCode, defn.centerX, defn.centerY, defn.width, defn.height); -#endif - mMap->mVirtualKeys.push(defn); - } while (consumeFieldDelimiterAndSkipWhitespace()); - - if (!mTokenizer->isEol()) { - ALOGE("%s: Expected end of line, got '%s'.", - mTokenizer->getLocation().string(), - mTokenizer->peekRemainderOfLine().string()); - return BAD_VALUE; - } - } - - mTokenizer->nextLine(); - } - - return NO_ERROR; -} - -bool VirtualKeyMap::Parser::consumeFieldDelimiterAndSkipWhitespace() { - mTokenizer->skipDelimiters(WHITESPACE); - if (mTokenizer->peekChar() == ':') { - mTokenizer->nextChar(); - mTokenizer->skipDelimiters(WHITESPACE); - return true; - } - return false; -} - -bool VirtualKeyMap::Parser::parseNextIntField(int32_t* outValue) { - if (!consumeFieldDelimiterAndSkipWhitespace()) { - return false; - } - - String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER); - char* end; - *outValue = strtol(token.string(), &end, 0); - if (token.isEmpty() || *end != '\0') { - ALOGE("Expected an integer, got '%s'.", token.string()); - return false; - } - return true; -} - -} // namespace android diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp new file mode 100644 index 0000000..ec5f95c --- /dev/null +++ b/libs/androidfw/ZipFileRO.cpp @@ -0,0 +1,995 @@ +/* + * Copyright (C) 2007 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. + */ + +// +// Read-only access to Zip archives, with minimal heap allocation. +// +#define LOG_TAG "zipro" +//#define LOG_NDEBUG 0 +#include <androidfw/ZipFileRO.h> +#include <utils/Log.h> +#include <utils/Compat.h> +#include <utils/misc.h> +#include <utils/threads.h> + +#include <zlib.h> + +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <assert.h> +#include <unistd.h> + +/* + * We must open binary files using open(path, ... | O_BINARY) under Windows. + * Otherwise strange read errors will happen. + */ +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +using namespace android; + +/* + * Zip file constants. + */ +#define kEOCDSignature 0x06054b50 +#define kEOCDLen 22 +#define kEOCDDiskNumber 4 // number of the current disk +#define kEOCDDiskNumberForCD 6 // disk number with the Central Directory +#define kEOCDNumEntries 8 // offset to #of entries in file +#define kEOCDTotalNumEntries 10 // offset to total #of entries in spanned archives +#define kEOCDSize 12 // size of the central directory +#define kEOCDFileOffset 16 // offset to central directory +#define kEOCDCommentSize 20 // offset to the length of the file comment + +#define kMaxCommentLen 65535 // longest possible in ushort +#define kMaxEOCDSearch (kMaxCommentLen + kEOCDLen) + +#define kLFHSignature 0x04034b50 +#define kLFHLen 30 // excluding variable-len fields +#define kLFHGPBFlags 6 // offset to GPB flags +#define kLFHNameLen 26 // offset to filename length +#define kLFHExtraLen 28 // offset to extra length + +#define kCDESignature 0x02014b50 +#define kCDELen 46 // excluding variable-len fields +#define kCDEGPBFlags 8 // offset to GPB flags +#define kCDEMethod 10 // offset to compression method +#define kCDEModWhen 12 // offset to modification timestamp +#define kCDECRC 16 // offset to entry CRC +#define kCDECompLen 20 // offset to compressed length +#define kCDEUncompLen 24 // offset to uncompressed length +#define kCDENameLen 28 // offset to filename length +#define kCDEExtraLen 30 // offset to extra length +#define kCDECommentLen 32 // offset to comment length +#define kCDELocalOffset 42 // offset to local hdr + +/* General Purpose Bit Flag */ +#define kGPFEncryptedFlag (1 << 0) +#define kGPFUnsupportedMask (kGPFEncryptedFlag) + +/* + * The values we return for ZipEntryRO use 0 as an invalid value, so we + * want to adjust the hash table index by a fixed amount. Using a large + * value helps insure that people don't mix & match arguments, e.g. to + * findEntryByIndex(). + */ +#define kZipEntryAdj 10000 + +ZipFileRO::~ZipFileRO() { + free(mHashTable); + if (mDirectoryMap) + mDirectoryMap->release(); + if (mFd >= 0) + TEMP_FAILURE_RETRY(close(mFd)); + if (mFileName) + free(mFileName); +} + +/* + * Convert a ZipEntryRO to a hash table index, verifying that it's in a + * valid range. + */ +int ZipFileRO::entryToIndex(const ZipEntryRO entry) const +{ + long ent = ((intptr_t) entry) - kZipEntryAdj; + if (ent < 0 || ent >= mHashTableSize || mHashTable[ent].name == NULL) { + ALOGW("Invalid ZipEntryRO %p (%ld)\n", entry, ent); + return -1; + } + return ent; +} + + +/* + * Open the specified file read-only. We memory-map the entire thing and + * close the file before returning. + */ +status_t ZipFileRO::open(const char* zipFileName) +{ + int fd = -1; + + assert(mDirectoryMap == NULL); + + /* + * Open and map the specified file. + */ + fd = TEMP_FAILURE_RETRY(::open(zipFileName, O_RDONLY | O_BINARY)); + if (fd < 0) { + ALOGW("Unable to open zip '%s': %s\n", zipFileName, strerror(errno)); + return NAME_NOT_FOUND; + } + + mFileLength = lseek64(fd, 0, SEEK_END); + if (mFileLength < kEOCDLen) { + TEMP_FAILURE_RETRY(close(fd)); + return UNKNOWN_ERROR; + } + + if (mFileName != NULL) { + free(mFileName); + } + mFileName = strdup(zipFileName); + + mFd = fd; + + /* + * Find the Central Directory and store its size and number of entries. + */ + if (!mapCentralDirectory()) { + goto bail; + } + + /* + * Verify Central Directory and create data structures for fast access. + */ + if (!parseZipArchive()) { + goto bail; + } + + return OK; + +bail: + free(mFileName); + mFileName = NULL; + TEMP_FAILURE_RETRY(close(fd)); + return UNKNOWN_ERROR; +} + +/* + * Parse the Zip archive, verifying its contents and initializing internal + * data structures. + */ +bool ZipFileRO::mapCentralDirectory(void) +{ + ssize_t readAmount = kMaxEOCDSearch; + if (readAmount > (ssize_t) mFileLength) + readAmount = mFileLength; + + if (readAmount < kEOCDSize) { + ALOGW("File too short to be a zip file"); + return false; + } + + unsigned char* scanBuf = (unsigned char*) malloc(readAmount); + if (scanBuf == NULL) { + ALOGW("couldn't allocate scanBuf: %s", strerror(errno)); + free(scanBuf); + return false; + } + + /* + * Make sure this is a Zip archive. + */ + if (lseek64(mFd, 0, SEEK_SET) != 0) { + ALOGW("seek to start failed: %s", strerror(errno)); + free(scanBuf); + return false; + } + + ssize_t actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, sizeof(int32_t))); + if (actual != (ssize_t) sizeof(int32_t)) { + ALOGI("couldn't read first signature from zip archive: %s", strerror(errno)); + free(scanBuf); + return false; + } + + unsigned int header = get4LE(scanBuf); + if (header != kLFHSignature) { + ALOGV("Not a Zip archive (found 0x%08x)\n", header); + free(scanBuf); + return false; + } + + /* + * Perform the traditional EOCD snipe hunt. + * + * We're searching for the End of Central Directory magic number, + * which appears at the start of the EOCD block. It's followed by + * 18 bytes of EOCD stuff and up to 64KB of archive comment. We + * need to read the last part of the file into a buffer, dig through + * it to find the magic number, parse some values out, and use those + * to determine the extent of the CD. + * + * We start by pulling in the last part of the file. + */ + off64_t searchStart = mFileLength - readAmount; + + if (lseek64(mFd, searchStart, SEEK_SET) != searchStart) { + ALOGW("seek %ld failed: %s\n", (long) searchStart, strerror(errno)); + free(scanBuf); + return false; + } + actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, readAmount)); + if (actual != (ssize_t) readAmount) { + ALOGW("Zip: read " ZD ", expected " ZD ". Failed: %s\n", + (ZD_TYPE) actual, (ZD_TYPE) readAmount, strerror(errno)); + free(scanBuf); + return false; + } + + /* + * Scan backward for the EOCD magic. In an archive without a trailing + * comment, we'll find it on the first try. (We may want to consider + * doing an initial minimal read; if we don't find it, retry with a + * second read as above.) + */ + int i; + for (i = readAmount - kEOCDLen; i >= 0; i--) { + if (scanBuf[i] == 0x50 && get4LE(&scanBuf[i]) == kEOCDSignature) { + ALOGV("+++ Found EOCD at buf+%d\n", i); + break; + } + } + if (i < 0) { + ALOGD("Zip: EOCD not found, %s is not zip\n", mFileName); + free(scanBuf); + return false; + } + + off64_t eocdOffset = searchStart + i; + const unsigned char* eocdPtr = scanBuf + i; + + assert(eocdOffset < mFileLength); + + /* + * Grab the CD offset and size, and the number of entries in the + * archive. After that, we can release our EOCD hunt buffer. + */ + unsigned int diskNumber = get2LE(eocdPtr + kEOCDDiskNumber); + unsigned int diskWithCentralDir = get2LE(eocdPtr + kEOCDDiskNumberForCD); + unsigned int numEntries = get2LE(eocdPtr + kEOCDNumEntries); + unsigned int totalNumEntries = get2LE(eocdPtr + kEOCDTotalNumEntries); + unsigned int centralDirSize = get4LE(eocdPtr + kEOCDSize); + unsigned int centralDirOffset = get4LE(eocdPtr + kEOCDFileOffset); + unsigned int commentSize = get2LE(eocdPtr + kEOCDCommentSize); + free(scanBuf); + + // Verify that they look reasonable. + if ((long long) centralDirOffset + (long long) centralDirSize > (long long) eocdOffset) { + ALOGW("bad offsets (dir %ld, size %u, eocd %ld)\n", + (long) centralDirOffset, centralDirSize, (long) eocdOffset); + return false; + } + if (numEntries == 0) { + ALOGW("empty archive?\n"); + return false; + } else if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) { + ALOGW("spanned archives not supported"); + return false; + } + + // Check to see if comment is a sane size + if ((commentSize > (mFileLength - kEOCDLen)) + || (eocdOffset > (mFileLength - kEOCDLen) - commentSize)) { + ALOGW("comment size runs off end of file"); + return false; + } + + ALOGV("+++ numEntries=%d dirSize=%d dirOffset=%d\n", + numEntries, centralDirSize, centralDirOffset); + + mDirectoryMap = new FileMap(); + if (mDirectoryMap == NULL) { + ALOGW("Unable to create directory map: %s", strerror(errno)); + return false; + } + + if (!mDirectoryMap->create(mFileName, mFd, centralDirOffset, centralDirSize, true)) { + ALOGW("Unable to map '%s' (" ZD " to " ZD "): %s\n", mFileName, + (ZD_TYPE) centralDirOffset, (ZD_TYPE) (centralDirOffset + centralDirSize), strerror(errno)); + return false; + } + + mNumEntries = numEntries; + mDirectoryOffset = centralDirOffset; + + return true; +} + + +/* + * Round up to the next highest power of 2. + * + * Found on http://graphics.stanford.edu/~seander/bithacks.html. + */ +static unsigned int roundUpPower2(unsigned int val) +{ + val--; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + val++; + + return val; +} + +bool ZipFileRO::parseZipArchive(void) +{ + bool result = false; + const unsigned char* cdPtr = (const unsigned char*) mDirectoryMap->getDataPtr(); + size_t cdLength = mDirectoryMap->getDataLength(); + int numEntries = mNumEntries; + + /* + * Create hash table. We have a minimum 75% load factor, possibly as + * low as 50% after we round off to a power of 2. + */ + mHashTableSize = roundUpPower2(1 + (numEntries * 4) / 3); + mHashTable = (HashEntry*) calloc(mHashTableSize, sizeof(HashEntry)); + + /* + * Walk through the central directory, adding entries to the hash + * table. + */ + const unsigned char* ptr = cdPtr; + for (int i = 0; i < numEntries; i++) { + if (get4LE(ptr) != kCDESignature) { + ALOGW("Missed a central dir sig (at %d)\n", i); + goto bail; + } + if (ptr + kCDELen > cdPtr + cdLength) { + ALOGW("Ran off the end (at %d)\n", i); + goto bail; + } + + long localHdrOffset = (long) get4LE(ptr + kCDELocalOffset); + if (localHdrOffset >= mDirectoryOffset) { + ALOGW("bad LFH offset %ld at entry %d\n", localHdrOffset, i); + goto bail; + } + + unsigned int gpbf = get2LE(ptr + kCDEGPBFlags); + if ((gpbf & kGPFUnsupportedMask) != 0) { + ALOGW("Invalid General Purpose Bit Flag: %d", gpbf); + goto bail; + } + + unsigned int nameLen = get2LE(ptr + kCDENameLen); + unsigned int extraLen = get2LE(ptr + kCDEExtraLen); + unsigned int commentLen = get2LE(ptr + kCDECommentLen); + + const char *name = (const char *) ptr + kCDELen; + + /* Check name for NULL characters */ + if (memchr(name, 0, nameLen) != NULL) { + ALOGW("Filename contains NUL byte"); + goto bail; + } + + /* add the CDE filename to the hash table */ + unsigned int hash = computeHash(name, nameLen); + addToHash(name, nameLen, hash); + + /* We don't care about the comment or extra data. */ + ptr += kCDELen + nameLen + extraLen + commentLen; + if ((size_t)(ptr - cdPtr) > cdLength) { + ALOGW("bad CD advance (%d vs " ZD ") at entry %d\n", + (int) (ptr - cdPtr), (ZD_TYPE) cdLength, i); + goto bail; + } + } + ALOGV("+++ zip good scan %d entries\n", numEntries); + result = true; + +bail: + return result; +} + +/* + * Simple string hash function for non-null-terminated strings. + */ +/*static*/ unsigned int ZipFileRO::computeHash(const char* str, int len) +{ + unsigned int hash = 0; + + while (len--) + hash = hash * 31 + *str++; + + return hash; +} + +/* + * Add a new entry to the hash table. + */ +void ZipFileRO::addToHash(const char* str, int strLen, unsigned int hash) +{ + int ent = hash & (mHashTableSize-1); + + /* + * We over-allocate the table, so we're guaranteed to find an empty slot. + */ + while (mHashTable[ent].name != NULL) + ent = (ent + 1) & (mHashTableSize-1); + + mHashTable[ent].name = str; + mHashTable[ent].nameLen = strLen; +} + +/* + * Find a matching entry. + * + * Returns NULL if not found. + */ +ZipEntryRO ZipFileRO::findEntryByName(const char* fileName) const +{ + /* + * If the ZipFileRO instance is not initialized, the entry number will + * end up being garbage since mHashTableSize is -1. + */ + if (mHashTableSize <= 0) { + return NULL; + } + + int nameLen = strlen(fileName); + unsigned int hash = computeHash(fileName, nameLen); + int ent = hash & (mHashTableSize-1); + + while (mHashTable[ent].name != NULL) { + if (mHashTable[ent].nameLen == nameLen && + memcmp(mHashTable[ent].name, fileName, nameLen) == 0) + { + /* match */ + return (ZipEntryRO)(long)(ent + kZipEntryAdj); + } + + ent = (ent + 1) & (mHashTableSize-1); + } + + return NULL; +} + +/* + * Find the Nth entry. + * + * This currently involves walking through the sparse hash table, counting + * non-empty entries. If we need to speed this up we can either allocate + * a parallel lookup table or (perhaps better) provide an iterator interface. + */ +ZipEntryRO ZipFileRO::findEntryByIndex(int idx) const +{ + if (idx < 0 || idx >= mNumEntries) { + ALOGW("Invalid index %d\n", idx); + return NULL; + } + + for (int ent = 0; ent < mHashTableSize; ent++) { + if (mHashTable[ent].name != NULL) { + if (idx-- == 0) + return (ZipEntryRO) (intptr_t)(ent + kZipEntryAdj); + } + } + + return NULL; +} + +/* + * Get the useful fields from the zip entry. + * + * Returns "false" if the offsets to the fields or the contents of the fields + * appear to be bogus. + */ +bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen, + size_t* pCompLen, off64_t* pOffset, long* pModWhen, long* pCrc32) const +{ + bool ret = false; + + const int ent = entryToIndex(entry); + if (ent < 0) { + ALOGW("cannot find entry"); + return false; + } + + HashEntry hashEntry = mHashTable[ent]; + + /* + * Recover the start of the central directory entry from the filename + * pointer. The filename is the first entry past the fixed-size data, + * so we can just subtract back from that. + */ + const unsigned char* ptr = (const unsigned char*) hashEntry.name; + off64_t cdOffset = mDirectoryOffset; + + ptr -= kCDELen; + + int method = get2LE(ptr + kCDEMethod); + if (pMethod != NULL) + *pMethod = method; + + if (pModWhen != NULL) + *pModWhen = get4LE(ptr + kCDEModWhen); + if (pCrc32 != NULL) + *pCrc32 = get4LE(ptr + kCDECRC); + + size_t compLen = get4LE(ptr + kCDECompLen); + if (pCompLen != NULL) + *pCompLen = compLen; + size_t uncompLen = get4LE(ptr + kCDEUncompLen); + if (pUncompLen != NULL) + *pUncompLen = uncompLen; + + /* + * If requested, determine the offset of the start of the data. All we + * have is the offset to the Local File Header, which is variable size, + * so we have to read the contents of the struct to figure out where + * the actual data starts. + * + * We also need to make sure that the lengths are not so large that + * somebody trying to map the compressed or uncompressed data runs + * off the end of the mapped region. + * + * Note we don't verify compLen/uncompLen if they don't request the + * dataOffset, because dataOffset is expensive to determine. However, + * if they don't have the file offset, they're not likely to be doing + * anything with the contents. + */ + if (pOffset != NULL) { + long localHdrOffset = get4LE(ptr + kCDELocalOffset); + if (localHdrOffset + kLFHLen >= cdOffset) { + ALOGE("ERROR: bad local hdr offset in zip\n"); + return false; + } + + unsigned char lfhBuf[kLFHLen]; + +#ifdef HAVE_PREAD + /* + * This file descriptor might be from zygote's preloaded assets, + * so we need to do an pread64() instead of a lseek64() + read() to + * guarantee atomicity across the processes with the shared file + * descriptors. + */ + ssize_t actual = + TEMP_FAILURE_RETRY(pread64(mFd, lfhBuf, sizeof(lfhBuf), localHdrOffset)); + + if (actual != sizeof(lfhBuf)) { + ALOGW("failed reading lfh from offset %ld\n", localHdrOffset); + return false; + } + + if (get4LE(lfhBuf) != kLFHSignature) { + ALOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; " + "got: data=0x%08lx\n", + localHdrOffset, kLFHSignature, get4LE(lfhBuf)); + return false; + } +#else /* HAVE_PREAD */ + /* + * For hosts don't have pread64() we cannot guarantee atomic reads from + * an offset in a file. Android should never run on those platforms. + * File descriptors inherited from a fork() share file offsets and + * there would be nothing to protect from two different processes + * calling lseek64() concurrently. + */ + + { + AutoMutex _l(mFdLock); + + if (lseek64(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) { + ALOGW("failed seeking to lfh at offset %ld\n", localHdrOffset); + return false; + } + + ssize_t actual = + TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf))); + if (actual != sizeof(lfhBuf)) { + ALOGW("failed reading lfh from offset %ld\n", localHdrOffset); + return false; + } + + if (get4LE(lfhBuf) != kLFHSignature) { + off64_t actualOffset = lseek64(mFd, 0, SEEK_CUR); + ALOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; " + "got: offset=" ZD " data=0x%08lx\n", + localHdrOffset, kLFHSignature, (ZD_TYPE) actualOffset, get4LE(lfhBuf)); + return false; + } + } +#endif /* HAVE_PREAD */ + + unsigned int gpbf = get2LE(lfhBuf + kLFHGPBFlags); + if ((gpbf & kGPFUnsupportedMask) != 0) { + ALOGW("Invalid General Purpose Bit Flag: %d", gpbf); + return false; + } + + off64_t dataOffset = localHdrOffset + kLFHLen + + get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen); + if (dataOffset >= cdOffset) { + ALOGW("bad data offset %ld in zip\n", (long) dataOffset); + return false; + } + + /* check lengths */ + if ((dataOffset >= cdOffset) || (compLen > (cdOffset - dataOffset))) { + ALOGW("bad compressed length in zip (%ld + " ZD " > %ld)\n", + (long) dataOffset, (ZD_TYPE) compLen, (long) cdOffset); + return false; + } + + if (method == kCompressStored && + ((dataOffset >= cdOffset) || + (uncompLen > (cdOffset - dataOffset)))) + { + ALOGE("ERROR: bad uncompressed length in zip (%ld + " ZD " > %ld)\n", + (long) dataOffset, (ZD_TYPE) uncompLen, (long) cdOffset); + return false; + } + + *pOffset = dataOffset; + } + + return true; +} + +/* + * Copy the entry's filename to the buffer. + */ +int ZipFileRO::getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen) + const +{ + int ent = entryToIndex(entry); + if (ent < 0) + return -1; + + int nameLen = mHashTable[ent].nameLen; + if (bufLen < nameLen+1) + return nameLen+1; + + memcpy(buffer, mHashTable[ent].name, nameLen); + buffer[nameLen] = '\0'; + return 0; +} + +/* + * Create a new FileMap object that spans the data in "entry". + */ +FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const +{ + /* + * TODO: the efficient way to do this is to modify FileMap to allow + * sub-regions of a file to be mapped. A reference-counting scheme + * can manage the base memory mapping. For now, we just create a brand + * new mapping off of the Zip archive file descriptor. + */ + + FileMap* newMap; + int method; + size_t uncompLen; + size_t compLen; + off64_t offset; + + if (!getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL)) { + return NULL; + } + + size_t actualLen; + if (method == kCompressStored) { + actualLen = uncompLen; + } else { + actualLen = compLen; + } + + newMap = new FileMap(); + if (!newMap->create(mFileName, mFd, offset, actualLen, true)) { + newMap->release(); + return NULL; + } + + return newMap; +} + +/* + * Uncompress an entry, in its entirety, into the provided output buffer. + * + * This doesn't verify the data's CRC, which might be useful for + * uncompressed data. The caller should be able to manage it. + */ +bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer) const +{ + const size_t kSequentialMin = 32768; + bool result = false; + int ent = entryToIndex(entry); + if (ent < 0) { + return false; + } + + int method; + size_t uncompLen, compLen; + off64_t offset; + const unsigned char* ptr; + FileMap *file; + + if (!getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL)) { + goto bail; + } + + file = createEntryFileMap(entry); + if (file == NULL) { + goto bail; + } + + ptr = (const unsigned char*) file->getDataPtr(); + + /* + * Experiment with madvise hint. When we want to uncompress a file, + * we pull some stuff out of the central dir entry and then hit a + * bunch of compressed or uncompressed data sequentially. The CDE + * visit will cause a limited amount of read-ahead because it's at + * the end of the file. We could end up doing lots of extra disk + * access if the file we're prying open is small. Bottom line is we + * probably don't want to turn MADV_SEQUENTIAL on and leave it on. + * + * So, if the compressed size of the file is above a certain minimum + * size, temporarily boost the read-ahead in the hope that the extra + * pair of system calls are negated by a reduction in page faults. + */ + if (compLen > kSequentialMin) + file->advise(FileMap::SEQUENTIAL); + + if (method == kCompressStored) { + memcpy(buffer, ptr, uncompLen); + } else { + if (!inflateBuffer(buffer, ptr, uncompLen, compLen)) + goto unmap; + } + + if (compLen > kSequentialMin) + file->advise(FileMap::NORMAL); + + result = true; + +unmap: + file->release(); +bail: + return result; +} + +/* + * Uncompress an entry, in its entirety, to an open file descriptor. + * + * This doesn't verify the data's CRC, but probably should. + */ +bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const +{ + bool result = false; + int ent = entryToIndex(entry); + if (ent < 0) { + return false; + } + + int method; + size_t uncompLen, compLen; + off64_t offset; + const unsigned char* ptr; + FileMap *file; + + if (!getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL)) { + goto bail; + } + + file = createEntryFileMap(entry); + if (file == NULL) { + goto bail; + } + + ptr = (const unsigned char*) file->getDataPtr(); + + if (method == kCompressStored) { + ssize_t actual = TEMP_FAILURE_RETRY(write(fd, ptr, uncompLen)); + if (actual < 0) { + ALOGE("Write failed: %s\n", strerror(errno)); + goto unmap; + } else if ((size_t) actual != uncompLen) { + ALOGE("Partial write during uncompress (" ZD " of " ZD ")\n", + (ZD_TYPE) actual, (ZD_TYPE) uncompLen); + goto unmap; + } else { + ALOGI("+++ successful write\n"); + } + } else { + if (!inflateBuffer(fd, ptr, uncompLen, compLen)) { + goto unmap; + } + } + + result = true; + +unmap: + file->release(); +bail: + return result; +} + +/* + * Uncompress "deflate" data from one buffer to another. + */ +/*static*/ bool ZipFileRO::inflateBuffer(void* outBuf, const void* inBuf, + size_t uncompLen, size_t compLen) +{ + bool result = false; + z_stream zstream; + int zerr; + + /* + * Initialize the zlib stream struct. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = (Bytef*)inBuf; + zstream.avail_in = compLen; + zstream.next_out = (Bytef*) outBuf; + zstream.avail_out = uncompLen; + zstream.data_type = Z_UNKNOWN; + + /* + * Use the undocumented "negative window bits" feature to tell zlib + * that there's no zlib header waiting for it. + */ + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + if (zerr == Z_VERSION_ERROR) { + ALOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + /* + * Expand data. + */ + zerr = inflate(&zstream, Z_FINISH); + if (zerr != Z_STREAM_END) { + ALOGW("Zip inflate failed, zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n", + zerr, zstream.next_in, zstream.avail_in, + zstream.next_out, zstream.avail_out); + goto z_bail; + } + + /* paranoia */ + if (zstream.total_out != uncompLen) { + ALOGW("Size mismatch on inflated file (%ld vs " ZD ")\n", + zstream.total_out, (ZD_TYPE) uncompLen); + goto z_bail; + } + + result = true; + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + return result; +} + +/* + * Uncompress "deflate" data from one buffer to an open file descriptor. + */ +/*static*/ bool ZipFileRO::inflateBuffer(int fd, const void* inBuf, + size_t uncompLen, size_t compLen) +{ + bool result = false; + const size_t kWriteBufSize = 32768; + unsigned char writeBuf[kWriteBufSize]; + z_stream zstream; + int zerr; + + /* + * Initialize the zlib stream struct. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = (Bytef*)inBuf; + zstream.avail_in = compLen; + zstream.next_out = (Bytef*) writeBuf; + zstream.avail_out = sizeof(writeBuf); + zstream.data_type = Z_UNKNOWN; + + /* + * Use the undocumented "negative window bits" feature to tell zlib + * that there's no zlib header waiting for it. + */ + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + if (zerr == Z_VERSION_ERROR) { + ALOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + /* + * Loop while we have more to do. + */ + do { + /* + * Expand data. + */ + zerr = inflate(&zstream, Z_NO_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + ALOGW("zlib inflate: zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n", + zerr, zstream.next_in, zstream.avail_in, + zstream.next_out, zstream.avail_out); + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != sizeof(writeBuf))) + { + long writeSize = zstream.next_out - writeBuf; + int cc = TEMP_FAILURE_RETRY(write(fd, writeBuf, writeSize)); + if (cc < 0) { + ALOGW("write failed in inflate: %s", strerror(errno)); + goto z_bail; + } else if (cc != (int) writeSize) { + ALOGW("write failed in inflate (%d vs %ld)", cc, writeSize); + goto z_bail; + } + + zstream.next_out = writeBuf; + zstream.avail_out = sizeof(writeBuf); + } + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + /* paranoia */ + if (zstream.total_out != uncompLen) { + ALOGW("Size mismatch on inflated file (%ld vs " ZD ")\n", + zstream.total_out, (ZD_TYPE) uncompLen); + goto z_bail; + } + + result = true; + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + return result; +} diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp new file mode 100644 index 0000000..997eb7d --- /dev/null +++ b/libs/androidfw/ZipUtils.cpp @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2007 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. + */ + +// +// Misc zip/gzip utility functions. +// + +#define LOG_TAG "ziputil" + +#include <androidfw/ZipUtils.h> +#include <androidfw/ZipFileRO.h> +#include <utils/Log.h> +#include <utils/Compat.h> + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <zlib.h> + +using namespace android; + +/* + * Utility function that expands zip/gzip "deflate" compressed data + * into a buffer. + * + * "fd" is an open file positioned at the start of the "deflate" data + * "buf" must hold at least "uncompressedLen" bytes. + */ +/*static*/ bool ZipUtils::inflateToBuffer(int fd, void* buf, + long uncompressedLen, long compressedLen) +{ + bool result = false; + const unsigned long kReadBufSize = 32768; + unsigned char* readBuf = NULL; + z_stream zstream; + int zerr; + unsigned long compRemaining; + + assert(uncompressedLen >= 0); + assert(compressedLen >= 0); + + readBuf = new unsigned char[kReadBufSize]; + if (readBuf == NULL) + goto bail; + compRemaining = compressedLen; + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = (Bytef*) buf; + zstream.avail_out = uncompressedLen; + zstream.data_type = Z_UNKNOWN; + + /* + * Use the undocumented "negative window bits" feature to tell zlib + * that there's no zlib header waiting for it. + */ + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + if (zerr == Z_VERSION_ERROR) { + ALOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + /* + * Loop while we have data. + */ + do { + unsigned long getSize; + + /* read as much as we can */ + if (zstream.avail_in == 0) { + getSize = (compRemaining > kReadBufSize) ? + kReadBufSize : compRemaining; + ALOGV("+++ reading %ld bytes (%ld left)\n", + getSize, compRemaining); + + int cc = TEMP_FAILURE_RETRY(read(fd, readBuf, getSize)); + if (cc < 0) { + ALOGW("inflate read failed: %s", strerror(errno)); + } else if (cc != (int) getSize) { + ALOGW("inflate read failed (%d vs %ld)", cc, getSize); + goto z_bail; + } + + compRemaining -= getSize; + + zstream.next_in = readBuf; + zstream.avail_in = getSize; + } + + /* uncompress the data */ + zerr = inflate(&zstream, Z_NO_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + ALOGD("zlib inflate call failed (zerr=%d)\n", zerr); + goto z_bail; + } + + /* output buffer holds all, so no need to write the output */ + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + if ((long) zstream.total_out != uncompressedLen) { + ALOGW("Size mismatch on inflated file (%ld vs %ld)\n", + zstream.total_out, uncompressedLen); + goto z_bail; + } + + // success! + result = true; + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] readBuf; + return result; +} + +/* + * Utility function that expands zip/gzip "deflate" compressed data + * into a buffer. + * + * (This is a clone of the previous function, but it takes a FILE* instead + * of an fd. We could pass fileno(fd) to the above, but we can run into + * trouble when "fp" has a different notion of what fd's file position is.) + * + * "fp" is an open file positioned at the start of the "deflate" data + * "buf" must hold at least "uncompressedLen" bytes. + */ +/*static*/ bool ZipUtils::inflateToBuffer(FILE* fp, void* buf, + long uncompressedLen, long compressedLen) +{ + bool result = false; + const unsigned long kReadBufSize = 32768; + unsigned char* readBuf = NULL; + z_stream zstream; + int zerr; + unsigned long compRemaining; + + assert(uncompressedLen >= 0); + assert(compressedLen >= 0); + + readBuf = new unsigned char[kReadBufSize]; + if (readBuf == NULL) + goto bail; + compRemaining = compressedLen; + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = (Bytef*) buf; + zstream.avail_out = uncompressedLen; + zstream.data_type = Z_UNKNOWN; + + /* + * Use the undocumented "negative window bits" feature to tell zlib + * that there's no zlib header waiting for it. + */ + zerr = inflateInit2(&zstream, -MAX_WBITS); + if (zerr != Z_OK) { + if (zerr == Z_VERSION_ERROR) { + ALOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + /* + * Loop while we have data. + */ + do { + unsigned long getSize; + + /* read as much as we can */ + if (zstream.avail_in == 0) { + getSize = (compRemaining > kReadBufSize) ? + kReadBufSize : compRemaining; + ALOGV("+++ reading %ld bytes (%ld left)\n", + getSize, compRemaining); + + int cc = fread(readBuf, 1, getSize, fp); + if (cc != (int) getSize) { + ALOGD("inflate read failed (%d vs %ld)\n", + cc, getSize); + goto z_bail; + } + + compRemaining -= getSize; + + zstream.next_in = readBuf; + zstream.avail_in = getSize; + } + + /* uncompress the data */ + zerr = inflate(&zstream, Z_NO_FLUSH); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + ALOGD("zlib inflate call failed (zerr=%d)\n", zerr); + goto z_bail; + } + + /* output buffer holds all, so no need to write the output */ + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + if ((long) zstream.total_out != uncompressedLen) { + ALOGW("Size mismatch on inflated file (%ld vs %ld)\n", + zstream.total_out, uncompressedLen); + goto z_bail; + } + + // success! + result = true; + +z_bail: + inflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] readBuf; + return result; +} + +/* + * Look at the contents of a gzip archive. We want to know where the + * data starts, and how long it will be after it is uncompressed. + * + * We expect to find the CRC and length as the last 8 bytes on the file. + * This is a pretty reasonable thing to expect for locally-compressed + * files, but there's a small chance that some extra padding got thrown + * on (the man page talks about compressed data written to tape). We + * don't currently deal with that here. If "gzip -l" whines, we're going + * to fail too. + * + * On exit, "fp" is pointing at the start of the compressed data. + */ +/*static*/ bool ZipUtils::examineGzip(FILE* fp, int* pCompressionMethod, + long* pUncompressedLen, long* pCompressedLen, unsigned long* pCRC32) +{ + enum { // flags + FTEXT = 0x01, + FHCRC = 0x02, + FEXTRA = 0x04, + FNAME = 0x08, + FCOMMENT = 0x10, + }; + int ic; + int method, flags; + int i; + + ic = getc(fp); + if (ic != 0x1f || getc(fp) != 0x8b) + return false; // not gzip + method = getc(fp); + flags = getc(fp); + + /* quick sanity checks */ + if (method == EOF || flags == EOF) + return false; + if (method != ZipFileRO::kCompressDeflated) + return false; + + /* skip over 4 bytes of mod time, 1 byte XFL, 1 byte OS */ + for (i = 0; i < 6; i++) + (void) getc(fp); + /* consume "extra" field, if present */ + if ((flags & FEXTRA) != 0) { + int len; + + len = getc(fp); + len |= getc(fp) << 8; + while (len-- && getc(fp) != EOF) + ; + } + /* consume filename, if present */ + if ((flags & FNAME) != 0) { + do { + ic = getc(fp); + } while (ic != 0 && ic != EOF); + } + /* consume comment, if present */ + if ((flags & FCOMMENT) != 0) { + do { + ic = getc(fp); + } while (ic != 0 && ic != EOF); + } + /* consume 16-bit header CRC, if present */ + if ((flags & FHCRC) != 0) { + (void) getc(fp); + (void) getc(fp); + } + + if (feof(fp) || ferror(fp)) + return false; + + /* seek to the end; CRC and length are in the last 8 bytes */ + long curPosn = ftell(fp); + unsigned char buf[8]; + fseek(fp, -8, SEEK_END); + *pCompressedLen = ftell(fp) - curPosn; + + if (fread(buf, 1, 8, fp) != 8) + return false; + /* seek back to start of compressed data */ + fseek(fp, curPosn, SEEK_SET); + + *pCompressionMethod = method; + *pCRC32 = ZipFileRO::get4LE(&buf[0]); + *pUncompressedLen = ZipFileRO::get4LE(&buf[4]); + + return true; +} diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp new file mode 100644 index 0000000..29686ef --- /dev/null +++ b/libs/androidfw/misc.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2005 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 "misc" + +// +// Miscellaneous utility functions. +// +#include <androidfw/misc.h> + +#include <sys/stat.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> + +using namespace android; + +namespace android { + +/* + * Get a file's type. + */ +FileType getFileType(const char* fileName) +{ + struct stat sb; + + if (stat(fileName, &sb) < 0) { + if (errno == ENOENT || errno == ENOTDIR) + return kFileTypeNonexistent; + else { + fprintf(stderr, "getFileType got errno=%d on '%s'\n", + errno, fileName); + return kFileTypeUnknown; + } + } else { + if (S_ISREG(sb.st_mode)) + return kFileTypeRegular; + else if (S_ISDIR(sb.st_mode)) + return kFileTypeDirectory; + else if (S_ISCHR(sb.st_mode)) + return kFileTypeCharDev; + else if (S_ISBLK(sb.st_mode)) + return kFileTypeBlockDev; + else if (S_ISFIFO(sb.st_mode)) + return kFileTypeFifo; +#ifdef HAVE_SYMLINKS + else if (S_ISLNK(sb.st_mode)) + return kFileTypeSymlink; + else if (S_ISSOCK(sb.st_mode)) + return kFileTypeSocket; +#endif + else + return kFileTypeUnknown; + } +} + +/* + * Get a file's modification date. + */ +time_t getFileModDate(const char* fileName) +{ + struct stat sb; + + if (stat(fileName, &sb) < 0) + return (time_t) -1; + + return sb.st_mtime; +} + +}; // namespace android diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk index 4ae23ec..0522212 100644 --- a/libs/androidfw/tests/Android.mk +++ b/libs/androidfw/tests/Android.mk @@ -4,19 +4,15 @@ include $(CLEAR_VARS) # Build the unit tests. test_src_files := \ - InputChannel_test.cpp \ - InputEvent_test.cpp \ - InputPublisherAndConsumer_test.cpp \ - ObbFile_test.cpp + ObbFile_test.cpp \ + ZipFileRO_test.cpp shared_libraries := \ libandroidfw \ libcutils \ libutils \ - libbinder \ libui \ - libstlport \ - libskia + libstlport static_libraries := \ libgtest \ diff --git a/libs/androidfw/tests/InputChannel_test.cpp b/libs/androidfw/tests/InputChannel_test.cpp deleted file mode 100644 index 7fff8af..0000000 --- a/libs/androidfw/tests/InputChannel_test.cpp +++ /dev/null @@ -1,158 +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 <androidfw/InputTransport.h> -#include <utils/Timers.h> -#include <utils/StopWatch.h> -#include <utils/StrongPointer.h> -#include <gtest/gtest.h> -#include <unistd.h> -#include <time.h> -#include <errno.h> - -#include "TestHelpers.h" - -namespace android { - -class InputChannelTest : public testing::Test { -protected: - virtual void SetUp() { } - virtual void TearDown() { } -}; - - -TEST_F(InputChannelTest, ConstructorAndDestructor_TakesOwnershipOfFileDescriptors) { - // Our purpose here is to verify that the input channel destructor closes the - // file descriptor provided to it. One easy way is to provide it with one end - // of a pipe and to check for EPIPE on the other end after the channel is destroyed. - Pipe pipe; - - sp<InputChannel> inputChannel = new InputChannel(String8("channel name"), pipe.sendFd); - - EXPECT_STREQ("channel name", inputChannel->getName().string()) - << "channel should have provided name"; - EXPECT_EQ(pipe.sendFd, inputChannel->getFd()) - << "channel should have provided fd"; - - inputChannel.clear(); // destroys input channel - - EXPECT_EQ(-EPIPE, pipe.readSignal()) - << "channel should have closed fd when destroyed"; - - // clean up fds of Pipe endpoints that were closed so we don't try to close them again - pipe.sendFd = -1; -} - -TEST_F(InputChannelTest, OpenInputChannelPair_ReturnsAPairOfConnectedChannels) { - sp<InputChannel> serverChannel, clientChannel; - - status_t result = InputChannel::openInputChannelPair(String8("channel name"), - serverChannel, clientChannel); - - ASSERT_EQ(OK, result) - << "should have successfully opened a channel pair"; - - // Name - EXPECT_STREQ("channel name (server)", serverChannel->getName().string()) - << "server channel should have suffixed name"; - EXPECT_STREQ("channel name (client)", clientChannel->getName().string()) - << "client channel should have suffixed name"; - - // Server->Client communication - InputMessage serverMsg; - memset(&serverMsg, 0, sizeof(InputMessage)); - serverMsg.header.type = InputMessage::TYPE_KEY; - serverMsg.body.key.action = AKEY_EVENT_ACTION_DOWN; - EXPECT_EQ(OK, serverChannel->sendMessage(&serverMsg)) - << "server channel should be able to send message to client channel"; - - InputMessage clientMsg; - EXPECT_EQ(OK, clientChannel->receiveMessage(&clientMsg)) - << "client channel should be able to receive message from server channel"; - EXPECT_EQ(serverMsg.header.type, clientMsg.header.type) - << "client channel should receive the correct message from server channel"; - EXPECT_EQ(serverMsg.body.key.action, clientMsg.body.key.action) - << "client channel should receive the correct message from server channel"; - - // Client->Server communication - InputMessage clientReply; - memset(&clientReply, 0, sizeof(InputMessage)); - clientReply.header.type = InputMessage::TYPE_FINISHED; - clientReply.body.finished.seq = 0x11223344; - clientReply.body.finished.handled = true; - EXPECT_EQ(OK, clientChannel->sendMessage(&clientReply)) - << "client channel should be able to send message to server channel"; - - InputMessage serverReply; - EXPECT_EQ(OK, serverChannel->receiveMessage(&serverReply)) - << "server channel should be able to receive message from client channel"; - EXPECT_EQ(clientReply.header.type, serverReply.header.type) - << "server channel should receive the correct message from client channel"; - EXPECT_EQ(clientReply.body.finished.seq, serverReply.body.finished.seq) - << "server channel should receive the correct message from client channel"; - EXPECT_EQ(clientReply.body.finished.handled, serverReply.body.finished.handled) - << "server channel should receive the correct message from client channel"; -} - -TEST_F(InputChannelTest, ReceiveSignal_WhenNoSignalPresent_ReturnsAnError) { - sp<InputChannel> serverChannel, clientChannel; - - status_t result = InputChannel::openInputChannelPair(String8("channel name"), - serverChannel, clientChannel); - - ASSERT_EQ(OK, result) - << "should have successfully opened a channel pair"; - - InputMessage msg; - EXPECT_EQ(WOULD_BLOCK, clientChannel->receiveMessage(&msg)) - << "receiveMessage should have returned WOULD_BLOCK"; -} - -TEST_F(InputChannelTest, ReceiveSignal_WhenPeerClosed_ReturnsAnError) { - sp<InputChannel> serverChannel, clientChannel; - - status_t result = InputChannel::openInputChannelPair(String8("channel name"), - serverChannel, clientChannel); - - ASSERT_EQ(OK, result) - << "should have successfully opened a channel pair"; - - serverChannel.clear(); // close server channel - - InputMessage msg; - EXPECT_EQ(DEAD_OBJECT, clientChannel->receiveMessage(&msg)) - << "receiveMessage should have returned DEAD_OBJECT"; -} - -TEST_F(InputChannelTest, SendSignal_WhenPeerClosed_ReturnsAnError) { - sp<InputChannel> serverChannel, clientChannel; - - status_t result = InputChannel::openInputChannelPair(String8("channel name"), - serverChannel, clientChannel); - - ASSERT_EQ(OK, result) - << "should have successfully opened a channel pair"; - - serverChannel.clear(); // close server channel - - InputMessage msg; - msg.header.type = InputMessage::TYPE_KEY; - EXPECT_EQ(DEAD_OBJECT, clientChannel->sendMessage(&msg)) - << "sendMessage should have returned DEAD_OBJECT"; -} - - -} // namespace android diff --git a/libs/androidfw/tests/InputEvent_test.cpp b/libs/androidfw/tests/InputEvent_test.cpp deleted file mode 100644 index e9164d1..0000000 --- a/libs/androidfw/tests/InputEvent_test.cpp +++ /dev/null @@ -1,581 +0,0 @@ -/* - * 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 <androidfw/Input.h> -#include <gtest/gtest.h> -#include <binder/Parcel.h> - -#include <math.h> -#include <core/SkMatrix.h> - -namespace android { - -class BaseTest : public testing::Test { -protected: - virtual void SetUp() { } - virtual void TearDown() { } -}; - -// --- PointerCoordsTest --- - -class PointerCoordsTest : public BaseTest { -}; - -TEST_F(PointerCoordsTest, ClearSetsBitsToZero) { - PointerCoords coords; - coords.clear(); - - ASSERT_EQ(0ULL, coords.bits); -} - -TEST_F(PointerCoordsTest, AxisValues) { - float* valuePtr; - PointerCoords coords; - coords.clear(); - - // Check invariants when no axes are present. - ASSERT_EQ(0, coords.getAxisValue(0)) - << "getAxisValue should return zero because axis is not present"; - ASSERT_EQ(0, coords.getAxisValue(1)) - << "getAxisValue should return zero because axis is not present"; - - // Set first axis. - ASSERT_EQ(OK, coords.setAxisValue(1, 5)); - ASSERT_EQ(0x00000002ULL, coords.bits); - ASSERT_EQ(5, coords.values[0]); - - ASSERT_EQ(0, coords.getAxisValue(0)) - << "getAxisValue should return zero because axis is not present"; - ASSERT_EQ(5, coords.getAxisValue(1)) - << "getAxisValue should return value of axis"; - - // Set an axis with a higher id than all others. (appending value at the end) - ASSERT_EQ(OK, coords.setAxisValue(3, 2)); - ASSERT_EQ(0x0000000aULL, coords.bits); - ASSERT_EQ(5, coords.values[0]); - ASSERT_EQ(2, coords.values[1]); - - ASSERT_EQ(0, coords.getAxisValue(0)) - << "getAxisValue should return zero because axis is not present"; - ASSERT_EQ(5, coords.getAxisValue(1)) - << "getAxisValue should return value of axis"; - ASSERT_EQ(0, coords.getAxisValue(2)) - << "getAxisValue should return zero because axis is not present"; - ASSERT_EQ(2, coords.getAxisValue(3)) - << "getAxisValue should return value of axis"; - - // Set an axis with an id lower than all others. (prepending value at beginning) - ASSERT_EQ(OK, coords.setAxisValue(0, 4)); - ASSERT_EQ(0x0000000bULL, coords.bits); - ASSERT_EQ(4, coords.values[0]); - ASSERT_EQ(5, coords.values[1]); - ASSERT_EQ(2, coords.values[2]); - - ASSERT_EQ(4, coords.getAxisValue(0)) - << "getAxisValue should return value of axis"; - ASSERT_EQ(5, coords.getAxisValue(1)) - << "getAxisValue should return value of axis"; - ASSERT_EQ(0, coords.getAxisValue(2)) - << "getAxisValue should return zero because axis is not present"; - ASSERT_EQ(2, coords.getAxisValue(3)) - << "getAxisValue should return value of axis"; - - // Set an axis with an id between the others. (inserting value in the middle) - ASSERT_EQ(OK, coords.setAxisValue(2, 1)); - ASSERT_EQ(0x0000000fULL, coords.bits); - ASSERT_EQ(4, coords.values[0]); - ASSERT_EQ(5, coords.values[1]); - ASSERT_EQ(1, coords.values[2]); - ASSERT_EQ(2, coords.values[3]); - - ASSERT_EQ(4, coords.getAxisValue(0)) - << "getAxisValue should return value of axis"; - ASSERT_EQ(5, coords.getAxisValue(1)) - << "getAxisValue should return value of axis"; - ASSERT_EQ(1, coords.getAxisValue(2)) - << "getAxisValue should return value of axis"; - ASSERT_EQ(2, coords.getAxisValue(3)) - << "getAxisValue should return value of axis"; - - // Set an existing axis value in place. - ASSERT_EQ(OK, coords.setAxisValue(1, 6)); - ASSERT_EQ(0x0000000fULL, coords.bits); - ASSERT_EQ(4, coords.values[0]); - ASSERT_EQ(6, coords.values[1]); - ASSERT_EQ(1, coords.values[2]); - ASSERT_EQ(2, coords.values[3]); - - ASSERT_EQ(4, coords.getAxisValue(0)) - << "getAxisValue should return value of axis"; - ASSERT_EQ(6, coords.getAxisValue(1)) - << "getAxisValue should return value of axis"; - ASSERT_EQ(1, coords.getAxisValue(2)) - << "getAxisValue should return value of axis"; - ASSERT_EQ(2, coords.getAxisValue(3)) - << "getAxisValue should return value of axis"; - - // Set maximum number of axes. - for (size_t axis = 4; axis < PointerCoords::MAX_AXES; axis++) { - ASSERT_EQ(OK, coords.setAxisValue(axis, axis)); - } - ASSERT_EQ(PointerCoords::MAX_AXES, __builtin_popcountll(coords.bits)); - - // Try to set one more axis beyond maximum number. - // Ensure bits are unchanged. - ASSERT_EQ(NO_MEMORY, coords.setAxisValue(PointerCoords::MAX_AXES, 100)); - ASSERT_EQ(PointerCoords::MAX_AXES, __builtin_popcountll(coords.bits)); -} - -TEST_F(PointerCoordsTest, Parcel) { - Parcel parcel; - - PointerCoords inCoords; - inCoords.clear(); - PointerCoords outCoords; - - // Round trip with empty coords. - inCoords.writeToParcel(&parcel); - parcel.setDataPosition(0); - outCoords.readFromParcel(&parcel); - - ASSERT_EQ(0ULL, outCoords.bits); - - // Round trip with some values. - parcel.freeData(); - inCoords.setAxisValue(2, 5); - inCoords.setAxisValue(5, 8); - - inCoords.writeToParcel(&parcel); - parcel.setDataPosition(0); - outCoords.readFromParcel(&parcel); - - ASSERT_EQ(outCoords.bits, inCoords.bits); - ASSERT_EQ(outCoords.values[0], inCoords.values[0]); - ASSERT_EQ(outCoords.values[1], inCoords.values[1]); -} - - -// --- KeyEventTest --- - -class KeyEventTest : public BaseTest { -}; - -TEST_F(KeyEventTest, Properties) { - KeyEvent event; - - // Initialize and get properties. - const nsecs_t ARBITRARY_DOWN_TIME = 1; - const nsecs_t ARBITRARY_EVENT_TIME = 2; - event.initialize(2, AINPUT_SOURCE_GAMEPAD, AKEY_EVENT_ACTION_DOWN, - AKEY_EVENT_FLAG_FROM_SYSTEM, AKEYCODE_BUTTON_X, 121, - AMETA_ALT_ON, 1, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME); - - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, event.getType()); - ASSERT_EQ(2, event.getDeviceId()); - ASSERT_EQ(AINPUT_SOURCE_GAMEPAD, event.getSource()); - ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, event.getAction()); - ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, event.getFlags()); - ASSERT_EQ(AKEYCODE_BUTTON_X, event.getKeyCode()); - ASSERT_EQ(121, event.getScanCode()); - ASSERT_EQ(AMETA_ALT_ON, event.getMetaState()); - ASSERT_EQ(1, event.getRepeatCount()); - ASSERT_EQ(ARBITRARY_DOWN_TIME, event.getDownTime()); - ASSERT_EQ(ARBITRARY_EVENT_TIME, event.getEventTime()); - - // Set source. - event.setSource(AINPUT_SOURCE_JOYSTICK); - ASSERT_EQ(AINPUT_SOURCE_JOYSTICK, event.getSource()); -} - - -// --- MotionEventTest --- - -class MotionEventTest : public BaseTest { -protected: - static const nsecs_t ARBITRARY_DOWN_TIME; - static const nsecs_t ARBITRARY_EVENT_TIME; - static const float X_OFFSET; - static const float Y_OFFSET; - - void initializeEventWithHistory(MotionEvent* event); - void assertEqualsEventWithHistory(const MotionEvent* event); -}; - -const nsecs_t MotionEventTest::ARBITRARY_DOWN_TIME = 1; -const nsecs_t MotionEventTest::ARBITRARY_EVENT_TIME = 2; -const float MotionEventTest::X_OFFSET = 1.0f; -const float MotionEventTest::Y_OFFSET = 1.1f; - -void MotionEventTest::initializeEventWithHistory(MotionEvent* event) { - PointerProperties pointerProperties[2]; - pointerProperties[0].clear(); - pointerProperties[0].id = 1; - pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; - pointerProperties[1].clear(); - pointerProperties[1].id = 2; - pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; - - PointerCoords pointerCoords[2]; - pointerCoords[0].clear(); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 11); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 12); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 13); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 14); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 15); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 16); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 17); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 18); - pointerCoords[1].clear(); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 20); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 21); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 23); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 24); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 25); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 26); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 27); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 28); - event->initialize(2, AINPUT_SOURCE_TOUCHSCREEN, AMOTION_EVENT_ACTION_MOVE, - AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED, - AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY, - X_OFFSET, Y_OFFSET, 2.0f, 2.1f, - ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, - 2, pointerProperties, pointerCoords); - - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 112); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 113); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 114); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 115); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 116); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 117); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 118); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 120); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 121); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 122); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 123); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 124); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 125); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 126); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 127); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 128); - event->addSample(ARBITRARY_EVENT_TIME + 1, pointerCoords); - - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 210); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 211); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 212); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 213); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 214); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 215); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 216); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 217); - pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 218); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 220); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 221); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 222); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 223); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 224); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 225); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 226); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 227); - pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 228); - event->addSample(ARBITRARY_EVENT_TIME + 2, pointerCoords); -} - -void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) { - // Check properties. - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()); - ASSERT_EQ(2, event->getDeviceId()); - ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, event->getSource()); - ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, event->getAction()); - ASSERT_EQ(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED, event->getFlags()); - ASSERT_EQ(AMOTION_EVENT_EDGE_FLAG_TOP, event->getEdgeFlags()); - ASSERT_EQ(AMETA_ALT_ON, event->getMetaState()); - ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, event->getButtonState()); - ASSERT_EQ(X_OFFSET, event->getXOffset()); - ASSERT_EQ(Y_OFFSET, event->getYOffset()); - ASSERT_EQ(2.0f, event->getXPrecision()); - ASSERT_EQ(2.1f, event->getYPrecision()); - ASSERT_EQ(ARBITRARY_DOWN_TIME, event->getDownTime()); - - ASSERT_EQ(2U, event->getPointerCount()); - ASSERT_EQ(1, event->getPointerId(0)); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, event->getToolType(0)); - ASSERT_EQ(2, event->getPointerId(1)); - ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, event->getToolType(1)); - - ASSERT_EQ(2U, event->getHistorySize()); - - // Check data. - ASSERT_EQ(ARBITRARY_EVENT_TIME, event->getHistoricalEventTime(0)); - ASSERT_EQ(ARBITRARY_EVENT_TIME + 1, event->getHistoricalEventTime(1)); - ASSERT_EQ(ARBITRARY_EVENT_TIME + 2, event->getEventTime()); - - ASSERT_EQ(11, event->getHistoricalRawPointerCoords(0, 0)-> - getAxisValue(AMOTION_EVENT_AXIS_Y)); - ASSERT_EQ(21, event->getHistoricalRawPointerCoords(1, 0)-> - getAxisValue(AMOTION_EVENT_AXIS_Y)); - ASSERT_EQ(111, event->getHistoricalRawPointerCoords(0, 1)-> - getAxisValue(AMOTION_EVENT_AXIS_Y)); - ASSERT_EQ(121, event->getHistoricalRawPointerCoords(1, 1)-> - getAxisValue(AMOTION_EVENT_AXIS_Y)); - ASSERT_EQ(211, event->getRawPointerCoords(0)-> - getAxisValue(AMOTION_EVENT_AXIS_Y)); - ASSERT_EQ(221, event->getRawPointerCoords(1)-> - getAxisValue(AMOTION_EVENT_AXIS_Y)); - - ASSERT_EQ(11, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0)); - ASSERT_EQ(21, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0)); - ASSERT_EQ(111, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1)); - ASSERT_EQ(121, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1)); - ASSERT_EQ(211, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0)); - ASSERT_EQ(221, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1)); - - ASSERT_EQ(10, event->getHistoricalRawX(0, 0)); - ASSERT_EQ(20, event->getHistoricalRawX(1, 0)); - ASSERT_EQ(110, event->getHistoricalRawX(0, 1)); - ASSERT_EQ(120, event->getHistoricalRawX(1, 1)); - ASSERT_EQ(210, event->getRawX(0)); - ASSERT_EQ(220, event->getRawX(1)); - - ASSERT_EQ(11, event->getHistoricalRawY(0, 0)); - ASSERT_EQ(21, event->getHistoricalRawY(1, 0)); - ASSERT_EQ(111, event->getHistoricalRawY(0, 1)); - ASSERT_EQ(121, event->getHistoricalRawY(1, 1)); - ASSERT_EQ(211, event->getRawY(0)); - ASSERT_EQ(221, event->getRawY(1)); - - ASSERT_EQ(X_OFFSET + 10, event->getHistoricalX(0, 0)); - ASSERT_EQ(X_OFFSET + 20, event->getHistoricalX(1, 0)); - ASSERT_EQ(X_OFFSET + 110, event->getHistoricalX(0, 1)); - ASSERT_EQ(X_OFFSET + 120, event->getHistoricalX(1, 1)); - ASSERT_EQ(X_OFFSET + 210, event->getX(0)); - ASSERT_EQ(X_OFFSET + 220, event->getX(1)); - - ASSERT_EQ(Y_OFFSET + 11, event->getHistoricalY(0, 0)); - ASSERT_EQ(Y_OFFSET + 21, event->getHistoricalY(1, 0)); - ASSERT_EQ(Y_OFFSET + 111, event->getHistoricalY(0, 1)); - ASSERT_EQ(Y_OFFSET + 121, event->getHistoricalY(1, 1)); - ASSERT_EQ(Y_OFFSET + 211, event->getY(0)); - ASSERT_EQ(Y_OFFSET + 221, event->getY(1)); - - ASSERT_EQ(12, event->getHistoricalPressure(0, 0)); - ASSERT_EQ(22, event->getHistoricalPressure(1, 0)); - ASSERT_EQ(112, event->getHistoricalPressure(0, 1)); - ASSERT_EQ(122, event->getHistoricalPressure(1, 1)); - ASSERT_EQ(212, event->getPressure(0)); - ASSERT_EQ(222, event->getPressure(1)); - - ASSERT_EQ(13, event->getHistoricalSize(0, 0)); - ASSERT_EQ(23, event->getHistoricalSize(1, 0)); - ASSERT_EQ(113, event->getHistoricalSize(0, 1)); - ASSERT_EQ(123, event->getHistoricalSize(1, 1)); - ASSERT_EQ(213, event->getSize(0)); - ASSERT_EQ(223, event->getSize(1)); - - ASSERT_EQ(14, event->getHistoricalTouchMajor(0, 0)); - ASSERT_EQ(24, event->getHistoricalTouchMajor(1, 0)); - ASSERT_EQ(114, event->getHistoricalTouchMajor(0, 1)); - ASSERT_EQ(124, event->getHistoricalTouchMajor(1, 1)); - ASSERT_EQ(214, event->getTouchMajor(0)); - ASSERT_EQ(224, event->getTouchMajor(1)); - - ASSERT_EQ(15, event->getHistoricalTouchMinor(0, 0)); - ASSERT_EQ(25, event->getHistoricalTouchMinor(1, 0)); - ASSERT_EQ(115, event->getHistoricalTouchMinor(0, 1)); - ASSERT_EQ(125, event->getHistoricalTouchMinor(1, 1)); - ASSERT_EQ(215, event->getTouchMinor(0)); - ASSERT_EQ(225, event->getTouchMinor(1)); - - ASSERT_EQ(16, event->getHistoricalToolMajor(0, 0)); - ASSERT_EQ(26, event->getHistoricalToolMajor(1, 0)); - ASSERT_EQ(116, event->getHistoricalToolMajor(0, 1)); - ASSERT_EQ(126, event->getHistoricalToolMajor(1, 1)); - ASSERT_EQ(216, event->getToolMajor(0)); - ASSERT_EQ(226, event->getToolMajor(1)); - - ASSERT_EQ(17, event->getHistoricalToolMinor(0, 0)); - ASSERT_EQ(27, event->getHistoricalToolMinor(1, 0)); - ASSERT_EQ(117, event->getHistoricalToolMinor(0, 1)); - ASSERT_EQ(127, event->getHistoricalToolMinor(1, 1)); - ASSERT_EQ(217, event->getToolMinor(0)); - ASSERT_EQ(227, event->getToolMinor(1)); - - ASSERT_EQ(18, event->getHistoricalOrientation(0, 0)); - ASSERT_EQ(28, event->getHistoricalOrientation(1, 0)); - ASSERT_EQ(118, event->getHistoricalOrientation(0, 1)); - ASSERT_EQ(128, event->getHistoricalOrientation(1, 1)); - ASSERT_EQ(218, event->getOrientation(0)); - ASSERT_EQ(228, event->getOrientation(1)); -} - -TEST_F(MotionEventTest, Properties) { - MotionEvent event; - - // Initialize, add samples and check properties. - initializeEventWithHistory(&event); - ASSERT_NO_FATAL_FAILURE(assertEqualsEventWithHistory(&event)); - - // Set source. - event.setSource(AINPUT_SOURCE_JOYSTICK); - ASSERT_EQ(AINPUT_SOURCE_JOYSTICK, event.getSource()); - - // Set action. - event.setAction(AMOTION_EVENT_ACTION_CANCEL); - ASSERT_EQ(AMOTION_EVENT_ACTION_CANCEL, event.getAction()); - - // Set meta state. - event.setMetaState(AMETA_CTRL_ON); - ASSERT_EQ(AMETA_CTRL_ON, event.getMetaState()); -} - -TEST_F(MotionEventTest, CopyFrom_KeepHistory) { - MotionEvent event; - initializeEventWithHistory(&event); - - MotionEvent copy; - copy.copyFrom(&event, true /*keepHistory*/); - - ASSERT_NO_FATAL_FAILURE(assertEqualsEventWithHistory(&event)); -} - -TEST_F(MotionEventTest, CopyFrom_DoNotKeepHistory) { - MotionEvent event; - initializeEventWithHistory(&event); - - MotionEvent copy; - copy.copyFrom(&event, false /*keepHistory*/); - - ASSERT_EQ(event.getPointerCount(), copy.getPointerCount()); - ASSERT_EQ(0U, copy.getHistorySize()); - - ASSERT_EQ(event.getPointerId(0), copy.getPointerId(0)); - ASSERT_EQ(event.getPointerId(1), copy.getPointerId(1)); - - ASSERT_EQ(event.getEventTime(), copy.getEventTime()); - - ASSERT_EQ(event.getX(0), copy.getX(0)); -} - -TEST_F(MotionEventTest, OffsetLocation) { - MotionEvent event; - initializeEventWithHistory(&event); - - event.offsetLocation(5.0f, -2.0f); - - ASSERT_EQ(X_OFFSET + 5.0f, event.getXOffset()); - ASSERT_EQ(Y_OFFSET - 2.0f, event.getYOffset()); -} - -TEST_F(MotionEventTest, Scale) { - MotionEvent event; - initializeEventWithHistory(&event); - - event.scale(2.0f); - - ASSERT_EQ(X_OFFSET * 2, event.getXOffset()); - ASSERT_EQ(Y_OFFSET * 2, event.getYOffset()); - - ASSERT_EQ(210 * 2, event.getRawX(0)); - ASSERT_EQ(211 * 2, event.getRawY(0)); - ASSERT_EQ((X_OFFSET + 210) * 2, event.getX(0)); - ASSERT_EQ((Y_OFFSET + 211) * 2, event.getY(0)); - ASSERT_EQ(212, event.getPressure(0)); - ASSERT_EQ(213, event.getSize(0)); - ASSERT_EQ(214 * 2, event.getTouchMajor(0)); - ASSERT_EQ(215 * 2, event.getTouchMinor(0)); - ASSERT_EQ(216 * 2, event.getToolMajor(0)); - ASSERT_EQ(217 * 2, event.getToolMinor(0)); - ASSERT_EQ(218, event.getOrientation(0)); -} - -TEST_F(MotionEventTest, Parcel) { - Parcel parcel; - - MotionEvent inEvent; - initializeEventWithHistory(&inEvent); - MotionEvent outEvent; - - // Round trip. - inEvent.writeToParcel(&parcel); - parcel.setDataPosition(0); - outEvent.readFromParcel(&parcel); - - ASSERT_NO_FATAL_FAILURE(assertEqualsEventWithHistory(&outEvent)); -} - -TEST_F(MotionEventTest, Transform) { - // Generate some points on a circle. - // Each point 'i' is a point on a circle of radius ROTATION centered at (3,2) at an angle - // of ARC * i degrees clockwise relative to the Y axis. - // The geometrical representation is irrelevant to the test, it's just easy to generate - // and check rotation. We set the orientation to the same angle. - // Coordinate system: down is increasing Y, right is increasing X. - const float PI_180 = float(M_PI / 180); - const float RADIUS = 10; - const float ARC = 36; - const float ROTATION = ARC * 2; - - const size_t pointerCount = 11; - PointerProperties pointerProperties[pointerCount]; - PointerCoords pointerCoords[pointerCount]; - for (size_t i = 0; i < pointerCount; i++) { - float angle = float(i * ARC * PI_180); - pointerProperties[i].clear(); - pointerProperties[i].id = i; - pointerCoords[i].clear(); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, sinf(angle) * RADIUS + 3); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, -cosf(angle) * RADIUS + 2); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, angle); - } - MotionEvent event; - event.initialize(0, 0, AMOTION_EVENT_ACTION_MOVE, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, pointerCount, pointerProperties, pointerCoords); - float originalRawX = 0 + 3; - float originalRawY = -RADIUS + 2; - - // Check original raw X and Y assumption. - ASSERT_NEAR(originalRawX, event.getRawX(0), 0.001); - ASSERT_NEAR(originalRawY, event.getRawY(0), 0.001); - - // Now translate the motion event so the circle's origin is at (0,0). - event.offsetLocation(-3, -2); - - // Offsetting the location should preserve the raw X and Y of the first point. - ASSERT_NEAR(originalRawX, event.getRawX(0), 0.001); - ASSERT_NEAR(originalRawY, event.getRawY(0), 0.001); - - // Apply a rotation about the origin by ROTATION degrees clockwise. - SkMatrix matrix; - matrix.setRotate(ROTATION); - event.transform(&matrix); - - // Check the points. - for (size_t i = 0; i < pointerCount; i++) { - float angle = float((i * ARC + ROTATION) * PI_180); - ASSERT_NEAR(sinf(angle) * RADIUS, event.getX(i), 0.001); - ASSERT_NEAR(-cosf(angle) * RADIUS, event.getY(i), 0.001); - ASSERT_NEAR(tanf(angle), tanf(event.getOrientation(i)), 0.1); - } - - // Applying the transformation should preserve the raw X and Y of the first point. - ASSERT_NEAR(originalRawX, event.getRawX(0), 0.001); - ASSERT_NEAR(originalRawY, event.getRawY(0), 0.001); -} - -} // namespace android diff --git a/libs/androidfw/tests/InputPublisherAndConsumer_test.cpp b/libs/androidfw/tests/InputPublisherAndConsumer_test.cpp deleted file mode 100644 index f45774b..0000000 --- a/libs/androidfw/tests/InputPublisherAndConsumer_test.cpp +++ /dev/null @@ -1,287 +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 <androidfw/InputTransport.h> -#include <utils/Timers.h> -#include <utils/StopWatch.h> -#include <gtest/gtest.h> -#include <unistd.h> -#include <time.h> -#include <sys/mman.h> -#include <cutils/ashmem.h> - -#include "TestHelpers.h" - -namespace android { - -class InputPublisherAndConsumerTest : public testing::Test { -protected: - sp<InputChannel> serverChannel, clientChannel; - InputPublisher* mPublisher; - InputConsumer* mConsumer; - PreallocatedInputEventFactory mEventFactory; - - virtual void SetUp() { - status_t result = InputChannel::openInputChannelPair(String8("channel name"), - serverChannel, clientChannel); - - mPublisher = new InputPublisher(serverChannel); - mConsumer = new InputConsumer(clientChannel); - } - - virtual void TearDown() { - if (mPublisher) { - delete mPublisher; - mPublisher = NULL; - } - - if (mConsumer) { - delete mConsumer; - mConsumer = NULL; - } - - serverChannel.clear(); - clientChannel.clear(); - } - - void PublishAndConsumeKeyEvent(); - void PublishAndConsumeMotionEvent(); -}; - -TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) { - EXPECT_EQ(serverChannel.get(), mPublisher->getChannel().get()); - EXPECT_EQ(clientChannel.get(), mConsumer->getChannel().get()); -} - -void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() { - status_t status; - - const uint32_t seq = 15; - const int32_t deviceId = 1; - const int32_t source = AINPUT_SOURCE_KEYBOARD; - const int32_t action = AKEY_EVENT_ACTION_DOWN; - const int32_t flags = AKEY_EVENT_FLAG_FROM_SYSTEM; - const int32_t keyCode = AKEYCODE_ENTER; - const int32_t scanCode = 13; - const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON; - const int32_t repeatCount = 1; - const nsecs_t downTime = 3; - const nsecs_t eventTime = 4; - - status = mPublisher->publishKeyEvent(seq, deviceId, source, action, flags, - keyCode, scanCode, metaState, repeatCount, downTime, eventTime); - ASSERT_EQ(OK, status) - << "publisher publishKeyEvent should return OK"; - - uint32_t consumeSeq; - InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); - ASSERT_EQ(OK, status) - << "consumer consume should return OK"; - - ASSERT_TRUE(event != NULL) - << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, event->getType()) - << "consumer should have returned a key event"; - - KeyEvent* keyEvent = static_cast<KeyEvent*>(event); - EXPECT_EQ(seq, consumeSeq); - EXPECT_EQ(deviceId, keyEvent->getDeviceId()); - EXPECT_EQ(source, keyEvent->getSource()); - EXPECT_EQ(action, keyEvent->getAction()); - EXPECT_EQ(flags, keyEvent->getFlags()); - EXPECT_EQ(keyCode, keyEvent->getKeyCode()); - EXPECT_EQ(scanCode, keyEvent->getScanCode()); - EXPECT_EQ(metaState, keyEvent->getMetaState()); - EXPECT_EQ(repeatCount, keyEvent->getRepeatCount()); - EXPECT_EQ(downTime, keyEvent->getDownTime()); - EXPECT_EQ(eventTime, keyEvent->getEventTime()); - - status = mConsumer->sendFinishedSignal(seq, true); - ASSERT_EQ(OK, status) - << "consumer sendFinishedSignal should return OK"; - - uint32_t finishedSeq = 0; - bool handled = false; - status = mPublisher->receiveFinishedSignal(&finishedSeq, &handled); - ASSERT_EQ(OK, status) - << "publisher receiveFinishedSignal should return OK"; - ASSERT_EQ(seq, finishedSeq) - << "publisher receiveFinishedSignal should have returned the original sequence number"; - ASSERT_TRUE(handled) - << "publisher receiveFinishedSignal should have set handled to consumer's reply"; -} - -void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() { - status_t status; - - const uint32_t seq = 15; - const int32_t deviceId = 1; - const int32_t source = AINPUT_SOURCE_TOUCHSCREEN; - const int32_t action = AMOTION_EVENT_ACTION_MOVE; - const int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; - const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP; - const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON; - const int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY; - const float xOffset = -10; - const float yOffset = -20; - const float xPrecision = 0.25; - const float yPrecision = 0.5; - const nsecs_t downTime = 3; - const size_t pointerCount = 3; - const nsecs_t eventTime = 4; - PointerProperties pointerProperties[pointerCount]; - PointerCoords pointerCoords[pointerCount]; - for (size_t i = 0; i < pointerCount; i++) { - pointerProperties[i].clear(); - pointerProperties[i].id = (i + 2) % pointerCount; - pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; - - pointerCoords[i].clear(); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, 100 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, 200 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i); - pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i); - } - - status = mPublisher->publishMotionEvent(seq, deviceId, source, action, flags, edgeFlags, - metaState, buttonState, xOffset, yOffset, xPrecision, yPrecision, - downTime, eventTime, pointerCount, - pointerProperties, pointerCoords); - ASSERT_EQ(OK, status) - << "publisher publishMotionEvent should return OK"; - - uint32_t consumeSeq; - InputEvent* event; - status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event); - ASSERT_EQ(OK, status) - << "consumer consume should return OK"; - - ASSERT_TRUE(event != NULL) - << "consumer should have returned non-NULL event"; - ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType()) - << "consumer should have returned a motion event"; - - MotionEvent* motionEvent = static_cast<MotionEvent*>(event); - EXPECT_EQ(seq, consumeSeq); - EXPECT_EQ(deviceId, motionEvent->getDeviceId()); - EXPECT_EQ(source, motionEvent->getSource()); - EXPECT_EQ(action, motionEvent->getAction()); - EXPECT_EQ(flags, motionEvent->getFlags()); - EXPECT_EQ(edgeFlags, motionEvent->getEdgeFlags()); - EXPECT_EQ(metaState, motionEvent->getMetaState()); - EXPECT_EQ(buttonState, motionEvent->getButtonState()); - EXPECT_EQ(xPrecision, motionEvent->getXPrecision()); - EXPECT_EQ(yPrecision, motionEvent->getYPrecision()); - EXPECT_EQ(downTime, motionEvent->getDownTime()); - EXPECT_EQ(eventTime, motionEvent->getEventTime()); - EXPECT_EQ(pointerCount, motionEvent->getPointerCount()); - EXPECT_EQ(0U, motionEvent->getHistorySize()); - - for (size_t i = 0; i < pointerCount; i++) { - SCOPED_TRACE(i); - EXPECT_EQ(pointerProperties[i].id, motionEvent->getPointerId(i)); - EXPECT_EQ(pointerProperties[i].toolType, motionEvent->getToolType(i)); - - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X), - motionEvent->getRawX(i)); - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), - motionEvent->getRawY(i)); - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X) + xOffset, - motionEvent->getX(i)); - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y) + yOffset, - motionEvent->getY(i)); - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), - motionEvent->getPressure(i)); - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE), - motionEvent->getSize(i)); - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), - motionEvent->getTouchMajor(i)); - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), - motionEvent->getTouchMinor(i)); - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), - motionEvent->getToolMajor(i)); - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), - motionEvent->getToolMinor(i)); - EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), - motionEvent->getOrientation(i)); - } - - status = mConsumer->sendFinishedSignal(seq, false); - ASSERT_EQ(OK, status) - << "consumer sendFinishedSignal should return OK"; - - uint32_t finishedSeq = 0; - bool handled = true; - status = mPublisher->receiveFinishedSignal(&finishedSeq, &handled); - ASSERT_EQ(OK, status) - << "publisher receiveFinishedSignal should return OK"; - ASSERT_EQ(seq, finishedSeq) - << "publisher receiveFinishedSignal should have returned the original sequence number"; - ASSERT_FALSE(handled) - << "publisher receiveFinishedSignal should have set handled to consumer's reply"; -} - -TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) { - ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent()); -} - -TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) { - ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); -} - -TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) { - status_t status; - const size_t pointerCount = 0; - PointerProperties pointerProperties[pointerCount]; - PointerCoords pointerCoords[pointerCount]; - - status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - pointerCount, pointerProperties, pointerCoords); - ASSERT_EQ(BAD_VALUE, status) - << "publisher publishMotionEvent should return BAD_VALUE"; -} - -TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) { - status_t status; - const size_t pointerCount = MAX_POINTERS + 1; - PointerProperties pointerProperties[pointerCount]; - PointerCoords pointerCoords[pointerCount]; - for (size_t i = 0; i < pointerCount; i++) { - pointerProperties[i].clear(); - pointerCoords[i].clear(); - } - - status = mPublisher->publishMotionEvent(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - pointerCount, pointerProperties, pointerCoords); - ASSERT_EQ(BAD_VALUE, status) - << "publisher publishMotionEvent should return BAD_VALUE"; -} - -TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) { - ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); - ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent()); - ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); - ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent()); - ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent()); -} - -} // namespace android diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h deleted file mode 100644 index d8e985e..0000000 --- a/libs/androidfw/tests/TestHelpers.h +++ /dev/null @@ -1,79 +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 TESTHELPERS_H -#define TESTHELPERS_H - -#include <utils/threads.h> - -namespace android { - -class Pipe { -public: - int sendFd; - int receiveFd; - - Pipe() { - int fds[2]; - ::pipe(fds); - - receiveFd = fds[0]; - sendFd = fds[1]; - } - - ~Pipe() { - if (sendFd != -1) { - ::close(sendFd); - } - - if (receiveFd != -1) { - ::close(receiveFd); - } - } - - status_t writeSignal() { - ssize_t nWritten = ::write(sendFd, "*", 1); - return nWritten == 1 ? 0 : -errno; - } - - status_t readSignal() { - char buf[1]; - ssize_t nRead = ::read(receiveFd, buf, 1); - return nRead == 1 ? 0 : nRead == 0 ? -EPIPE : -errno; - } -}; - -class DelayedTask : public Thread { - int mDelayMillis; - -public: - DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { } - -protected: - virtual ~DelayedTask() { } - - virtual void doTask() = 0; - - virtual bool threadLoop() { - usleep(mDelayMillis * 1000); - doTask(); - return false; - } -}; - -} // namespace android - -#endif // TESTHELPERS_H diff --git a/libs/androidfw/tests/ZipFileRO_test.cpp b/libs/androidfw/tests/ZipFileRO_test.cpp new file mode 100644 index 0000000..cb9c721 --- /dev/null +++ b/libs/androidfw/tests/ZipFileRO_test.cpp @@ -0,0 +1,64 @@ +/* + * 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 "ZipFileRO_test" +#include <utils/Log.h> +#include <androidfw/ZipFileRO.h> + +#include <gtest/gtest.h> + +#include <fcntl.h> +#include <string.h> + +namespace android { + +class ZipFileROTest : public testing::Test { +protected: + virtual void SetUp() { + } + + virtual void TearDown() { + } +}; + +TEST_F(ZipFileROTest, ZipTimeConvertSuccess) { + struct tm t; + + // 2011-06-29 14:40:40 + long when = 0x3EDD7514; + + ZipFileRO::zipTimeToTimespec(when, &t); + + EXPECT_EQ(2011, t.tm_year + 1900) + << "Year was improperly converted."; + + EXPECT_EQ(6, t.tm_mon) + << "Month was improperly converted."; + + EXPECT_EQ(29, t.tm_mday) + << "Day was improperly converted."; + + EXPECT_EQ(14, t.tm_hour) + << "Hour was improperly converted."; + + EXPECT_EQ(40, t.tm_min) + << "Minute was improperly converted."; + + EXPECT_EQ(40, t.tm_sec) + << "Second was improperly converted."; +} + +} diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index a630ea1..411c133 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -10,6 +10,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) thread/TaskManager.cpp \ font/CacheTexture.cpp \ font/Font.cpp \ + AssetAtlas.cpp \ FontRenderer.cpp \ GammaFontRenderer.cpp \ Caches.cpp \ @@ -21,6 +22,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) Extensions.cpp \ FboCache.cpp \ GradientCache.cpp \ + Image.cpp \ Layer.cpp \ LayerCache.cpp \ LayerRenderer.cpp \ @@ -39,6 +41,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) SkiaShader.cpp \ Snapshot.cpp \ Stencil.cpp \ + Texture.cpp \ TextureCache.cpp \ TextDropShadowCache.cpp @@ -52,17 +55,26 @@ ifeq ($(USE_OPENGL_RENDERER),true) external/skia/include/images \ external/skia/src/core \ external/skia/src/ports \ - external/skia/include/utils \ - $(intermediates) \ - frameworks/rs/cpp \ - frameworks/rs + external/skia/include/utils - LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DGL_GLEXT_PROTOTYPES + LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES LOCAL_MODULE_CLASS := SHARED_LIBRARIES - LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libGLESv2 libskia libui libRS libRScpp + LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libEGL libGLESv2 libskia libui LOCAL_MODULE := libhwui LOCAL_MODULE_TAGS := optional + ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT)) + LOCAL_CFLAGS += -DANDROID_ENABLE_RENDERSCRIPT + LOCAL_SHARED_LIBRARIES += libRS libRScpp libstlport + LOCAL_C_INCLUDES += \ + $(intermediates) \ + frameworks/rs/cpp \ + frameworks/rs \ + external/stlport/stlport \ + bionic/ \ + bionic/libstdc++/include + endif + ifndef HWUI_COMPILE_SYMBOLS LOCAL_CFLAGS += -fvisibility=hidden endif diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp new file mode 100644 index 0000000..eb8bb9f --- /dev/null +++ b/libs/hwui/AssetAtlas.cpp @@ -0,0 +1,142 @@ +/* + * 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 "AssetAtlas.h" +#include "Caches.h" + +#include <GLES2/gl2ext.h> + +namespace android { +namespace uirenderer { + +/////////////////////////////////////////////////////////////////////////////// +// Lifecycle +/////////////////////////////////////////////////////////////////////////////// + +void AssetAtlas::init(sp<GraphicBuffer> buffer, int* map, int count) { + if (mImage) { + return; + } + + mImage = new Image(buffer); + + if (mImage->getTexture()) { + Caches& caches = Caches::getInstance(); + + mTexture = new Texture(caches); + mTexture->id = mImage->getTexture(); + mTexture->width = buffer->getWidth(); + mTexture->height = buffer->getHeight(); + + createEntries(caches, map, count); + } else { + ALOGW("Could not create atlas image"); + + delete mImage; + mImage = NULL; + mTexture = NULL; + } + + mGenerationId++; +} + +void AssetAtlas::terminate() { + if (mImage) { + delete mImage; + mImage = NULL; + + delete mTexture; + mTexture = NULL; + + for (size_t i = 0; i < mEntries.size(); i++) { + delete mEntries.valueAt(i); + } + mEntries.clear(); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Entries +/////////////////////////////////////////////////////////////////////////////// + +AssetAtlas::Entry* AssetAtlas::getEntry(SkBitmap* const bitmap) const { + ssize_t index = mEntries.indexOfKey(bitmap); + return index >= 0 ? mEntries.valueAt(index) : NULL; +} + +Texture* AssetAtlas::getEntryTexture(SkBitmap* const bitmap) const { + ssize_t index = mEntries.indexOfKey(bitmap); + return index >= 0 ? mEntries.valueAt(index)->texture : NULL; +} + +/** + * Delegates changes to wrapping and filtering to the base atlas texture + * instead of applying the changes to the virtual textures. + */ +struct DelegateTexture: public Texture { + DelegateTexture(Caches& caches, Texture* delegate): Texture(caches), mDelegate(delegate) { } + + virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, + bool force = false, GLenum renderTarget = GL_TEXTURE_2D) { + mDelegate->setWrapST(wrapS, wrapT, bindTexture, force, renderTarget); + } + + virtual void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false, + bool force = false, GLenum renderTarget = GL_TEXTURE_2D) { + mDelegate->setFilterMinMag(min, mag, bindTexture, force, renderTarget); + } + +private: + Texture* const mDelegate; +}; // struct DelegateTexture + +/** + * TODO: This method does not take the rotation flag into account + */ +void AssetAtlas::createEntries(Caches& caches, int* map, int count) { + const float width = float(mTexture->width); + const float height = float(mTexture->height); + + for (int i = 0; i < count; ) { + SkBitmap* bitmap = (SkBitmap*) map[i++]; + int x = map[i++]; + int y = map[i++]; + bool rotated = map[i++] > 0; + + // Bitmaps should never be null, we're just extra paranoid + if (!bitmap) continue; + + const UvMapper mapper( + x / width, (x + bitmap->width()) / width, + y / height, (y + bitmap->height()) / height); + + Texture* texture = new DelegateTexture(caches, mTexture); + texture->id = mTexture->id; + texture->blend = !bitmap->isOpaque(); + texture->width = bitmap->width(); + texture->height = bitmap->height(); + + Entry* entry = new Entry(bitmap, x, y, rotated, texture, mapper, *this); + texture->uvMapper = &entry->uvMapper; + + mEntries.add(entry->bitmap, entry); + } +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h new file mode 100644 index 0000000..a28efc6 --- /dev/null +++ b/libs/hwui/AssetAtlas.h @@ -0,0 +1,195 @@ +/* + * 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_ASSET_ATLAS_H +#define ANDROID_HWUI_ASSET_ATLAS_H + +#include <GLES2/gl2.h> + +#include <ui/GraphicBuffer.h> + +#include <utils/KeyedVector.h> + +#include <cutils/compiler.h> + +#include <SkBitmap.h> + +#include "Image.h" +#include "Texture.h" +#include "UvMapper.h" + +namespace android { +namespace uirenderer { + +class Caches; + +/** + * An asset atlas holds a collection of framework bitmaps in a single OpenGL + * texture. Each bitmap is associated with a location, defined in pixels, + * inside the atlas. The atlas is generated by the framework and bound as + * an external texture using the EGLImageKHR extension. + */ +class AssetAtlas { +public: + /** + * Entry representing the position and rotation of a + * bitmap inside the atlas. + */ + struct Entry { + /** + * The bitmap that generated this atlas entry. + */ + SkBitmap* bitmap; + + /** + * Location of the bitmap inside the atlas, in pixels. + */ + int x; + int y; + + /** + * If set, the bitmap is rotated 90 degrees (clockwise) + * inside the atlas. + */ + bool rotated; + + /* + * A "virtual texture" object that represents the texture + * this entry belongs to. This texture should never be + * modified. + */ + Texture* texture; + + /** + * Maps texture coordinates in the [0..1] range into the + * correct range to sample this entry from the atlas. + */ + const UvMapper uvMapper; + + /** + * Atlas this entry belongs to. + */ + const AssetAtlas& atlas; + + /** + * Unique identifier used to merge bitmaps and 9-patches stored + * in the atlas. + */ + const void* getMergeId() const { + return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey; + } + + private: + Entry(SkBitmap* bitmap, int x, int y, bool rotated, + Texture* texture, const UvMapper& mapper, const AssetAtlas& atlas): + bitmap(bitmap), x(x), y(y), rotated(rotated), + texture(texture), uvMapper(mapper), atlas(atlas) { + } + + ~Entry() { + delete texture; + } + + friend class AssetAtlas; + }; + + AssetAtlas(): mTexture(NULL), mImage(NULL), mGenerationId(0), + mBlendKey(true), mOpaqueKey(false) { } + ~AssetAtlas() { terminate(); } + + /** + * Initializes the atlas with the specified buffer and + * map. The buffer is a gralloc'd texture that will be + * used as an EGLImage. The map is a list of SkBitmap* + * and their (x, y) positions as well as their rotation + * flags. + * + * This method returns immediately if the atlas is already + * initialized. To re-initialize the atlas, you must + * first call terminate(). + */ + ANDROID_API void init(sp<GraphicBuffer> buffer, int* map, int count); + + /** + * Destroys the atlas texture. This object can be + * re-initialized after calling this method. + * + * After calling this method, the width, height + * and texture are set to 0. + */ + ANDROID_API void terminate(); + + /** + * Returns the width of this atlas in pixels. + * Can return 0 if the atlas is not initialized. + */ + uint32_t getWidth() const { + return mTexture ? mTexture->width : 0; + } + + /** + * Returns the height of this atlas in pixels. + * Can return 0 if the atlas is not initialized. + */ + uint32_t getHeight() const { + return mTexture ? mTexture->height : 0; + } + + /** + * Returns the OpenGL name of the texture backing this atlas. + * Can return 0 if the atlas is not initialized. + */ + GLuint getTexture() const { + return mTexture ? mTexture->id : 0; + } + + /** + * 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; + + /** + * 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; + + /** + * Returns the current generation id of the atlas. + */ + uint32_t getGenerationId() const { + return mGenerationId; + } + +private: + void createEntries(Caches& caches, int* map, int count); + + Texture* mTexture; + Image* mImage; + + uint32_t mGenerationId; + + const bool mBlendKey; + const bool mOpaqueKey; + + KeyedVector<SkBitmap*, Entry*> mEntries; +}; // class AssetAtlas + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_ASSET_ATLAS_H diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index a381a68..f8d3589 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -47,19 +47,21 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -Caches::Caches(): Singleton<Caches>(), mExtensions(Extensions::getInstance()), mInitialized(false) { +Caches::Caches(): Singleton<Caches>(), + mExtensions(Extensions::getInstance()), mInitialized(false) { init(); initFont(); initConstraints(); initProperties(); + initStaticProperties(); initExtensions(); mDebugLevel = readDebugLevel(); ALOGD("Enabling debug mode %d", mDebugLevel); } -void Caches::init() { - if (mInitialized) return; +bool Caches::init() { + if (mInitialized) return false; glGenBuffers(1, &meshBuffer); glBindBuffer(GL_ARRAY_BUFFER, meshBuffer); @@ -82,6 +84,7 @@ void Caches::init() { mTextureUnit = 0; mRegionMesh = NULL; + mMeshIndices = 0; blend = false; lastSrcMode = GL_ZERO; @@ -94,7 +97,13 @@ void Caches::init() { debugOverdraw = false; debugStencilClip = kStencilHide; + patchCache.init(*this); + mInitialized = true; + + resetBoundTextures(); + + return true; } void Caches::initFont() { @@ -132,6 +141,18 @@ void Caches::initConstraints() { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); } +void Caches::initStaticProperties() { + gpuPixelBuffersEnabled = false; + + // OpenGL ES 3.0+ specific features + if (mExtensions.hasPixelBufferObjects()) { + char property[PROPERTY_VALUE_MAX]; + if (property_get(PROPERTY_ENABLE_GPU_PIXEL_BUFFERS, property, "true") > 0) { + gpuPixelBuffersEnabled = !strcmp(property, "true"); + } + } +} + bool Caches::initProperties() { bool prevDebugLayersUpdates = debugLayersUpdates; bool prevDebugOverdraw = debugOverdraw; @@ -145,11 +166,16 @@ bool Caches::initProperties() { debugLayersUpdates = false; } + debugOverdraw = false; if (property_get(PROPERTY_DEBUG_OVERDRAW, property, NULL) > 0) { INIT_LOGD(" Overdraw debug enabled: %s", property); - debugOverdraw = !strcmp(property, "true"); - } else { - debugOverdraw = false; + if (!strcmp(property, "show")) { + debugOverdraw = true; + mOverdrawDebugColorSet = kColorSet_Default; + } else if (!strcmp(property, "show_deuteranomaly")) { + debugOverdraw = true; + mOverdrawDebugColorSet = kColorSet_Deuteranomaly; + } } // See Properties.h for valid values @@ -191,8 +217,9 @@ void Caches::terminate() { glDeleteBuffers(1, &meshBuffer); mCurrentBuffer = 0; - glDeleteBuffers(1, &mRegionMeshIndices); + glDeleteBuffers(1, &mMeshIndices); delete[] mRegionMesh; + mMeshIndices = 0; mRegionMesh = NULL; fboCache.clear(); @@ -200,6 +227,12 @@ void Caches::terminate() { programCache.clear(); currentProgram = NULL; + assetAtlas.terminate(); + + patchCache.clear(); + + clearGarbage(); + mInitialized = false; } @@ -207,6 +240,16 @@ void Caches::terminate() { // Debug /////////////////////////////////////////////////////////////////////////////// +uint32_t Caches::getOverdrawColor(uint32_t amount) const { + static uint32_t sOverdrawColors[2][4] = { + { 0x2f0000ff, 0x2f00ff00, 0x3fff0000, 0x7fff0000 }, + { 0x2f0000ff, 0x4fffff00, 0x5fff8ad8, 0x7fff0000 } + }; + if (amount < 1) amount = 1; + if (amount > 4) amount = 4; + return sOverdrawColors[mOverdrawDebugColorSet][amount - 1]; +} + void Caches::dumpMemoryUsage() { String8 stringLog; dumpMemoryUsage(stringLog); @@ -227,15 +270,19 @@ void Caches::dumpMemoryUsage(String8 &log) { pathCache.getSize(), pathCache.getMaxSize()); log.appendFormat(" TextDropShadowCache %8d / %8d\n", dropShadowCache.getSize(), dropShadowCache.getMaxSize()); + log.appendFormat(" PatchCache %8d / %8d\n", + patchCache.getSize(), patchCache.getMaxSize()); for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) { - const uint32_t size = fontRenderer->getFontRendererSize(i); - log.appendFormat(" FontRenderer %d %8d / %8d\n", i, size, size); + const uint32_t sizeA8 = fontRenderer->getFontRendererSize(i, GL_ALPHA); + const uint32_t sizeRGBA = fontRenderer->getFontRendererSize(i, GL_RGBA); + log.appendFormat(" FontRenderer %d A8 %8d / %8d\n", i, sizeA8, sizeA8); + log.appendFormat(" FontRenderer %d RGBA %8d / %8d\n", i, sizeRGBA, sizeRGBA); + log.appendFormat(" FontRenderer %d total %8d / %8d\n", i, sizeA8 + sizeRGBA, + sizeA8 + sizeRGBA); } log.appendFormat("Other:\n"); log.appendFormat(" FboCache %8d / %8d\n", fboCache.getSize(), fboCache.getMaxSize()); - log.appendFormat(" PatchCache %8d / %8d\n", - patchCache.getSize(), patchCache.getMaxSize()); uint32_t total = 0; total += textureCache.getSize(); @@ -244,8 +291,10 @@ void Caches::dumpMemoryUsage(String8 &log) { total += gradientCache.getSize(); total += pathCache.getSize(); total += dropShadowCache.getSize(); + total += patchCache.getSize(); for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) { - total += fontRenderer->getFontRendererSize(i); + total += fontRenderer->getFontRendererSize(i, GL_ALPHA); + total += fontRenderer->getFontRendererSize(i, GL_RGBA); } log.appendFormat("Total memory usage:\n"); @@ -259,6 +308,7 @@ void Caches::dumpMemoryUsage(String8 &log) { void Caches::clearGarbage() { textureCache.clearGarbage(); pathCache.clearGarbage(); + patchCache.clearGarbage(); Vector<DisplayList*> displayLists; Vector<Layer*> layers; @@ -298,6 +348,11 @@ void Caches::deleteDisplayListDeferred(DisplayList* displayList) { void Caches::flush(FlushMode mode) { FLUSH_LOGD("Flushing caches (mode %d)", mode); + // We must stop tasks before clearing caches + if (mode > kFlushMode_Layers) { + tasks.stop(); + } + switch (mode) { case kFlushMode_Full: textureCache.clear(); @@ -305,13 +360,13 @@ void Caches::flush(FlushMode mode) { dropShadowCache.clear(); gradientCache.clear(); fontRenderer->clear(); + fboCache.clear(); dither.clear(); // fall through case kFlushMode_Moderate: fontRenderer->flush(); textureCache.flush(); pathCache.clear(); - tasks.stop(); // fall through case kFlushMode_Layers: layerCache.clear(); @@ -357,6 +412,32 @@ bool Caches::bindIndicesBuffer(const GLuint buffer) { return false; } +bool Caches::bindIndicesBuffer() { + if (!mMeshIndices) { + uint16_t* regionIndices = new uint16_t[gMaxNumberOfQuads * 6]; + for (uint32_t i = 0; i < gMaxNumberOfQuads; i++) { + uint16_t quad = i * 4; + int index = i * 6; + regionIndices[index ] = quad; // top-left + regionIndices[index + 1] = quad + 1; // top-right + regionIndices[index + 2] = quad + 2; // bottom-left + regionIndices[index + 3] = quad + 2; // bottom-left + regionIndices[index + 4] = quad + 1; // top-right + regionIndices[index + 5] = quad + 3; // bottom-right + } + + glGenBuffers(1, &mMeshIndices); + bool force = bindIndicesBuffer(mMeshIndices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, gMaxNumberOfQuads * 6 * sizeof(uint16_t), + regionIndices, GL_STATIC_DRAW); + + delete[] regionIndices; + return force; + } + + return bindIndicesBuffer(mMeshIndices); +} + bool Caches::unbindIndicesBuffer() { if (mCurrentIndicesBuffer) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -441,6 +522,50 @@ void Caches::activeTexture(GLuint textureUnit) { } } +void Caches::resetActiveTexture() { + mTextureUnit = -1; +} + +void Caches::bindTexture(GLuint texture) { + if (mBoundTextures[mTextureUnit] != texture) { + glBindTexture(GL_TEXTURE_2D, texture); + mBoundTextures[mTextureUnit] = texture; + } +} + +void Caches::bindTexture(GLenum target, GLuint texture) { + if (mBoundTextures[mTextureUnit] != texture) { + glBindTexture(target, texture); + mBoundTextures[mTextureUnit] = texture; + } +} + +void Caches::deleteTexture(GLuint texture) { + // When glDeleteTextures() is called on a currently bound texture, + // OpenGL ES specifies that the texture is then considered unbound + // Consider the following series of calls: + // + // glGenTextures -> creates texture name 2 + // glBindTexture(2) + // glDeleteTextures(2) -> 2 is now unbound + // glGenTextures -> can return 2 again + // + // If we don't call glBindTexture(2) after the second glGenTextures + // call, any texture operation will be performed on the default + // texture (name=0) + + for (int i = 0; i < REQUIRED_TEXTURE_UNITS_COUNT; i++) { + if (mBoundTextures[i] == texture) { + mBoundTextures[i] = 0; + } + } + glDeleteTextures(1, &texture); +} + +void Caches::resetBoundTextures() { + memset(mBoundTextures, 0, REQUIRED_TEXTURE_UNITS_COUNT * sizeof(GLuint)); +} + /////////////////////////////////////////////////////////////////////////////// // Scissor /////////////////////////////////////////////////////////////////////////////// @@ -545,28 +670,7 @@ void Caches::unregisterFunctors(uint32_t functorCount) { TextureVertex* Caches::getRegionMesh() { // Create the mesh, 2 triangles and 4 vertices per rectangle in the region if (!mRegionMesh) { - mRegionMesh = new TextureVertex[REGION_MESH_QUAD_COUNT * 4]; - - uint16_t* regionIndices = new uint16_t[REGION_MESH_QUAD_COUNT * 6]; - for (int i = 0; i < REGION_MESH_QUAD_COUNT; i++) { - uint16_t quad = i * 4; - int index = i * 6; - regionIndices[index ] = quad; // top-left - regionIndices[index + 1] = quad + 1; // top-right - regionIndices[index + 2] = quad + 2; // bottom-left - regionIndices[index + 3] = quad + 2; // bottom-left - regionIndices[index + 4] = quad + 1; // top-right - regionIndices[index + 5] = quad + 3; // bottom-right - } - - glGenBuffers(1, &mRegionMeshIndices); - bindIndicesBuffer(mRegionMeshIndices); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, REGION_MESH_QUAD_COUNT * 6 * sizeof(uint16_t), - regionIndices, GL_STATIC_DRAW); - - delete[] regionIndices; - } else { - bindIndicesBuffer(mRegionMeshIndices); + mRegionMesh = new TextureVertex[gMaxNumberOfQuads * 4]; } return mRegionMesh; diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 91b938b..282aee9 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -21,13 +21,18 @@ #define LOG_TAG "OpenGLRenderer" #endif +#include <GLES3/gl3.h> + +#include <utils/KeyedVector.h> #include <utils/Singleton.h> +#include <utils/Vector.h> #include <cutils/compiler.h> #include "thread/TaskProcessor.h" #include "thread/TaskManager.h" +#include "AssetAtlas.h" #include "FontRenderer.h" #include "GammaFontRenderer.h" #include "TextureCache.h" @@ -50,9 +55,11 @@ namespace uirenderer { // Globals /////////////////////////////////////////////////////////////////////////////// +// GL ES 2.0 defines that at least 16 texture units must be supported #define REQUIRED_TEXTURE_UNITS_COUNT 3 -#define REGION_MESH_QUAD_COUNT 512 +// Maximum number of quads that pre-allocated meshes can draw +static const uint32_t gMaxNumberOfQuads = 2048; // Generates simple and textured vertices #define FV(x, y, u, v) { { x, y }, { u, v } } @@ -74,6 +81,7 @@ static const GLsizei gVertexAAWidthOffset = 2 * sizeof(float); static const GLsizei gVertexAALengthOffset = 3 * sizeof(float); static const GLsizei gMeshCount = 4; +// Must define as many texture units as specified by REQUIRED_TEXTURE_UNITS_COUNT static const GLenum gTextureUnits[] = { GL_TEXTURE0, GL_TEXTURE1, @@ -113,7 +121,7 @@ public: /** * Initialize caches. */ - void init(); + bool init(); /** * Initialize global system properties. @@ -142,6 +150,12 @@ public: } /** + * Returns a non-premultiplied ARGB color for the specified + * amount of overdraw (1 for 1x, 2 for 2x, etc.) + */ + uint32_t getOverdrawColor(uint32_t amount) const; + + /** * Call this on each frame to ensure that garbage is deleted from * GPU memory. */ @@ -172,6 +186,11 @@ public: */ bool unbindMeshBuffer(); + /** + * Binds a global indices buffer that can draw up to + * gMaxNumberOfQuads quads. + */ + bool bindIndicesBuffer(); bool bindIndicesBuffer(const GLuint buffer); bool unbindIndicesBuffer(); @@ -213,6 +232,38 @@ public: void activeTexture(GLuint textureUnit); /** + * Invalidate the cached value of the active texture unit. + */ + void resetActiveTexture(); + + /** + * Binds the specified texture as a GL_TEXTURE_2D texture. + * All texture bindings must be performed with this method or + * bindTexture(GLenum, GLuint). + */ + void bindTexture(GLuint texture); + + /** + * Binds the specified texture with the specified render target. + * All texture bindings must be performed with this method or + * bindTexture(GLuint). + */ + void bindTexture(GLenum target, GLuint texture); + + /** + * Deletes the specified texture and clears it from the cache + * of bound textures. + * All textures must be deleted using this method. + */ + void deleteTexture(GLuint texture); + + /** + * Signals that the cache of bound textures should be cleared. + * Other users of the context may have altered which textures are bound. + */ + void resetBoundTextures(); + + /** * Sets the scissor for the current surface. */ bool setScissor(GLint x, GLint y, GLint width, GLint height); @@ -290,6 +341,10 @@ public: Dither dither; Stencil stencil; + AssetAtlas assetAtlas; + + bool gpuPixelBuffersEnabled; + // Debug methods PFNGLINSERTEVENTMARKEREXTPROC eventMark; PFNGLPUSHGROUPMARKEREXTPROC startMark; @@ -299,9 +354,15 @@ public: PFNGLGETOBJECTLABELEXTPROC getLabel; private: + enum OverdrawColorSet { + kColorSet_Default = 0, + kColorSet_Deuteranomaly + }; + void initFont(); void initExtensions(); void initConstraints(); + void initStaticProperties(); static void eventMarkNull(GLsizei length, const GLchar* marker) { } static void startMarkNull(GLsizei length, const GLchar* marker) { } @@ -336,7 +397,9 @@ private: // Used to render layers TextureVertex* mRegionMesh; - GLuint mRegionMeshIndices; + + // Global index buffer + GLuint mMeshIndices; mutable Mutex mGarbageLock; Vector<Layer*> mLayerGarbage; @@ -346,6 +409,10 @@ private: bool mInitialized; uint32_t mFunctorsCount; + + GLuint mBoundTextures[REQUIRED_TEXTURE_UNITS_COUNT]; + + OverdrawColorSet mOverdrawDebugColorSet; }; // class Caches }; // namespace uirenderer diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h index 790c4f4..786f12a 100644 --- a/libs/hwui/Debug.h +++ b/libs/hwui/Debug.h @@ -53,8 +53,6 @@ // Turn on to display debug info about 9patch objects #define DEBUG_PATCHES 0 -// Turn on to "explode" 9patch objects -#define DEBUG_EXPLODE_PATCHES 0 // Turn on to display vertex and tex coords data about 9patch objects // This flag requires DEBUG_PATCHES to be turned on #define DEBUG_PATCHES_VERTICES 0 diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp index 9323a3a..7eb7028 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -20,6 +20,8 @@ #include <SkCanvas.h> #include <utils/Trace.h> +#include <ui/Rect.h> +#include <ui/Region.h> #include "Caches.h" #include "Debug.h" @@ -51,32 +53,36 @@ class Batch { public: virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) = 0; virtual ~Batch() {} + virtual bool purelyDrawBatch() { return false; } + virtual bool coversBounds(const Rect& bounds) { return false; } }; class DrawBatch : public Batch { public: - DrawBatch(int batchId, mergeid_t mergeId) : mBatchId(batchId), mMergeId(mergeId) { + DrawBatch(const DeferInfo& deferInfo) : mAllOpsOpaque(true), + mBatchId(deferInfo.batchId), mMergeId(deferInfo.mergeId) { mOps.clear(); } virtual ~DrawBatch() { mOps.clear(); } - void add(DrawOp* op) { + virtual void add(DrawOp* op, const DeferredDisplayState* state, bool opaqueOverBounds) { // NOTE: ignore empty bounds special case, since we don't merge across those ops - mBounds.unionWith(op->state.mBounds); - mOps.add(op); + mBounds.unionWith(state->mBounds); + mAllOpsOpaque &= opaqueOverBounds; + mOps.add(OpStatePair(op, state)); } - bool intersects(Rect& rect) { + bool intersects(const Rect& rect) { if (!rect.intersects(mBounds)) return false; for (unsigned int i = 0; i < mOps.size(); i++) { - if (rect.intersects(mOps[i]->state.mBounds)) { + if (rect.intersects(mOps[i].state->mBounds)) { #if DEBUG_DEFER - DEFER_LOGD("op intersects with op %p with bounds %f %f %f %f:", mOps[i], - mOps[i]->state.mBounds.left, mOps[i]->state.mBounds.top, - mOps[i]->state.mBounds.right, mOps[i]->state.mBounds.bottom); - mOps[i]->output(2); + DEFER_LOGD("op intersects with op %p with bounds %f %f %f %f:", mOps[i].op, + mOps[i].state->mBounds.left, mOps[i].state->mBounds.top, + mOps[i].state->mBounds.right, mOps[i].state->mBounds.bottom); + mOps[i].op->output(2); #endif return true; } @@ -85,15 +91,15 @@ public: } virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { - DEFER_LOGD("%d replaying DrawingBatch %p, with %d ops (batch id %x, merge id %p)", - index, this, mOps.size(), mOps[0]->getBatchId(), mOps[0]->getMergeId()); + DEFER_LOGD("%d replaying DrawBatch %p, with %d ops (batch id %x, merge id %p)", + index, this, mOps.size(), getBatchId(), getMergeId()); status_t status = DrawGlInfo::kStatusDone; DisplayListLogBuffer& logBuffer = DisplayListLogBuffer::getInstance(); for (unsigned int i = 0; i < mOps.size(); i++) { - DrawOp* op = mOps[i]; - - renderer.restoreDisplayState(op->state); + DrawOp* op = mOps[i].op; + const DeferredDisplayState* state = mOps[i].state; + renderer.restoreDisplayState(*state); #if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS renderer.eventMark(op->name()); @@ -102,7 +108,7 @@ public: status |= op->applyDraw(renderer, dirty); #if DEBUG_MERGE_BEHAVIOR - Rect& bounds = mOps[i]->state.mBounds; + const Rect& bounds = state->mBounds; int batchColor = 0x1f000000; if (getBatchId() & 0x1) batchColor |= 0x0000ff; if (getBatchId() & 0x2) batchColor |= 0x00ff00; @@ -114,14 +120,28 @@ public: return status; } + virtual bool purelyDrawBatch() { return true; } + + virtual bool coversBounds(const Rect& bounds) { + if (CC_LIKELY(!mAllOpsOpaque || !mBounds.contains(bounds) || count() == 1)) return false; + + Region uncovered(android::Rect(bounds.left, bounds.top, bounds.right, bounds.bottom)); + for (unsigned int i = 0; i < mOps.size(); i++) { + const Rect &r = mOps[i].state->mBounds; + uncovered.subtractSelf(android::Rect(r.left, r.top, r.right, r.bottom)); + } + return uncovered.isEmpty(); + } + inline int getBatchId() const { return mBatchId; } inline mergeid_t getMergeId() const { return mMergeId; } inline int count() const { return mOps.size(); } protected: - Vector<DrawOp*> mOps; - Rect mBounds; + Vector<OpStatePair> mOps; + Rect mBounds; // union of bounds of contained ops private: + bool mAllOpsOpaque; int mBatchId; mergeid_t mMergeId; }; @@ -132,39 +152,77 @@ private: class MergingDrawBatch : public DrawBatch { public: - MergingDrawBatch(int batchId, mergeid_t mergeId) : DrawBatch(batchId, mergeId) {} + MergingDrawBatch(DeferInfo& deferInfo, int width, int height) : + DrawBatch(deferInfo), mClipRect(width, height), + mClipSideFlags(kClipSide_None) {} + + /* + * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds + * and clip side flags. Positive bounds delta means new bounds fit in old. + */ + static inline bool checkSide(const int currentFlags, const int newFlags, const int side, + float boundsDelta) { + bool currentClipExists = currentFlags & side; + bool newClipExists = newFlags & side; + + // if current is clipped, we must be able to fit new bounds in current + if (boundsDelta > 0 && currentClipExists) return false; + + // if new is clipped, we must be able to fit current bounds in new + if (boundsDelta < 0 && newClipExists) return false; + + return true; + } /* * Checks if a (mergeable) op can be merged into this batch * * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is * important to consider all paint attributes used in the draw calls in deciding both a) if an - * op tries to merge at all, and b) if the op + * op tries to merge at all, and b) if the op can merge with another set of ops * * False positives can lead to information from the paints of subsequent merged operations being * dropped, so we make simplifying qualifications on the ops that can merge, per op type. */ - bool canMergeWith(DrawOp* op) { - if (!op->state.mMatrix.isPureTranslate()) return false; - + bool canMergeWith(const DrawOp* op, const DeferredDisplayState* state) { bool isTextBatch = getBatchId() == DeferredDisplayList::kOpBatch_Text || getBatchId() == DeferredDisplayList::kOpBatch_ColorText; // Overlapping other operations is only allowed for text without shadow. For other ops, // multiDraw isn't guaranteed to overdraw correctly - if (!isTextBatch || op->state.mDrawModifiers.mHasShadow) { - if (intersects(op->state.mBounds)) return false; + if (!isTextBatch || state->mDrawModifiers.mHasShadow) { + if (intersects(state->mBounds)) return false; } + const DeferredDisplayState* lhs = state; + const DeferredDisplayState* rhs = mOps[0].state; - const DeferredDisplayState& lhs = op->state; - const DeferredDisplayState& rhs = mOps[0]->state; + if (NEQ_FALPHA(lhs->mAlpha, rhs->mAlpha)) return false; - if (NEQ_FALPHA(lhs.mAlpha, rhs.mAlpha)) return false; + /* Clipping compatibility check + * + * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its + * clip for that side. + */ + const int currentFlags = mClipSideFlags; + const int newFlags = state->mClipSideFlags; + if (currentFlags != kClipSide_None || newFlags != kClipSide_None) { + const Rect& opBounds = state->mBounds; + float boundsDelta = mBounds.left - opBounds.left; + if (!checkSide(currentFlags, newFlags, kClipSide_Left, boundsDelta)) return false; + boundsDelta = mBounds.top - opBounds.top; + if (!checkSide(currentFlags, newFlags, kClipSide_Top, boundsDelta)) return false; + + // right and bottom delta calculation reversed to account for direction + boundsDelta = opBounds.right - mBounds.right; + if (!checkSide(currentFlags, newFlags, kClipSide_Right, boundsDelta)) return false; + boundsDelta = opBounds.bottom - mBounds.bottom; + if (!checkSide(currentFlags, newFlags, kClipSide_Bottom, boundsDelta)) return false; + } // if paints are equal, then modifiers + paint attribs don't need to be compared - if (op->mPaint == mOps[0]->mPaint) return true; + if (op->mPaint == mOps[0].op->mPaint) return true; - if (op->getPaintAlpha() != mOps[0]->getPaintAlpha()) return false; + if (op->getPaintAlpha() != mOps[0].op->getPaintAlpha()) return false; /* Draw Modifiers compatibility check * @@ -178,9 +236,8 @@ public: * * These ignore cases prevent us from simply memcmp'ing the drawModifiers */ - - const DrawModifiers& lhsMod = lhs.mDrawModifiers; - const DrawModifiers& rhsMod = rhs.mDrawModifiers; + const DrawModifiers& lhsMod = lhs->mDrawModifiers; + const DrawModifiers& rhsMod = rhs->mDrawModifiers; if (lhsMod.mShader != rhsMod.mShader) return false; if (lhsMod.mColorFilter != rhsMod.mColorFilter) return false; @@ -192,17 +249,37 @@ public: return true; } + virtual void add(DrawOp* op, const DeferredDisplayState* state, bool opaqueOverBounds) { + DrawBatch::add(op, state, opaqueOverBounds); + + const int newClipSideFlags = state->mClipSideFlags; + mClipSideFlags |= newClipSideFlags; + if (newClipSideFlags & kClipSide_Left) mClipRect.left = state->mClip.left; + if (newClipSideFlags & kClipSide_Top) mClipRect.top = state->mClip.top; + if (newClipSideFlags & kClipSide_Right) mClipRect.right = state->mClip.right; + if (newClipSideFlags & kClipSide_Bottom) mClipRect.bottom = state->mClip.bottom; + } + virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { - DEFER_LOGD("%d replaying DrawingBatch %p, with %d ops (batch id %x, merge id %p)", - index, this, mOps.size(), getBatchId(), getMergeId()); + DEFER_LOGD("%d replaying MergingDrawBatch %p, with %d ops," + " clip flags %x (batch id %x, merge id %p)", + index, this, mOps.size(), mClipSideFlags, getBatchId(), getMergeId()); if (mOps.size() == 1) { - return DrawBatch::replay(renderer, dirty, false); + return DrawBatch::replay(renderer, dirty, -1); } - DrawOp* op = mOps[0]; + // clipping in the merged case is done ahead of time since all ops share the clip (if any) + renderer.setupMergedMultiDraw(mClipSideFlags ? &mClipRect : NULL); + + DrawOp* op = mOps[0].op; DisplayListLogBuffer& buffer = DisplayListLogBuffer::getInstance(); buffer.writeCommand(0, "multiDraw"); buffer.writeCommand(1, op->name()); + +#if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS + renderer.eventMark("multiDraw"); + renderer.eventMark(op->name()); +#endif status_t status = op->multiDraw(renderer, dirty, mOps, mBounds); #if DEBUG_MERGE_BEHAVIOR @@ -211,16 +288,25 @@ public: #endif return status; } + +private: + /* + * Contains the effective clip rect shared by all merged ops. Initialized to the layer viewport, + * it will shrink if an op must be clipped on a certain side. The clipped sides are reflected in + * mClipSideFlags. + */ + Rect mClipRect; + int mClipSideFlags; }; class StateOpBatch : public Batch { public: // creates a single operation batch - StateOpBatch(StateOp* op) : mOp(op) {} + StateOpBatch(const StateOp* op, const DeferredDisplayState* state) : mOp(op), mState(state) {} virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { DEFER_LOGD("replaying state op batch %p", this); - renderer.restoreDisplayState(mOp->state); + renderer.restoreDisplayState(*mState); // use invalid save count because it won't be used at flush time - RestoreToCountOp is the // only one to use it, and we don't use that class at flush time, instead calling @@ -232,16 +318,18 @@ public: private: const StateOp* mOp; + const DeferredDisplayState* mState; }; class RestoreToCountBatch : public Batch { public: - RestoreToCountBatch(StateOp* op, int restoreCount) : mOp(op), mRestoreCount(restoreCount) {} + RestoreToCountBatch(const StateOp* op, const DeferredDisplayState* state, int restoreCount) : + mOp(op), mState(state), mRestoreCount(restoreCount) {} virtual status_t replay(OpenGLRenderer& renderer, Rect& dirty, int index) { DEFER_LOGD("batch %p restoring to count %d", this, mRestoreCount); - renderer.restoreDisplayState(mOp->state); + renderer.restoreDisplayState(*mState); renderer.restoreToCount(mRestoreCount); return DrawGlInfo::kStatusDone; } @@ -249,6 +337,8 @@ public: private: // we use the state storage for the RestoreToCountOp, but don't replay the op itself const StateOp* mOp; + const DeferredDisplayState* mState; + /* * The count used here represents the flush() time saveCount. This is as opposed to the * DisplayList record time, or defer() time values (which are RestoreToCountOp's mCount, and @@ -294,6 +384,7 @@ void DeferredDisplayList::clear() { mBatches.clear(); mSaveStack.clear(); mEarliestBatchIndex = 0; + mEarliestUnclearedIndex = 0; } ///////////////////////////////////////////////////////////////////////////////// @@ -398,22 +489,45 @@ void DeferredDisplayList::addRestoreToCount(OpenGLRenderer& renderer, StateOp* o } void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { - if (renderer.storeDisplayState(op->state, getDrawOpDeferFlags())) { + /* 1: op calculates local bounds */ + DeferredDisplayState* const state = createState(); + if (op->getLocalBounds(renderer.getDrawModifiers(), state->mBounds)) { + if (state->mBounds.isEmpty()) { + // valid empty bounds, don't bother deferring + tryRecycleState(state); + return; + } + } else { + state->mBounds.setEmpty(); + } + + /* 2: renderer calculates global bounds + stores state */ + if (renderer.storeDisplayState(*state, getDrawOpDeferFlags())) { + tryRecycleState(state); return; // quick rejected } - int batchId = kOpBatch_None; - mergeid_t mergeId = (mergeid_t) -1; - bool mergeable = op->onDefer(renderer, &batchId, &mergeId); + /* 3: ask op for defer info, given renderer state */ + DeferInfo deferInfo; + op->onDefer(renderer, deferInfo, *state); // complex clip has a complex set of expectations on the renderer state - for now, avoid taking // the merge path in those cases - mergeable &= !recordingComplexClip(); + deferInfo.mergeable &= !recordingComplexClip(); + deferInfo.opaqueOverBounds &= !recordingComplexClip() && mSaveStack.isEmpty(); + + if (CC_LIKELY(mAvoidOverdraw) && mBatches.size() && + state->mClipSideFlags != kClipSide_ConservativeFull && + deferInfo.opaqueOverBounds && state->mBounds.contains(mBounds)) { + // avoid overdraw by resetting drawing state + discarding drawing ops + discardDrawingBatches(mBatches.size() - 1); + resetBatchingState(); + } if (CC_UNLIKELY(renderer.getCaches().drawReorderDisabled)) { // TODO: elegant way to reuse batches? - DrawBatch* b = new DrawBatch(batchId, mergeId); - b->add(op); + DrawBatch* b = new DrawBatch(deferInfo); + b->add(op, state, deferInfo.opaqueOverBounds); mBatches.add(b); return; } @@ -425,10 +539,10 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { // (eventually, should be similar shader) int insertBatchIndex = mBatches.size(); if (!mBatches.isEmpty()) { - if (op->state.mBounds.isEmpty()) { + if (state->mBounds.isEmpty()) { // don't know the bounds for op, so add to last batch and start from scratch on next op - DrawBatch* b = new DrawBatch(batchId, mergeId); - b->add(op); + DrawBatch* b = new DrawBatch(deferInfo); + b->add(op, state, deferInfo.opaqueOverBounds); mBatches.add(b); resetBatchingState(); #if DEBUG_DEFER @@ -438,19 +552,19 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { return; } - if (mergeable) { + if (deferInfo.mergeable) { // Try to merge with any existing batch with same mergeId. - if (mMergingBatches[batchId].get(mergeId, targetBatch)) { - if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op)) { + if (mMergingBatches[deferInfo.batchId].get(deferInfo.mergeId, targetBatch)) { + if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op, state)) { targetBatch = NULL; } } } else { // join with similar, non-merging batch - targetBatch = (DrawBatch*)mBatchLookup[batchId]; + targetBatch = (DrawBatch*)mBatchLookup[deferInfo.batchId]; } - if (targetBatch || mergeable) { + if (targetBatch || deferInfo.mergeable) { // iterate back toward target to see if anything drawn since should overlap the new op // if no target, merging ops still interate to find similar batch to insert after for (int i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) { @@ -459,19 +573,19 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { if (overBatch == targetBatch) break; // TODO: also consider shader shared between batch types - if (batchId == overBatch->getBatchId()) { + if (deferInfo.batchId == overBatch->getBatchId()) { insertBatchIndex = i + 1; if (!targetBatch) break; // found insert position, quit } - if (overBatch->intersects(op->state.mBounds)) { + if (overBatch->intersects(state->mBounds)) { // NOTE: it may be possible to optimize for special cases where two operations // of the same batch/paint could swap order, such as with a non-mergeable // (clipped) and a mergeable text operation targetBatch = NULL; #if DEBUG_DEFER - DEFER_LOGD("op couldn't join batch %d, was intersected by batch %d", - targetIndex, i); + DEFER_LOGD("op couldn't join batch %p, was intersected by batch %d", + targetBatch, i); op->output(2); #endif break; @@ -481,27 +595,30 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { } if (!targetBatch) { - if (mergeable) { - targetBatch = new MergingDrawBatch(batchId, mergeId); - mMergingBatches[batchId].put(mergeId, targetBatch); + if (deferInfo.mergeable) { + targetBatch = new MergingDrawBatch(deferInfo, + renderer.getViewportWidth(), renderer.getViewportHeight()); + mMergingBatches[deferInfo.batchId].put(deferInfo.mergeId, targetBatch); } else { - targetBatch = new DrawBatch(batchId, mergeId); - mBatchLookup[batchId] = targetBatch; - DEFER_LOGD("creating Batch %p, bid %x, at %d", - targetBatch, batchId, insertBatchIndex); + targetBatch = new DrawBatch(deferInfo); + mBatchLookup[deferInfo.batchId] = targetBatch; } + DEFER_LOGD("creating %singBatch %p, bid %x, at %d", + deferInfo.mergeable ? "Merg" : "Draw", + targetBatch, deferInfo.batchId, insertBatchIndex); mBatches.insertAt(targetBatch, insertBatchIndex); } - targetBatch->add(op); + targetBatch->add(op, state, deferInfo.opaqueOverBounds); } void DeferredDisplayList::storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* op) { DEFER_LOGD("%p adding state op barrier at pos %d", this, mBatches.size()); - renderer.storeDisplayState(op->state, getStateOpDeferFlags()); - mBatches.add(new StateOpBatch(op)); + DeferredDisplayState* state = createState(); + renderer.storeDisplayState(*state, getStateOpDeferFlags()); + mBatches.add(new StateOpBatch(op, state)); resetBatchingState(); } @@ -512,8 +629,9 @@ void DeferredDisplayList::storeRestoreToCountBarrier(OpenGLRenderer& renderer, S // store displayState for the restore operation, as it may be associated with a saveLayer that // doesn't have kClip_SaveFlag set - renderer.storeDisplayState(op->state, getStateOpDeferFlags()); - mBatches.add(new RestoreToCountBatch(op, newSaveCount)); + DeferredDisplayState* state = createState(); + renderer.storeDisplayState(*state, getStateOpDeferFlags()); + mBatches.add(new RestoreToCountBatch(op, state, newSaveCount)); resetBatchingState(); } @@ -526,7 +644,9 @@ static status_t replayBatchList(const Vector<Batch*>& batchList, status_t status = DrawGlInfo::kStatusDone; for (unsigned int i = 0; i < batchList.size(); i++) { - status |= batchList[i]->replay(renderer, dirty, i); + if (batchList[i]) { + status |= batchList[i]->replay(renderer, dirty, i); + } } DEFER_LOGD("--flushed, drew %d batches", batchList.size()); return status; @@ -548,6 +668,13 @@ status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { DrawModifiers restoreDrawModifiers = renderer.getDrawModifiers(); renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + if (CC_LIKELY(mAvoidOverdraw)) { + for (unsigned int i = 1; i < mBatches.size(); i++) { + if (mBatches[i] && mBatches[i]->coversBounds(mBounds)) { + discardDrawingBatches(i - 1); + } + } + } // NOTE: depth of the save stack at this point, before playback, should be reflected in // FLUSH_SAVE_STACK_DEPTH, so that save/restores match up correctly status |= replayBatchList(mBatches, renderer, dirty); @@ -560,5 +687,17 @@ status_t DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { return status; } +void DeferredDisplayList::discardDrawingBatches(const unsigned int maxIndex) { + for (unsigned int i = mEarliestUnclearedIndex; i <= maxIndex; i++) { + // leave deferred state ops alone for simplicity (empty save restore pairs may now exist) + if (mBatches[i] && mBatches[i]->purelyDrawBatch()) { + DrawBatch* b = (DrawBatch*) mBatches[i]; + delete mBatches[i]; + mBatches.replaceAt(NULL, i); + } + } + mEarliestUnclearedIndex = maxIndex + 1; +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h index 9782c1c..3dcbd0b 100644 --- a/libs/hwui/DeferredDisplayList.h +++ b/libs/hwui/DeferredDisplayList.h @@ -18,11 +18,13 @@ #define ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H #include <utils/Errors.h> +#include <utils/LinearAllocator.h> #include <utils/Vector.h> +#include <utils/TinyHashMap.h> #include "Matrix.h" +#include "OpenGLRenderer.h" #include "Rect.h" -#include "utils/TinyHashMap.h" class SkBitmap; @@ -34,18 +36,56 @@ class DrawOp; class SaveOp; class SaveLayerOp; class StateOp; + +class DeferredDisplayState; class OpenGLRenderer; class Batch; class DrawBatch; class MergingDrawBatch; -typedef void* mergeid_t; +typedef const void* mergeid_t; + +class DeferredDisplayState { +public: + /** static void* operator new(size_t size); PURPOSELY OMITTED **/ + static void* operator new(size_t size, LinearAllocator& allocator) { + return allocator.alloc(size); + } + + // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped + Rect mBounds; + + // the below are set and used by the OpenGLRenderer at record and deferred playback + bool mClipValid; + Rect mClip; + int mClipSideFlags; // specifies which sides of the bounds are clipped, unclipped if cleared + bool mClipped; + mat4 mMatrix; + DrawModifiers mDrawModifiers; + float mAlpha; +}; + +class OpStatePair { +public: + OpStatePair() + : op(NULL), state(NULL) {} + OpStatePair(DrawOp* newOp, const DeferredDisplayState* newState) + : op(newOp), state(newState) {} + OpStatePair(const OpStatePair& other) + : op(other.op), state(other.state) {} + DrawOp* op; + const DeferredDisplayState* state; +}; class DeferredDisplayList { public: - DeferredDisplayList() { clear(); } + 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 @@ -75,11 +115,19 @@ public: /** * Add a draw op into the DeferredDisplayList, reordering as needed (for performance) if - * disallowReorder is false, respecting draw order when overlaps occur + * disallowReorder is false, respecting draw order when overlaps occur. */ void addDrawOp(OpenGLRenderer& renderer, DrawOp* op); private: + DeferredDisplayState* createState() { + return new (mAllocator) DeferredDisplayState(); + } + + void tryRecycleState(DeferredDisplayState* state) { + mAllocator.rewindIfLastAlloc(state, sizeof(DeferredDisplayState)); + } + /** * Resets the batching back-pointers, creating a barrier in the operation stream so that no ops * added in the future will be inserted into a batch that already exist. @@ -96,6 +144,12 @@ private: int getStateOpDeferFlags() const; int getDrawOpDeferFlags() const; + void discardDrawingBatches(const unsigned int maxIndex); + + // layer space bounds of rendering + Rect mBounds; + const bool mAvoidOverdraw; + /** * At defer time, stores the *defer time* savecount of save/saveLayer ops that were deferred, so * that when an associated restoreToCount is deferred, it can be recorded as a @@ -112,12 +166,35 @@ private: // Points to the index after the most recent barrier int mEarliestBatchIndex; + // Points to the first index that may contain a pure drawing batch + int mEarliestUnclearedIndex; + /** * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not * collide, which avoids the need to resolve mergeid collisions. */ TinyHashMap<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count]; + + LinearAllocator mAllocator; +}; + +/** + * Struct containing information that instructs the defer + */ +struct DeferInfo { +public: + DeferInfo() : + batchId(DeferredDisplayList::kOpBatch_None), + mergeId((mergeid_t) -1), + mergeable(false), + opaqueOverBounds(false) { + }; + + int batchId; + mergeid_t mergeId; + bool mergeable; + bool opaqueOverBounds; // opaque over bounds in DeferredDisplayState - can skip ops below }; }; // namespace uirenderer diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index 1cbd531..bb6526e 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -44,13 +44,14 @@ void DisplayList::outputLogBuffer(int fd) { } DisplayList::DisplayList(const DisplayListRenderer& recorder) : - mTransformMatrix(NULL), mTransformCamera(NULL), mTransformMatrix3D(NULL), + mDestroyed(false), mTransformMatrix(NULL), mTransformCamera(NULL), mTransformMatrix3D(NULL), mStaticMatrix(NULL), mAnimationMatrix(NULL) { initFromDisplayListRenderer(recorder); } DisplayList::~DisplayList() { + mDestroyed = true; clearResources(); } @@ -63,7 +64,6 @@ void DisplayList::destroyDisplayListDeferred(DisplayList* displayList) { void DisplayList::clearResources() { mDisplayListData = NULL; - mSize = 0; // TODO: shouldn't be needed, WAR possible use after delete mClipRectOp = NULL; mSaveLayerOp = NULL; @@ -100,6 +100,10 @@ void DisplayList::clearResources() { 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)); @@ -134,6 +138,7 @@ void DisplayList::clearResources() { mBitmapResources.clear(); mOwnedBitmapResources.clear(); mFilterResources.clear(); + mPatchResources.clear(); mShaders.clear(); mSourcePaths.clear(); mPaints.clear(); @@ -169,6 +174,10 @@ void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorde 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(); @@ -197,6 +206,13 @@ void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorde caches.resourceCache.incrementRefcountLocked(resource); } + 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); @@ -342,7 +358,7 @@ void DisplayList::outputViewProperties(const int level) { } if (mAnimationMatrix) { ALOGD("%*sConcatMatrix (animation) %p: " MATRIX_STRING, - level * 2, "", mAnimationMatrix, MATRIX_ARGS(mStaticMatrix)); + level * 2, "", mAnimationMatrix, MATRIX_ARGS(mAnimationMatrix)); } if (mMatrixFlags != 0) { if (mMatrixFlags == TRANSLATION) { @@ -352,6 +368,8 @@ void DisplayList::outputViewProperties(const int level) { level * 2, "", mTransformMatrix, MATRIX_ARGS(mTransformMatrix)); } } + + bool clipToBoundsNeeded = mCaching ? false : mClipToBounds; if (mAlpha < 1) { if (mCaching) { ALOGD("%*sSetOverrideLayerAlpha %.2f", level * 2, "", mAlpha); @@ -359,15 +377,16 @@ void DisplayList::outputViewProperties(const int level) { ALOGD("%*sScaleAlpha %.2f", level * 2, "", mAlpha); } else { int flags = SkCanvas::kHasAlphaLayer_SaveFlag; - if (mClipToBounds) { + 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 (mClipToBounds && !mCaching) { + if (clipToBoundsNeeded) { ALOGD("%*sClipRect %.2f, %.2f, %.2f, %.2f", level * 2, "", 0.0f, 0.0f, (float) mRight - mLeft, (float) mBottom - mTop); } @@ -402,6 +421,7 @@ void DisplayList::setViewProperties(OpenGLRenderer& renderer, T& handler, renderer.concatMatrix(mTransformMatrix); } } + bool clipToBoundsNeeded = mCaching ? false : mClipToBounds; if (mAlpha < 1) { if (mCaching) { renderer.setOverrideLayerAlpha(mAlpha); @@ -412,15 +432,16 @@ void DisplayList::setViewProperties(OpenGLRenderer& renderer, T& handler, // 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 (mClipToBounds) { + 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 (mClipToBounds && !mCaching) { + if (clipToBoundsNeeded) { handler(mClipRectOp->reinit(0, 0, mRight - mLeft, mBottom - mTop, SkRegion::kIntersect_Op), PROPERTY_SAVECOUNT, mClipToBounds); } @@ -480,7 +501,11 @@ void DisplayList::replay(ReplayStateStruct& replayStruct, const int level) { */ template <class T> void DisplayList::iterate(OpenGLRenderer& renderer, T& handler, const int level) { - if (mSize == 0 || mAlpha <= 0 || CC_UNLIKELY(!mSaveOp)) { // TODO: shouldn't need mSaveOp check + 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; } @@ -514,6 +539,10 @@ void DisplayList::iterate(OpenGLRenderer& renderer, T& handler, const int level) 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); } diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 5f84329..1cd5f1c 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -26,13 +26,15 @@ #include <private/hwui/DrawGlInfo.h> +#include <utils/LinearAllocator.h> #include <utils/RefBase.h> #include <utils/SortedVector.h> #include <utils/String8.h> #include <utils/Vector.h> + #include <cutils/compiler.h> -#include "utils/LinearAllocator.h" +#include <androidfw/ResourceTypes.h> #include "Debug.h" @@ -111,7 +113,6 @@ public: void initFromDisplayListRenderer(const DisplayListRenderer& recorder, bool reusing = false); - void defer(DeferStateStruct& deferStruct, const int level); void replay(ReplayStateStruct& replayStruct, const int level); @@ -129,7 +130,12 @@ public: void setName(const char* name) { if (name) { - mName.setTo(name); + char* lastPeriod = strrchr(name, '.'); + if (lastPeriod) { + mName.setTo(lastPeriod + 1); + } else { + mName.setTo(name); + } } } @@ -479,6 +485,7 @@ private: Vector<SkBitmap*> mBitmapResources; Vector<SkBitmap*> mOwnedBitmapResources; Vector<SkiaColorFilter*> mFilterResources; + Vector<Res_png_9patch*> mPatchResources; Vector<SkPaint*> mPaints; Vector<SkPath*> mPaths; @@ -496,6 +503,7 @@ private: uint32_t mFunctorCount; String8 mName; + bool mDestroyed; // used for debugging crash, TODO: remove once invalid state crash fixed // View properties bool mClipToBounds; @@ -525,11 +533,11 @@ private: * an alpha causes a SaveLayerAlpha to occur). These operations point into mDisplayListData's * allocation, or null if uninitialized. * - * These are initialized (via friend 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. + * 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) + * They should be used at most once per frame (one call to 'iterate') to avoid overwriting data */ ClipRectOp* mClipRectOp; SaveLayerOp* mSaveLayerOp; diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index a0290e3..326805a 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -26,26 +26,19 @@ #include <private/hwui/DrawGlInfo.h> #include "OpenGLRenderer.h" +#include "AssetAtlas.h" #include "DeferredDisplayList.h" #include "DisplayListRenderer.h" +#include "UvMapper.h" #include "utils/LinearAllocator.h" #define CRASH() do { \ - *(int *)(uintptr_t)0xbbadbeef = 0; \ + *(int *)(uintptr_t) 0xbbadbeef = 0; \ ((void(*)())0)(); /* More reliable, but doesn't say BBADBEEF */ \ } while(false) -#define MATRIX_STRING "[%.2f %.2f %.2f] [%.2f %.2f %.2f] [%.2f %.2f %.2f]" -#define 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 RECT_STRING "%.2f %.2f %.2f %.2f" -#define RECT_ARGS(r) \ - r.left, r.top, r.right, r.bottom - // Use OP_LOG for logging with arglist, OP_LOGS if just printing char* -#define OP_LOGS(s) OP_LOG("%s", s) +#define OP_LOGS(s) OP_LOG("%s", (s)) #define OP_LOG(s, ...) ALOGD( "%*s" s, level * 2, "", __VA_ARGS__ ) namespace android { @@ -84,20 +77,11 @@ public: virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level, bool useQuickReject) = 0; - virtual void output(int level, uint32_t logFlags = 0) = 0; + virtual void output(int level, uint32_t logFlags = 0) const = 0; // NOTE: it would be nice to declare constants and overriding the implementation in each op to // point at the constants, but that seems to require a .cpp file virtual const char* name() = 0; - - /** - * Stores the relevant canvas state of the object between deferral and replay (if the canvas - * state supports being stored) See OpenGLRenderer::simpleClipAndState() - * - * TODO: don't reserve space for StateOps that won't be deferred - */ - DeferredDisplayState state; - }; class StateOp : public DisplayListOp { @@ -136,11 +120,6 @@ public: return; } - if (!getLocalBounds(state.mBounds)) { - // empty bounds signify bounds can't be calculated - state.mBounds.setEmpty(); - } - deferStruct.mDeferredList.addDrawOp(deferStruct.mRenderer, this); } @@ -163,16 +142,16 @@ public: * reducing which operations are tagged as mergeable. */ virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, - const Vector<DrawOp*>& ops, const Rect& bounds) { + const Vector<OpStatePair>& ops, const Rect& bounds) { status_t status = DrawGlInfo::kStatusDone; for (unsigned int i = 0; i < ops.size(); i++) { - renderer.restoreDisplayState(ops[i]->state); - status |= ops[i]->applyDraw(renderer, dirty); + renderer.restoreDisplayState(*(ops[i].state), true); + status |= ops[i].op->applyDraw(renderer, dirty); } return status; } - /* + /** * When this method is invoked the state field is initialized to have the * final rendering state. We can thus use it to process data as it will be * used at draw time. @@ -180,21 +159,25 @@ public: * Additionally, this method allows subclasses to provide defer-time preferences for batching * and merging. * - * Return true if the op can merge with others of its kind (such subclasses should implement - * multiDraw) + * if a subclass can set deferInfo.mergeable to true, it should implement multiDraw() */ - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) {} + + /** + * Query the conservative, local bounds (unmapped) bounds of the op. + * + * returns true if bounds exist + */ + virtual bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) { return false; } - // returns true if bounds exist - virtual bool getLocalBounds(Rect& localBounds) { return false; } - // TODO: better refine localbounds usage void setQuickRejected(bool quickRejected) { mQuickRejected = quickRejected; } bool getQuickRejected() { return mQuickRejected; } - inline int getPaintAlpha() { + inline int getPaintAlpha() const { return OpenGLRenderer::getAlphaDirect(mPaint); } @@ -209,6 +192,23 @@ protected: return renderer.filterPaint(mPaint); } + // Helper method for determining op opaqueness. Assumes op fills its bounds in local + // coordinates, and that paint's alpha is used + inline bool isOpaqueOverBounds(const DeferredDisplayState& state) { + // ensure that local bounds cover mapped bounds + 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; + + SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint); + return (mode == SkXfermode::kSrcOver_Mode || + mode == SkXfermode::kSrc_Mode); + + } + SkPaint* mPaint; // should be accessed via getPaint() when applying bool mQuickRejected; }; @@ -218,6 +218,9 @@ public: DrawBoundedOp(float left, float top, float right, float bottom, SkPaint* paint) : DrawOp(paint), mLocalBounds(left, top, right, bottom) {} + DrawBoundedOp(const Rect& localBounds, 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) @@ -232,24 +235,20 @@ public: } // default empty constructor for bounds, to be overridden in child constructor body - DrawBoundedOp(SkPaint* paint) - : DrawOp(paint) {} + DrawBoundedOp(SkPaint* paint): DrawOp(paint) { } - bool getLocalBounds(Rect& localBounds) { + bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) { localBounds.set(mLocalBounds); + if (drawModifiers.mHasShadow) { + // TODO: inspect paint's looper directly + Rect shadow(mLocalBounds); + shadow.translate(drawModifiers.mShadowDx, drawModifiers.mShadowDy); + shadow.outset(drawModifiers.mShadowRadius); + localBounds.unionWith(shadow); + } return true; } - bool mergeAllowed() { - if (!state.mMatrix.isPureTranslate()) return false; - - // checks that we're unclipped, and srcover - const Rect& opBounds = state.mBounds; - return fabs(opBounds.getWidth() - mLocalBounds.getWidth()) < 0.1 && - fabs(opBounds.getHeight() - mLocalBounds.getHeight()) < 0.1 && - (OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode); - } - protected: Rect mLocalBounds; // displayed area in LOCAL coord. doesn't incorporate stroke, so check paint }; @@ -275,7 +274,7 @@ public: renderer.save(mFlags); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Save flags %x", mFlags); } @@ -309,7 +308,7 @@ public: renderer.restoreToCount(saveCount + mCount); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Restore to count %d", mCount); } @@ -348,7 +347,7 @@ public: renderer.saveLayer(mArea.left, mArea.top, mArea.right, mArea.bottom, mAlpha, mMode, mFlags); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("SaveLayer%s of area " RECT_STRING, (isSaveLayerAlpha() ? "Alpha" : ""),RECT_ARGS(mArea)); } @@ -369,7 +368,7 @@ private: return this; } - bool isSaveLayerAlpha() { return mAlpha < 255 && mMode == SkXfermode::kSrcOver_Mode; } + bool isSaveLayerAlpha() const { return mAlpha < 255 && mMode == SkXfermode::kSrcOver_Mode; } Rect mArea; int mAlpha; SkXfermode::Mode mMode; @@ -385,7 +384,7 @@ public: renderer.translate(mDx, mDy); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Translate by %f %f", mDx, mDy); } @@ -405,7 +404,7 @@ public: renderer.rotate(mDegrees); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Rotate by %f degrees", mDegrees); } @@ -424,7 +423,7 @@ public: renderer.scale(mSx, mSy); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Scale by %f %f", mSx, mSy); } @@ -444,7 +443,7 @@ public: renderer.skew(mSx, mSy); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Skew by %f %f", mSx, mSy); } @@ -464,8 +463,12 @@ public: renderer.setMatrix(mMatrix); } - virtual void output(int level, uint32_t logFlags) { - OP_LOG("SetMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix)); + virtual void output(int level, uint32_t logFlags) const { + if (mMatrix) { + OP_LOG("SetMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix)); + } else { + OP_LOGS("SetMatrix (reset)"); + } } virtual const char* name() { return "SetMatrix"; } @@ -483,7 +486,7 @@ public: renderer.concatMatrix(mMatrix); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("ConcatMatrix " MATRIX_STRING, MATRIX_ARGS(mMatrix)); } @@ -527,7 +530,7 @@ public: renderer.clipRect(mArea.left, mArea.top, mArea.right, mArea.bottom, mOp); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("ClipRect " RECT_STRING, RECT_ARGS(mArea)); } @@ -556,7 +559,7 @@ public: renderer.clipPath(mPath, mOp); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { SkRect bounds = mPath->getBounds(); OP_LOG("ClipPath bounds " RECT_STRING, bounds.left(), bounds.top(), bounds.right(), bounds.bottom()); @@ -577,7 +580,7 @@ public: renderer.clipRegion(mRegion, mOp); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { SkIRect bounds = mRegion->getBounds(); OP_LOG("ClipRegion bounds %d %d %d %d", bounds.left(), bounds.top(), bounds.right(), bounds.bottom()); @@ -587,7 +590,6 @@ public: private: SkRegion* mRegion; - SkRegion::Op mOp; }; class ResetShaderOp : public StateOp { @@ -596,7 +598,7 @@ public: renderer.resetShader(); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOGS("ResetShader"); } @@ -611,7 +613,7 @@ public: renderer.setupShader(mShader); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("SetupShader, shader %p", mShader); } @@ -627,7 +629,7 @@ public: renderer.resetColorFilter(); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOGS("ResetColorFilter"); } @@ -643,7 +645,7 @@ public: renderer.setupColorFilter(mColorFilter); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("SetupColorFilter, filter %p", mColorFilter); } @@ -659,7 +661,7 @@ public: renderer.resetShadow(); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOGS("ResetShadow"); } @@ -675,7 +677,7 @@ public: renderer.setupShadow(mRadius, mDx, mDy, mColor); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("SetupShadow, radius %f, %f, %f, color %#x", mRadius, mDx, mDy, mColor); } @@ -694,7 +696,7 @@ public: renderer.resetPaintFilter(); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOGS("ResetPaintFilter"); } @@ -710,7 +712,7 @@ public: renderer.setupPaintFilter(mClearBits, mSetBits); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("SetupPaintFilter, clear %#x, set %#x", mClearBits, mSetBits); } @@ -721,7 +723,6 @@ private: int mSetBits; }; - /////////////////////////////////////////////////////////////////////////////// // DRAW OPERATIONS - these are operations that can draw to the canvas's device /////////////////////////////////////////////////////////////////////////////// @@ -729,34 +730,63 @@ 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) {} + : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(), paint), + mBitmap(bitmap), mAtlas(Caches::getInstance().assetAtlas) { + mEntry = mAtlas.getEntry(bitmap); + if (mEntry) { + mEntryGenerationId = mAtlas.getGenerationId(); + mUvMapper = mEntry->uvMapper; + } + } virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawBitmap(mBitmap, mLocalBounds.left, mLocalBounds.top, getPaint(renderer)); } + AssetAtlas::Entry* getAtlasEntry() { + // The atlas entry is stale, let's get a new one + if (mEntry && mEntryGenerationId != mAtlas.getGenerationId()) { + mEntryGenerationId = mAtlas.getGenerationId(); + mEntry = mAtlas.getEntry(mBitmap); + mUvMapper = mEntry->uvMapper; + } + return mEntry; + } + #define SET_TEXTURE(ptr, posRect, offsetRect, texCoordsRect, xDim, yDim) \ TextureVertex::set(ptr++, posRect.xDim - offsetRect.left, posRect.yDim - offsetRect.top, \ texCoordsRect.xDim, texCoordsRect.yDim) + /** + * This multi-draw operation builds a mesh on the stack by generating a quad + * for each bitmap in the batch. This method is also responsible for dirtying + * the current layer, if any. + */ virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, - const Vector<DrawOp*>& ops, const Rect& bounds) { - renderer.restoreDisplayState(state, true); // restore all but the clip - renderer.setFullScreenClip(); // ensure merged ops aren't clipped + const Vector<OpStatePair>& ops, const Rect& bounds) { + const DeferredDisplayState& firstState = *(ops[0].state); + renderer.restoreDisplayState(firstState, true); // restore all but the clip + TextureVertex vertices[6 * ops.size()]; TextureVertex* vertex = &vertices[0]; - // TODO: manually handle rect clip for bitmaps by adjusting texCoords per op, and allowing - // them to be merged in getBatchId() - const Rect texCoords(0, 0, 1, 1); + const bool hasLayer = renderer.hasLayer(); + bool pureTranslate = true; - const float width = mBitmap->width(); - const float height = mBitmap->height(); + // TODO: manually handle rect clip for bitmaps by adjusting texCoords per op, + // and allowing them to be merged in getBatchId() for (unsigned int i = 0; i < ops.size(); i++) { - const Rect& opBounds = ops[i]->state.mBounds; + const DeferredDisplayState& state = *(ops[i].state); + const Rect& opBounds = state.mBounds; + // When we reach multiDraw(), the matrix can be either + // pureTranslate or simple (translate and/or scale). + // If the matrix is not pureTranslate, then we have a scale + pureTranslate &= state.mMatrix.isPureTranslate(); + + Rect texCoords(0, 0, 1, 1); + ((DrawBitmapOp*) ops[i].op)->mUvMapper.map(texCoords); + SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, top); SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top); SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom); @@ -764,29 +794,45 @@ public: SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom); SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top); SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, bottom); + + if (hasLayer) { + renderer.dirtyLayer(opBounds.left, opBounds.top, opBounds.right, opBounds.bottom); + } } - return renderer.drawBitmaps(mBitmap, ops.size(), &vertices[0], bounds, mPaint); + return renderer.drawBitmaps(mBitmap, mEntry, ops.size(), &vertices[0], + pureTranslate, bounds, mPaint); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw bitmap %p at %f %f", mBitmap, mLocalBounds.left, mLocalBounds.top); } virtual const char* name() { return "DrawBitmap"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Bitmap; - *mergeId = (mergeid_t)mBitmap; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap; + deferInfo.mergeId = getAtlasEntry() ? + (mergeid_t) mEntry->getMergeId() : (mergeid_t) mBitmap; - // don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in - // MergingDrawBatch::canMergeWith - return mergeAllowed() && (mBitmap->getConfig() != SkBitmap::kA8_Config); + // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation + // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in + // MergingDrawBatch::canMergeWith() + // TODO: support clipped bitmaps by handling them in SET_TEXTURE + deferInfo.mergeable = state.mMatrix.isSimple() && state.mMatrix.positiveScale() && + !state.mClipSideFlags && + OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode && + (mBitmap->getConfig() != SkBitmap::kA8_Config); } const SkBitmap* bitmap() { return mBitmap; } protected: SkBitmap* mBitmap; + const AssetAtlas& mAtlas; + uint32_t mEntryGenerationId; + AssetAtlas::Entry* mEntry; + UvMapper mUvMapper; }; class DrawBitmapMatrixOp : public DrawBoundedOp { @@ -802,15 +848,15 @@ public: return renderer.drawBitmap(mBitmap, mMatrix, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + 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 bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Bitmap; - return false; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap; } private: @@ -831,16 +877,16 @@ public: getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw bitmap %p src="RECT_STRING", dst="RECT_STRING, mBitmap, RECT_ARGS(mSrc), RECT_ARGS(mLocalBounds)); } virtual const char* name() { return "DrawBitmapRect"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Bitmap; - return false; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap; } private: @@ -858,15 +904,15 @@ public: mLocalBounds.top, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw bitmap %p", mBitmap); } virtual const char* name() { return "DrawBitmapData"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Bitmap; - return false; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap; } }; @@ -883,15 +929,15 @@ public: mVertices, mColors, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw bitmap %p mesh %d x %d", mBitmap, mMeshWidth, mMeshHeight); } virtual const char* name() { return "DrawBitmapMesh"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Bitmap; - return false; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Bitmap; } private: @@ -904,45 +950,148 @@ private: class DrawPatchOp : public DrawBoundedOp { public: - DrawPatchOp(SkBitmap* bitmap, const int32_t* xDivs, - const int32_t* yDivs, const uint32_t* colors, uint32_t width, uint32_t height, - int8_t numColors, float left, float top, float right, float bottom, - int alpha, SkXfermode::Mode mode) - : DrawBoundedOp(left, top, right, bottom, 0), - mBitmap(bitmap), mxDivs(xDivs), myDivs(yDivs), - mColors(colors), mxDivsCount(width), myDivsCount(height), - mNumColors(numColors), mAlpha(alpha), mMode(mode) {}; + DrawPatchOp(SkBitmap* bitmap, Res_png_9patch* patch, + float left, float top, float right, float bottom, SkPaint* paint) + : DrawBoundedOp(left, top, right, bottom, paint), + mBitmap(bitmap), mPatch(patch), mGenerationId(0), mMesh(NULL), + mAtlas(Caches::getInstance().assetAtlas) { + mEntry = mAtlas.getEntry(bitmap); + if (mEntry) { + mEntryGenerationId = mAtlas.getGenerationId(); + } + }; + + AssetAtlas::Entry* getAtlasEntry() { + // The atlas entry is stale, let's get a new one + if (mEntry && mEntryGenerationId != mAtlas.getGenerationId()) { + mEntryGenerationId = mAtlas.getGenerationId(); + mEntry = mAtlas.getEntry(mBitmap); + } + return mEntry; + } + + const Patch* getMesh(OpenGLRenderer& renderer) { + if (!mMesh || renderer.getCaches().patchCache.getGenerationId() != mGenerationId) { + PatchCache& cache = renderer.getCaches().patchCache; + mMesh = cache.get(getAtlasEntry(), mBitmap->width(), mBitmap->height(), + mLocalBounds.getWidth(), mLocalBounds.getHeight(), mPatch); + mGenerationId = cache.getGenerationId(); + } + return mMesh; + } + + /** + * This multi-draw operation builds an indexed mesh on the stack by copying + * and transforming the vertices of each 9-patch in the batch. This method + * is also responsible for dirtying the current layer, if any. + */ + virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, + const Vector<OpStatePair>& ops, const Rect& bounds) { + const DeferredDisplayState& firstState = *(ops[0].state); + renderer.restoreDisplayState(firstState, true); // restore all but the clip + + // Batches will usually contain a small number of items so it's + // worth performing a first iteration to count the exact number + // of vertices we need in the new mesh + uint32_t totalVertices = 0; + for (unsigned int i = 0; i < ops.size(); i++) { + totalVertices += ((DrawPatchOp*) ops[i].op)->getMesh(renderer)->verticesCount; + } + + const bool hasLayer = renderer.hasLayer(); + + uint32_t indexCount = 0; + + TextureVertex vertices[totalVertices]; + TextureVertex* vertex = &vertices[0]; + + // Create a mesh that contains the transformed vertices for all the + // 9-patch objects that are part of the batch. Note that onDefer() + // enforces ops drawn by this function to have a pure translate or + // identity matrix + for (unsigned int i = 0; i < ops.size(); i++) { + DrawPatchOp* patchOp = (DrawPatchOp*) ops[i].op; + const DeferredDisplayState* state = ops[i].state; + const Patch* opMesh = patchOp->getMesh(renderer); + uint32_t vertexCount = opMesh->verticesCount; + if (vertexCount == 0) continue; + + // We use the bounds to know where to translate our vertices + // Using patchOp->state.mBounds wouldn't work because these + // bounds are clipped + const float tx = (int) floorf(state->mMatrix.getTranslateX() + + patchOp->mLocalBounds.left + 0.5f); + const float ty = (int) floorf(state->mMatrix.getTranslateY() + + patchOp->mLocalBounds.top + 0.5f); + + // Copy & transform all the vertices for the current operation + 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]); + } + + // Dirty the current layer if possible. When the 9-patch does not + // contain empty quads we can take a shortcut and simply set the + // dirty rect to the object's bounds. + if (hasLayer) { + if (!opMesh->hasEmptyQuads) { + renderer.dirtyLayer(tx, ty, + tx + patchOp->mLocalBounds.getWidth(), + ty + patchOp->mLocalBounds.getHeight()); + } else { + const size_t count = opMesh->quads.size(); + for (size_t i = 0; i < count; i++) { + const Rect& quadBounds = opMesh->quads[i]; + const float x = tx + quadBounds.left; + const float y = ty + quadBounds.top; + renderer.dirtyLayer(x, y, + x + quadBounds.getWidth(), y + quadBounds.getHeight()); + } + } + } + + indexCount += opMesh->indexCount; + } + + return renderer.drawPatches(mBitmap, getAtlasEntry(), + &vertices[0], indexCount, getPaint(renderer)); + } virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { - // NOTE: not calling the virtual method, which takes a paint - return renderer.drawPatch(mBitmap, mxDivs, myDivs, mColors, - mxDivsCount, myDivsCount, mNumColors, - mLocalBounds.left, mLocalBounds.top, - mLocalBounds.right, mLocalBounds.bottom, mAlpha, mMode); + // We're not calling the public variant of drawPatch() here + // This method won't perform the quickReject() since we've already done it at this point + return renderer.drawPatch(mBitmap, getMesh(renderer), getAtlasEntry(), + mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom, + getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw patch "RECT_STRING, RECT_ARGS(mLocalBounds)); } virtual const char* name() { return "DrawPatch"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Patch; - *mergeId = (mergeid_t)mBitmap; - return true; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Patch; + deferInfo.mergeId = getAtlasEntry() ? (mergeid_t) mEntry->getMergeId() : (mergeid_t) mBitmap; + deferInfo.mergeable = state.mMatrix.isPureTranslate() && + OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; + deferInfo.opaqueOverBounds = isOpaqueOverBounds(state) && mBitmap->isOpaque(); } private: SkBitmap* mBitmap; - const int32_t* mxDivs; - const int32_t* myDivs; - const uint32_t* mColors; - uint32_t mxDivsCount; - uint32_t myDivsCount; - int8_t mNumColors; - int mAlpha; - SkXfermode::Mode mMode; + Res_png_9patch* mPatch; + + uint32_t mGenerationId; + const Patch* mMesh; + + const AssetAtlas& mAtlas; + uint32_t mEntryGenerationId; + AssetAtlas::Entry* mEntry; }; class DrawColorOp : public DrawOp { @@ -954,7 +1103,7 @@ public: return renderer.drawColor(mColor, mMode); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw color %#x, mode %d", mColor, mMode); } @@ -970,7 +1119,7 @@ public: DrawStrokableOp(float left, float top, float right, float bottom, SkPaint* paint) : DrawBoundedOp(left, top, right, bottom, paint) {}; - bool getLocalBounds(Rect& localBounds) { + bool getLocalBounds(const DrawModifiers& drawModifiers, Rect& localBounds) { localBounds.set(mLocalBounds); if (mPaint && mPaint->getStyle() != SkPaint::kFill_Style) { localBounds.outset(strokeWidthOutset()); @@ -978,15 +1127,15 @@ public: return true; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { if (mPaint->getPathEffect()) { - *batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture; + deferInfo.batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture; } else { - *batchId = mPaint->isAntiAlias() ? + deferInfo.batchId = mPaint->isAntiAlias() ? DeferredDisplayList::kOpBatch_AlphaVertices : DeferredDisplayList::kOpBatch_Vertices; } - return false; } }; @@ -1000,10 +1149,17 @@ public: mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Rect "RECT_STRING, RECT_ARGS(mLocalBounds)); } + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + DrawStrokableOp::onDefer(renderer, deferInfo, state); + deferInfo.opaqueOverBounds = isOpaqueOverBounds(state) && + mPaint->getStyle() == SkPaint::kFill_Style; + } + virtual const char* name() { return "DrawRect"; } }; @@ -1017,15 +1173,15 @@ public: return renderer.drawRects(mRects, mCount, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Rects count %d", mCount); } virtual const char* name() { return "DrawRects"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = DeferredDisplayList::kOpBatch_Vertices; - return false; + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = DeferredDisplayList::kOpBatch_Vertices; } private: @@ -1044,7 +1200,7 @@ public: mLocalBounds.right, mLocalBounds.bottom, mRx, mRy, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw RoundRect "RECT_STRING", rx %f, ry %f", RECT_ARGS(mLocalBounds), mRx, mRy); } @@ -1065,7 +1221,7 @@ public: return renderer.drawCircle(mX, mY, mRadius, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Circle x %f, y %f, r %f", mX, mY, mRadius); } @@ -1087,7 +1243,7 @@ public: mLocalBounds.right, mLocalBounds.bottom, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Oval "RECT_STRING, RECT_ARGS(mLocalBounds)); } @@ -1107,7 +1263,7 @@ public: mStartAngle, mSweepAngle, mUseCenter, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Arc "RECT_STRING", start %f, sweep %f, useCenter %d", RECT_ARGS(mLocalBounds), mStartAngle, mSweepAngle, mUseCenter); } @@ -1136,15 +1292,15 @@ public: return renderer.drawPath(mPath, getPaint(renderer)); } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { SkPaint* paint = getPaint(renderer); renderer.getCaches().pathCache.precache(mPath, paint); - *batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture; - return false; + deferInfo.batchId = DeferredDisplayList::kOpBatch_AlphaMaskTexture; } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Path %p in "RECT_STRING, mPath, RECT_ARGS(mLocalBounds)); } @@ -1166,17 +1322,17 @@ public: return renderer.drawLines(mPoints, mCount, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Lines count %d", mCount); } virtual const char* name() { return "DrawLines"; } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { - *batchId = mPaint->isAntiAlias() ? + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { + deferInfo.batchId = mPaint->isAntiAlias() ? DeferredDisplayList::kOpBatch_AlphaVertices : DeferredDisplayList::kOpBatch_Vertices; - return false; } protected: @@ -1193,7 +1349,7 @@ public: return renderer.drawPoints(mPoints, mCount, getPaint(renderer)); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Points count %d", mCount); } @@ -1205,20 +1361,19 @@ public: DrawSomeTextOp(const char* text, int bytesCount, int count, SkPaint* paint) : DrawOp(paint), mText(text), mBytesCount(bytesCount), mCount(count) {}; - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw some text, %d bytes", mBytesCount); } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { SkPaint* paint = getPaint(renderer); FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint); fontRenderer.precache(paint, mText, mCount, mat4::identity()); - *batchId = mPaint->getColor() == 0xff000000 ? + deferInfo.batchId = mPaint->getColor() == 0xff000000 ? DeferredDisplayList::kOpBatch_Text : DeferredDisplayList::kOpBatch_ColorText; - - return false; } protected: @@ -1270,27 +1425,14 @@ private: class DrawTextOp : public DrawBoundedOp { public: DrawTextOp(const char* text, int bytesCount, int count, float x, float y, - const float* positions, SkPaint* paint, float length) - : DrawBoundedOp(paint), mText(text), mBytesCount(bytesCount), mCount(count), - mX(x), mY(y), mPositions(positions), mLength(length) { - // duplicates bounds calculation from OpenGLRenderer::drawText, but doesn't alter mX - SkPaint::FontMetrics metrics; - paint->getFontMetrics(&metrics, 0.0f); - switch (paint->getTextAlign()) { - case SkPaint::kCenter_Align: - x -= length / 2.0f; - break; - case SkPaint::kRight_Align: - x -= length; - break; - default: - break; - } - mLocalBounds.set(x, mY + metrics.fTop, x + length, mY + metrics.fBottom); + const float* positions, 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)); } - virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { + virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, + const DeferredDisplayState& state) { SkPaint* paint = getPaint(renderer); FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint); const mat4& transform = renderer.findBestFontTransform(state.mMatrix); @@ -1298,39 +1440,45 @@ public: fontRenderer.precache(paint, mText, mCount, transform); mPrecacheTransform = transform; } - *batchId = mPaint->getColor() == 0xff000000 ? + deferInfo.batchId = mPaint->getColor() == 0xff000000 ? DeferredDisplayList::kOpBatch_Text : DeferredDisplayList::kOpBatch_ColorText; - *mergeId = (mergeid_t)mPaint->getColor(); + deferInfo.mergeId = (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)); - return mergeAllowed() && noDecorations; + deferInfo.mergeable = state.mMatrix.isPureTranslate() && noDecorations && + OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; } virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { + Rect bounds; + getLocalBounds(renderer.getDrawModifiers(), bounds); return renderer.drawText(mText, mBytesCount, mCount, mX, mY, - mPositions, getPaint(renderer), mLength); + mPositions, getPaint(renderer), mTotalAdvance, bounds); } virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty, - const Vector<DrawOp*>& ops, const Rect& bounds) { + const Vector<OpStatePair>& ops, const Rect& bounds) { status_t status = DrawGlInfo::kStatusDone; - renderer.setFullScreenClip(); // ensure merged ops aren't clipped for (unsigned int i = 0; i < ops.size(); i++) { + const DeferredDisplayState& state = *(ops[i].state); DrawOpMode drawOpMode = (i == ops.size() - 1) ? kDrawOpMode_Flush : kDrawOpMode_Defer; - renderer.restoreDisplayState(ops[i]->state, true); // restore all but the clip + renderer.restoreDisplayState(state, true); // restore all but the clip - DrawTextOp& op = *((DrawTextOp*)ops[i]); + DrawTextOp& op = *((DrawTextOp*)ops[i].op); + // quickReject() will not occure in drawText() so we can use mLocalBounds + // directly, we do not need to account for shadow by calling getLocalBounds() status |= renderer.drawText(op.mText, op.mBytesCount, op.mCount, op.mX, op.mY, - op.mPositions, op.getPaint(renderer), op.mLength, drawOpMode); + op.mPositions, op.getPaint(renderer), op.mTotalAdvance, op.mLocalBounds, + drawOpMode); } return status; } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Text of count %d, bytes %d", mCount, mBytesCount); } @@ -1343,7 +1491,7 @@ private: float mX; float mY; const float* mPositions; - float mLength; + float mTotalAdvance; mat4 mPrecacheTransform; }; @@ -1363,7 +1511,7 @@ public: return ret; } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Functor %p", mFunctor); } @@ -1397,7 +1545,7 @@ public: return DrawGlInfo::kStatusDone; } - virtual void output(int level, uint32_t logFlags) { + 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); @@ -1420,7 +1568,7 @@ public: return renderer.drawLayer(mLayer, mX, mY); } - virtual void output(int level, uint32_t logFlags) { + virtual void output(int level, uint32_t logFlags) const { OP_LOG("Draw Layer %p at %f %f", mLayer, mX, mY); } diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 876c38a..8866029 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -57,6 +57,10 @@ void DisplayListRenderer::reset() { 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)); } @@ -74,6 +78,7 @@ void DisplayListRenderer::reset() { mBitmapResources.clear(); mOwnedBitmapResources.clear(); mFilterResources.clear(); + mPatchResources.clear(); mSourcePaths.clear(); mShaders.clear(); @@ -313,20 +318,13 @@ status_t DisplayListRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, in return DrawGlInfo::kStatusDone; } -status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, - const int32_t* yDivs, const uint32_t* colors, uint32_t width, uint32_t height, - int8_t numColors, float left, float top, float right, float bottom, SkPaint* paint) { - int alpha; - SkXfermode::Mode mode; - OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode); - +status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, + float left, float top, float right, float bottom, SkPaint* paint) { bitmap = refBitmap(bitmap); - xDivs = refBuffer<int>(xDivs, width); - yDivs = refBuffer<int>(yDivs, height); - colors = refBuffer<uint32_t>(colors, numColors); + patch = refPatch(patch); + paint = refPaint(paint); - addDrawOp(new (alloc()) DrawPatchOp(bitmap, xDivs, yDivs, colors, width, height, numColors, - left, top, right, bottom, alpha, mode)); + addDrawOp(new (alloc()) DrawPatchOp(bitmap, patch, left, top, right, bottom, paint)); return DrawGlInfo::kStatusDone; } @@ -423,17 +421,16 @@ status_t DisplayListRenderer::drawPosText(const char* text, int bytesCount, int status_t DisplayListRenderer::drawText(const char* text, int bytesCount, int count, float x, float y, const float* positions, SkPaint* paint, - float length, DrawOpMode drawOpMode) { + float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode) { if (!text || count <= 0) return DrawGlInfo::kStatusDone; - if (length < 0.0f) length = paint->measureText(text, bytesCount); - 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, length); + DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count, + x, y, positions, paint, totalAdvance, bounds); addDrawOp(op); return DrawGlInfo::kStatusDone; } @@ -467,10 +464,12 @@ void DisplayListRenderer::setupColorFilter(SkiaColorFilter* 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() { @@ -506,11 +505,12 @@ void DisplayListRenderer::addStateOp(StateOp* op) { void DisplayListRenderer::addDrawOp(DrawOp* op) { Rect localBounds; - if (op->getLocalBounds(localBounds)) { + if (op->getLocalBounds(mDrawModifiers, localBounds)) { bool rejected = quickRejectNoScissor(localBounds.left, localBounds.top, localBounds.right, localBounds.bottom); op->setQuickRejected(rejected); } + mHasDrawOps = true; addOpInternal(op); } diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 75abad6..d233150 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -103,8 +103,7 @@ public: 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, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, + 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); @@ -122,7 +121,8 @@ public: 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 length, DrawOpMode drawOpMode); + const float* positions, SkPaint* paint, float totalAdvance, const Rect& bounds, + DrawOpMode drawOpMode); virtual status_t drawRects(const float* rects, int count, SkPaint* paint); @@ -156,6 +156,10 @@ public: return mFilterResources; } + const Vector<Res_png_9patch*>& getPatchResources() const { + return mPatchResources; + } + const Vector<SkiaShader*>& getShaders() const { return mShaders; } @@ -265,11 +269,14 @@ private: } inline SkMatrix* refMatrix(SkMatrix* 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; + 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) { @@ -315,9 +322,16 @@ private: return colorFilter; } + inline Res_png_9patch* refPatch(Res_png_9patch* patch) { + mPatchResources.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; diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp index 19b3849..77006a4 100644 --- a/libs/hwui/Dither.cpp +++ b/libs/hwui/Dither.cpp @@ -24,12 +24,15 @@ namespace uirenderer { // Lifecycle /////////////////////////////////////////////////////////////////////////////// +Dither::Dither(): mCaches(NULL), mInitialized(false), mDitherTexture(0) { +} + void Dither::bindDitherTexture() { if (!mInitialized) { - bool useFloatTexture = Extensions::getInstance().getMajorGlVersion() >= 3; + bool useFloatTexture = Extensions::getInstance().hasFloatTextures(); glGenTextures(1, &mDitherTexture); - glBindTexture(GL_TEXTURE_2D, mDitherTexture); + mCaches->bindTexture(mDitherTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -68,13 +71,13 @@ void Dither::bindDitherTexture() { mInitialized = true; } else { - glBindTexture(GL_TEXTURE_2D, mDitherTexture); + mCaches->bindTexture(mDitherTexture); } } void Dither::clear() { if (mInitialized) { - glDeleteTextures(1, &mDitherTexture); + mCaches->deleteTexture(mDitherTexture); mInitialized = false; } } @@ -84,8 +87,10 @@ void Dither::clear() { /////////////////////////////////////////////////////////////////////////////// void Dither::setupProgram(Program* program, GLuint* textureUnit) { + if (!mCaches) mCaches = &Caches::getInstance(); + GLuint textureSlot = (*textureUnit)++; - Caches::getInstance().activeTexture(textureSlot); + mCaches->activeTexture(textureSlot); bindDitherTexture(); diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h index 4d1f921..546236b 100644 --- a/libs/hwui/Dither.h +++ b/libs/hwui/Dither.h @@ -24,9 +24,7 @@ namespace android { namespace uirenderer { -/////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// +class Caches; // Must be a power of two #define DITHER_KERNEL_SIZE 4 @@ -39,7 +37,7 @@ namespace uirenderer { */ class Dither { public: - Dither(): mInitialized(false), mDitherTexture(0) { } + Dither(); void clear(); void setupProgram(Program* program, GLuint* textureUnit); @@ -47,6 +45,7 @@ public: private: void bindDitherTexture(); + Caches* mCaches; bool mInitialized; GLuint mDitherTexture; }; diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp index 51aec8d..84e7e65 100644 --- a/libs/hwui/Extensions.cpp +++ b/libs/hwui/Extensions.cpp @@ -14,8 +14,19 @@ * limitations under the License. */ +#define LOG_TAG "OpenGLRenderer" + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +#include <utils/Log.h> + #include "Debug.h" #include "Extensions.h" +#include "Properties.h" namespace android { @@ -40,33 +51,28 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// Extensions::Extensions(): Singleton<Extensions>() { - const char* buffer = (const char*) glGetString(GL_EXTENSIONS); - const char* current = buffer; - const char* head = current; - EXT_LOGD("Available GL extensions:"); - do { - head = strchr(current, ' '); - String8 s(current, head ? head - current : strlen(current)); - if (s.length()) { - mExtensionList.add(s); - EXT_LOGD(" %s", s.string()); - } - current = head + 1; - } while (head); - - mHasNPot = hasExtension("GL_OES_texture_npot"); - mHasFramebufferFetch = hasExtension("GL_NV_shader_framebuffer_fetch"); - mHasDiscardFramebuffer = hasExtension("GL_EXT_discard_framebuffer"); - mHasDebugMarker = hasExtension("GL_EXT_debug_marker"); - mHasDebugLabel = hasExtension("GL_EXT_debug_label"); - mHasTiledRendering = hasExtension("GL_QCOM_tiled_rendering"); - mHas1BitStencil = hasExtension("GL_OES_stencil1"); - mHas4BitStencil = hasExtension("GL_OES_stencil4"); - - mExtensions = strdup(buffer); + // Query GL extensions + findExtensions((const char*) glGetString(GL_EXTENSIONS), mGlExtensionList); + mHasNPot = hasGlExtension("GL_OES_texture_npot"); + mHasFramebufferFetch = hasGlExtension("GL_NV_shader_framebuffer_fetch"); + mHasDiscardFramebuffer = hasGlExtension("GL_EXT_discard_framebuffer"); + mHasDebugMarker = hasGlExtension("GL_EXT_debug_marker"); + mHasDebugLabel = hasGlExtension("GL_EXT_debug_label"); + mHasTiledRendering = hasGlExtension("GL_QCOM_tiled_rendering"); + mHas1BitStencil = hasGlExtension("GL_OES_stencil1"); + mHas4BitStencil = hasGlExtension("GL_OES_stencil4"); + + // Query EGL extensions + findExtensions(eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS), mEglExtensionList); + + char property[PROPERTY_VALUE_MAX]; + if (property_get(PROPERTY_DEBUG_NV_PROFILING, property, NULL) > 0) { + mHasNvSystemTime = !strcmp(property, "true") && hasEglExtension("EGL_NV_system_time"); + } else { + mHasNvSystemTime = false; + } const char* version = (const char*) glGetString(GL_VERSION); - mVersion = strdup(version); // Section 6.1.5 of the OpenGL ES specification indicates the GL version // string strictly follows this format: @@ -80,7 +86,7 @@ Extensions::Extensions(): Singleton<Extensions>() { // or more digits. The release number and vendor specific information are // optional." - if (sscanf(version, "OpenGL ES %d.%d", &mVersionMajor, &mVersionMinor) !=2) { + if (sscanf(version, "OpenGL ES %d.%d", &mVersionMajor, &mVersionMinor) != 2) { // If we cannot parse the version number, assume OpenGL ES 2.0 mVersionMajor = 2; mVersionMinor = 0; @@ -88,22 +94,41 @@ Extensions::Extensions(): Singleton<Extensions>() { } Extensions::~Extensions() { - free(mExtensions); - free(mVersion); } /////////////////////////////////////////////////////////////////////////////// // Methods /////////////////////////////////////////////////////////////////////////////// -bool Extensions::hasExtension(const char* extension) const { +bool Extensions::hasGlExtension(const char* extension) const { + const String8 s(extension); + return mGlExtensionList.indexOf(s) >= 0; +} + +bool Extensions::hasEglExtension(const char* extension) const { const String8 s(extension); - return mExtensionList.indexOf(s) >= 0; + return mEglExtensionList.indexOf(s) >= 0; +} + +void Extensions::findExtensions(const char* extensions, SortedVector<String8>& list) const { + const char* current = extensions; + const char* head = current; + EXT_LOGD("Available extensions:"); + do { + head = strchr(current, ' '); + String8 s(current, head ? head - current : strlen(current)); + if (s.length()) { + list.add(s); + EXT_LOGD(" %s", s.string()); + } + current = head + 1; + } while (head); } void Extensions::dump() const { - ALOGD("%s", mVersion); - ALOGD("Supported extensions:\n%s", mExtensions); + ALOGD("%s", (const char*) glGetString(GL_VERSION)); + ALOGD("Supported GL extensions:\n%s", (const char*) glGetString(GL_EXTENSIONS)); + ALOGD("Supported EGL extensions:\n%s", eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS)); } }; // namespace uirenderer diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h index 54a3987..25d4c5e 100644 --- a/libs/hwui/Extensions.h +++ b/libs/hwui/Extensions.h @@ -17,13 +17,12 @@ #ifndef ANDROID_HWUI_EXTENSIONS_H #define ANDROID_HWUI_EXTENSIONS_H +#include <cutils/compiler.h> + #include <utils/Singleton.h> #include <utils/SortedVector.h> #include <utils/String8.h> -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - namespace android { namespace uirenderer { @@ -31,11 +30,8 @@ namespace uirenderer { // Classes /////////////////////////////////////////////////////////////////////////////// -class Extensions: public Singleton<Extensions> { +class ANDROID_API Extensions: public Singleton<Extensions> { public: - Extensions(); - ~Extensions(); - inline bool hasNPot() const { return mHasNPot; } inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; } inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; } @@ -44,21 +40,30 @@ public: inline bool hasTiledRendering() const { return mHasTiledRendering; } inline bool has1BitStencil() const { return mHas1BitStencil; } inline bool has4BitStencil() const { return mHas4BitStencil; } + inline bool hasNvSystemTime() const { return mHasNvSystemTime; } + inline bool hasUnpackRowLength() const { return mVersionMajor >= 3; } + inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; } + inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; } + inline bool hasFloatTextures() const { return mVersionMajor >= 3; } inline int getMajorGlVersion() const { return mVersionMajor; } inline int getMinorGlVersion() const { return mVersionMinor; } - bool hasExtension(const char* extension) const; + bool hasGlExtension(const char* extension) const; + bool hasEglExtension(const char* extension) const; void dump() const; private: - friend class Singleton<Extensions>; + Extensions(); + ~Extensions(); - SortedVector<String8> mExtensionList; + void findExtensions(const char* extensions, SortedVector<String8>& list) const; + + friend class Singleton<Extensions>; - char* mExtensions; - char* mVersion; + SortedVector<String8> mGlExtensionList; + SortedVector<String8> mEglExtensionList; bool mHasNPot; bool mHasFramebufferFetch; @@ -68,6 +73,7 @@ private: bool mHasTiledRendering; bool mHas1BitStencil; bool mHas4BitStencil; + bool mHasNvSystemTime; int mVersionMajor; int mVersionMinor; diff --git a/libs/hwui/Fence.h b/libs/hwui/Fence.h new file mode 100644 index 0000000..f175e98 --- /dev/null +++ b/libs/hwui/Fence.h @@ -0,0 +1,113 @@ +/* + * 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_FENCE_H +#define ANDROID_HWUI_FENCE_H + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +namespace android { +namespace uirenderer { + +/** + * Creating a Fence instance inserts a new sync fence in the OpenGL + * commands stream. The caller can then wait for the fence to be signaled + * by calling the wait method. + */ +class Fence { +public: + enum { + /** + * Default timeout in nano-seconds for wait() + */ + kDefaultTimeout = 1000000000 + }; + + /** + * Inserts a new sync fence in the OpenGL commands stream. + */ + Fence() { + mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (mDisplay != EGL_NO_DISPLAY) { + mFence = eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, NULL); + } else { + mFence = EGL_NO_SYNC_KHR; + } + } + + /** + * Destroys the fence. Any caller waiting on the fence will be + * signaled immediately. + */ + ~Fence() { + if (mFence != EGL_NO_SYNC_KHR) { + eglDestroySyncKHR(mDisplay, mFence); + } + } + + /** + * Blocks the calling thread until this fence is signaled, or until + * <timeout> nanoseconds have passed. + * + * Returns true if waiting for the fence was successful, false if + * a timeout or an error occurred. + */ + bool wait(EGLTimeKHR timeout = kDefaultTimeout) { + EGLint waitStatus = eglClientWaitSyncKHR(mDisplay, mFence, + EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, timeout); + if (waitStatus == EGL_FALSE) { + ALOGW("Failed to wait for the fence %#x", eglGetError()); + } + return waitStatus == EGL_CONDITION_SATISFIED_KHR; + } + +private: + EGLDisplay mDisplay; + EGLSyncKHR mFence; + +}; // class Fence + +/** + * An AutoFence creates a Fence instance and waits for the fence + * to be signaled when the AutoFence is destroyed. This is useful + * to automatically wait for a series of OpenGL commands to be + * executed. For example: + * + * void drawAndWait() { + * glDrawElements(); + * AutoFence fence; + * } + */ +class AutoFence { +public: + AutoFence(EGLTimeKHR timeout = Fence::kDefaultTimeout): mTimeout(timeout) { + } + + ~AutoFence() { + mFence.wait(mTimeout); + } + +private: + EGLTimeKHR mTimeout; + Fence mFence; + +}; // class AutoFence + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_FENCE_H diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 543cfa2..00e7870 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -21,10 +21,11 @@ #include <cutils/properties.h> -#include <utils/Functor.h> #include <utils/Log.h> +#ifdef ANDROID_ENABLE_RENDERSCRIPT #include <RenderScript.h> +#endif #include "utils/Blur.h" #include "utils/Timing.h" @@ -33,6 +34,7 @@ #include "Debug.h" #include "Extensions.h" #include "FontRenderer.h" +#include "OpenGLRenderer.h" #include "PixelBuffer.h" #include "Rect.h" @@ -43,6 +45,52 @@ namespace uirenderer { #define RS_MIN_INPUT_CUTOFF 10000 /////////////////////////////////////////////////////////////////////////////// +// TextSetupFunctor +/////////////////////////////////////////////////////////////////////////////// +status_t TextSetupFunctor::operator ()(int what, void* data) { + Data* typedData = reinterpret_cast<Data*>(data); + GLenum glyphFormat = typedData ? typedData->glyphFormat : GL_ALPHA; + + renderer->setupDraw(); + renderer->setupDrawTextGamma(paint); + renderer->setupDrawDirtyRegionsDisabled(); + renderer->setupDrawWithTexture(glyphFormat == GL_ALPHA); + switch (glyphFormat) { + case GL_ALPHA: { + renderer->setupDrawAlpha8Color(paint->getColor(), alpha); + break; + } + case GL_RGBA: { + float floatAlpha = alpha / 255.0f; + renderer->setupDrawColor(floatAlpha, floatAlpha, floatAlpha, floatAlpha); + break; + } + default: { +#if DEBUG_FONT_RENDERER + ALOGD("TextSetupFunctor: called with unknown glyph format %x", glyphFormat); +#endif + break; + } + } + renderer->setupDrawColorFilter(); + renderer->setupDrawShader(); + renderer->setupDrawBlending(true, mode); + renderer->setupDrawProgram(); + renderer->setupDrawModelView(x, y, x, y, pureTranslate, true); + // 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->setupDrawTextGammaUniforms(); + + return NO_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// // FontRenderer /////////////////////////////////////////////////////////////////////////////// @@ -62,8 +110,6 @@ FontRenderer::FontRenderer() : mLinearFiltering = false; - mIndexBufferID = 0; - mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH; mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT; mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH; @@ -103,17 +149,16 @@ FontRenderer::FontRenderer() : sLogFontRendererCreate = false; } -FontRenderer::~FontRenderer() { - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - delete mCacheTextures[i]; +void clearCacheTextures(Vector<CacheTexture*>& cacheTextures) { + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + delete cacheTextures[i]; } - mCacheTextures.clear(); + cacheTextures.clear(); +} - if (mInitialized) { - // Unbinding the buffer shouldn't be necessary but it crashes with some drivers - Caches::getInstance().unbindIndicesBuffer(); - glDeleteBuffers(1, &mIndexBufferID); - } +FontRenderer::~FontRenderer() { + clearCacheTextures(mACacheTextures); + clearCacheTextures(mRGBACacheTextures); LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); while (it.next()) { @@ -130,15 +175,19 @@ void FontRenderer::flushAllAndInvalidate() { it.value()->invalidateTextureCache(); } - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - mCacheTextures[i]->init(); + for (uint32_t i = 0; i < mACacheTextures.size(); i++) { + mACacheTextures[i]->init(); + } + + for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) { + mRGBACacheTextures[i]->init(); } } -void FontRenderer::flushLargeCaches() { +void FontRenderer::flushLargeCaches(Vector<CacheTexture*>& cacheTextures) { // Start from 1; don't deallocate smallest/default texture - for (uint32_t i = 1; i < mCacheTextures.size(); i++) { - CacheTexture* cacheTexture = mCacheTextures[i]; + for (uint32_t i = 1; i < cacheTextures.size(); i++) { + CacheTexture* cacheTexture = cacheTextures[i]; if (cacheTexture->getPixelBuffer()) { cacheTexture->init(); LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); @@ -150,11 +199,16 @@ void FontRenderer::flushLargeCaches() { } } -CacheTexture* FontRenderer::cacheBitmapInTexture(const SkGlyph& glyph, - uint32_t* startX, uint32_t* startY) { - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - if (mCacheTextures[i]->fitBitmap(glyph, startX, startY)) { - return mCacheTextures[i]; +void FontRenderer::flushLargeCaches() { + flushLargeCaches(mACacheTextures); + flushLargeCaches(mRGBACacheTextures); +} + +CacheTexture* FontRenderer::cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures, + const SkGlyph& glyph, uint32_t* startX, uint32_t* startY) { + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + if (cacheTextures[i]->fitBitmap(glyph, startX, startY)) { + return cacheTextures[i]; } } // Could not fit glyph into current cache textures @@ -175,9 +229,27 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp cachedGlyph->mIsValid = false; + // choose an appropriate cache texture list for this glyph format + SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); + Vector<CacheTexture*>* cacheTextures = NULL; + switch (format) { + case SkMask::kA8_Format: + case SkMask::kBW_Format: + cacheTextures = &mACacheTextures; + break; + case SkMask::kARGB32_Format: + cacheTextures = &mRGBACacheTextures; + break; + default: +#if DEBUG_FONT_RENDERER + ALOGD("getCacheTexturesForFormat: unknown SkMask format %x", format); +#endif + return; + } + // If the glyph is too tall, don't cache it if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > - mCacheTextures[mCacheTextures.size() - 1]->getHeight()) { + (*cacheTextures)[cacheTextures->size() - 1]->getHeight()) { ALOGE("Font size too large to fit in cache. width, height = %i, %i", (int) glyph.fWidth, (int) glyph.fHeight); return; @@ -187,14 +259,14 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp uint32_t startX = 0; uint32_t startY = 0; - CacheTexture* cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); + CacheTexture* cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY); if (!cacheTexture) { if (!precaching) { // If the new glyph didn't fit and we are not just trying to precache it, // clear out the cache and try again flushAllAndInvalidate(); - cacheTexture = cacheBitmapInTexture(glyph, &startX, &startY); + cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY); } if (!cacheTexture) { @@ -222,24 +294,21 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp cacheTexture->allocateMesh(); } - // Tells us whether the glyphs is B&W (1 bit per pixel) - // or anti-aliased (8 bits per pixel) - SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); - uint8_t* cacheBuffer = cacheTexture->getPixelBuffer()->map(); - uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; - - // Copy the glyph image, taking the mask format into account uint8_t* bitmapBuffer = (uint8_t*) glyph.fImage; - int stride = glyph.rowBytes(); - - uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE; - memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); + int srcStride = glyph.rowBytes(); + // Copy the glyph image, taking the mask format into account switch (format) { case SkMask::kA8_Format: { + uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; + uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX + - TEXTURE_BORDER_SIZE; + // write leading border line + memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); + // write glyph data if (mGammaTable) { - for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) { + for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { row = cacheY * cacheWidth; cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { @@ -249,21 +318,55 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; } } else { - for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += stride) { + for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { row = cacheY * cacheWidth; memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth); cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; } } + // write trailing border line + row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; + memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); + break; + } + case SkMask::kARGB32_Format: { + // prep data lengths + const size_t formatSize = PixelBuffer::formatSize(GL_RGBA); + const size_t borderSize = formatSize * TEXTURE_BORDER_SIZE; + size_t rowSize = formatSize * glyph.fWidth; + // prep advances + size_t dstStride = formatSize * cacheWidth; + // prep indices + // - we actually start one row early, and then increment before first copy + uint8_t* src = &bitmapBuffer[0 - srcStride]; + uint8_t* dst = &cacheBuffer[cacheTexture->getOffset(startX, startY - 1)]; + uint8_t* dstEnd = &cacheBuffer[cacheTexture->getOffset(startX, endY - 1)]; + uint8_t* dstL = dst - borderSize; + uint8_t* dstR = dst + rowSize; + // write leading border line + memset(dstL, 0, rowSize + 2 * borderSize); + // write glyph data + while (dst < dstEnd) { + memset(dstL += dstStride, 0, borderSize); // leading border column + memcpy(dst += dstStride, src += srcStride, rowSize); // glyph data + memset(dstR += dstStride, 0, borderSize); // trailing border column + } + // write trailing border line + memset(dstL += dstStride, 0, rowSize + 2 * borderSize); break; } case SkMask::kBW_Format: { + uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; + uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX + - TEXTURE_BORDER_SIZE; static const uint8_t COLORS[2] = { 0, 255 }; - + // write leading border line + memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); + // write glyph data for (cacheY = startY; cacheY < endY; cacheY++) { cacheX = startX; - int rowBytes = stride; + int rowBytes = srcStride; uint8_t* buffer = bitmapBuffer; row = cacheY * cacheWidth; @@ -276,23 +379,24 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp } cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; - bitmapBuffer += stride; + bitmapBuffer += srcStride; } + // write trailing border line + row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; + memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); break; } default: - ALOGW("Unkown glyph format: 0x%x", format); + ALOGW("Unknown glyph format: 0x%x", format); break; } - row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; - memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); - cachedGlyph->mIsValid = true; } -CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool allocate) { - CacheTexture* cacheTexture = new CacheTexture(width, height, gMaxNumberOfQuads); +CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum format, + bool allocate) { + CacheTexture* cacheTexture = new CacheTexture(width, height, format, gMaxNumberOfQuads); if (allocate) { Caches::getInstance().activeTexture(0); @@ -304,44 +408,23 @@ CacheTexture* FontRenderer::createCacheTexture(int width, int height, bool alloc } void FontRenderer::initTextTexture() { - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - delete mCacheTextures[i]; - } - mCacheTextures.clear(); + clearCacheTextures(mACacheTextures); + clearCacheTextures(mRGBACacheTextures); mUploadTexture = false; - mCacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, true)); - mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false)); - mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, false)); - mCacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, false)); - mCurrentCacheTexture = mCacheTextures[0]; -} - -// Avoid having to reallocate memory and render quad by quad -void FontRenderer::initVertexArrayBuffers() { - uint32_t numIndices = gMaxNumberOfQuads * 6; - uint32_t indexBufferSizeBytes = numIndices * sizeof(uint16_t); - uint16_t* indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes); - - // Four verts, two triangles , six indices per quad - for (uint32_t i = 0; i < gMaxNumberOfQuads; i++) { - int i6 = i * 6; - int i4 = i * 4; - - indexBufferData[i6 + 0] = i4 + 0; - indexBufferData[i6 + 1] = i4 + 1; - indexBufferData[i6 + 2] = i4 + 2; - - indexBufferData[i6 + 3] = i4 + 0; - indexBufferData[i6 + 4] = i4 + 2; - indexBufferData[i6 + 5] = i4 + 3; - } - - glGenBuffers(1, &mIndexBufferID); - Caches::getInstance().bindIndicesBuffer(mIndexBufferID); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_STATIC_DRAW); - - free(indexBufferData); + mACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, + GL_ALPHA, true)); + mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, + GL_ALPHA, false)); + mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, + GL_ALPHA, false)); + mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, + GL_ALPHA, false)); + mRGBACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, + GL_RGBA, false)); + mRGBACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, + GL_RGBA, false)); + mCurrentCacheTexture = mACacheTextures[0]; } // We don't want to allocate anything unless we actually draw text @@ -351,11 +434,28 @@ void FontRenderer::checkInit() { } initTextTexture(); - initVertexArrayBuffers(); mInitialized = true; } +void checkTextureUpdateForCache(Caches& caches, Vector<CacheTexture*>& cacheTextures, + bool& resetPixelStore, GLuint& lastTextureId) { + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + CacheTexture* cacheTexture = cacheTextures[i]; + if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) { + if (cacheTexture->getTextureId() != lastTextureId) { + lastTextureId = cacheTexture->getTextureId(); + caches.activeTexture(0); + caches.bindTexture(lastTextureId); + } + + if (cacheTexture->upload()) { + resetPixelStore = true; + } + } + } +} + void FontRenderer::checkTextureUpdate() { if (!mUploadTexture) { return; @@ -368,25 +468,8 @@ void FontRenderer::checkTextureUpdate() { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Iterate over all the cache textures and see which ones need to be updated - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - CacheTexture* cacheTexture = mCacheTextures[i]; - if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) { - if (cacheTexture->getTextureId() != lastTextureId) { - lastTextureId = cacheTexture->getTextureId(); - caches.activeTexture(0); - glBindTexture(GL_TEXTURE_2D, lastTextureId); - } - - if (cacheTexture->upload()) { - resetPixelStore = true; - } - -#if DEBUG_FONT_RENDERER - ALOGD("glTexSubimage for cacheTexture %d: x, y, width height = %d, %d, %d, %d", - i, x, y, width, height); -#endif - } - } + checkTextureUpdateForCache(caches, mACacheTextures, resetPixelStore, lastTextureId); + checkTextureUpdateForCache(caches, mRGBACacheTextures, resetPixelStore, lastTextureId); // Unbind any PBO we might have used to update textures caches.unbindPixelBuffer(); @@ -400,21 +483,21 @@ void FontRenderer::checkTextureUpdate() { mUploadTexture = false; } -void FontRenderer::issueDrawCommand() { +void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) { + Caches& caches = Caches::getInstance(); bool first = true; bool force = false; - - GLuint lastId = 0; - Caches& caches = Caches::getInstance(); - - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - CacheTexture* texture = mCacheTextures[i]; + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + CacheTexture* texture = cacheTextures[i]; if (texture->canDraw()) { if (first) { - if (mFunctor) (*mFunctor)(0, NULL); + if (mFunctor) { + TextSetupFunctor::Data functorData(texture->getFormat()); + (*mFunctor)(0, &functorData); + } checkTextureUpdate(); - caches.bindIndicesBuffer(mIndexBufferID); + caches.bindIndicesBuffer(); if (!mDrawn) { // If returns true, a VBO was bound and we must @@ -427,7 +510,7 @@ void FontRenderer::issueDrawCommand() { first = false; } - glBindTexture(GL_TEXTURE_2D, texture->getTextureId()); + caches.bindTexture(texture->getTextureId()); texture->setLinearFiltering(mLinearFiltering, false); TextureVertex* mesh = texture->mesh(); @@ -441,6 +524,11 @@ void FontRenderer::issueDrawCommand() { texture->resetMesh(); } } +} + +void FontRenderer::issueDrawCommand() { + issueDrawCommand(mACacheTextures); + issueDrawCommand(mRGBACacheTextures); mDrawn = true; } @@ -532,13 +620,18 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch return image; } +#ifdef ANDROID_ENABLE_RENDERSCRIPT // Align buffers for renderscript usage if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) { paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT; } - int size = paddedWidth * paddedHeight; uint8_t* dataBuffer = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, size); +#else + int size = paddedWidth * paddedHeight; + uint8_t* dataBuffer = (uint8_t*) malloc(size); +#endif + memset(dataBuffer, 0, size); int penX = radius - bounds.left; @@ -611,13 +704,13 @@ bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *t bool FontRenderer::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) { + float hOffset, float vOffset, Rect* bounds, Functor* functor) { if (!mCurrentFont) { ALOGE("No font set"); return false; } - initRender(clip, bounds, NULL); + initRender(clip, bounds, functor); mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); finishRender(); @@ -633,49 +726,55 @@ void FontRenderer::removeFont(const Font* font) { } void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int32_t radius) { - if (width * height * radius < RS_MIN_INPUT_CUTOFF) { - float *gaussian = new float[2 * radius + 1]; - Blur::generateGaussianWeights(gaussian, radius); +#ifdef ANDROID_ENABLE_RENDERSCRIPT + if (width * height * radius >= RS_MIN_INPUT_CUTOFF) { + uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height); + + if (mRs == 0) { + mRs = new RSC::RS(); + if (!mRs->init(RSC::RS_INIT_LOW_LATENCY | RSC::RS_INIT_SYNCHRONOUS)) { + ALOGE("blur RS failed to init"); + } - uint8_t* scratch = new uint8_t[width * height]; - Blur::horizontal(gaussian, radius, *image, scratch, width, height); - Blur::vertical(gaussian, radius, scratch, *image, width, height); + mRsElement = RSC::Element::A_8(mRs); + mRsScript = RSC::ScriptIntrinsicBlur::create(mRs, mRsElement); + } - delete[] gaussian; - delete[] scratch; - return; - } + RSC::sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0); + RSC::sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, + RS_ALLOCATION_MIPMAP_NONE, RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, + *image); + RSC::sp<RSC::Allocation> aout = RSC::Allocation::createTyped(mRs, t, + RS_ALLOCATION_MIPMAP_NONE, RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, + outImage); - uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height); + mRsScript->setRadius(radius); + mRsScript->setInput(ain); + mRsScript->forEach(aout); - if (mRs.get() == 0) { - mRs = new RSC::RS(); - if (!mRs->init(true, true)) { - ALOGE("blur RS failed to init"); - } + // replace the original image's pointer, avoiding a copy back to the original buffer + free(*image); + *image = outImage; - mRsElement = RSC::Element::A_8(mRs); - mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement); + return; } +#endif - sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0); - sp<RSC::Allocation> ain = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE, - RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, *image); - sp<RSC::Allocation> aout = RSC::Allocation::createTyped(mRs, t, RS_ALLOCATION_MIPMAP_NONE, - RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, outImage); + float *gaussian = new float[2 * radius + 1]; + Blur::generateGaussianWeights(gaussian, radius); - mRsScript->setRadius(radius); - mRsScript->blur(ain, aout); + uint8_t* scratch = new uint8_t[width * height]; + Blur::horizontal(gaussian, radius, *image, scratch, width, height); + Blur::vertical(gaussian, radius, scratch, *image, width, height); - // replace the original image's pointer, avoiding a copy back to the original buffer - free(*image); - *image = outImage; + delete[] gaussian; + delete[] scratch; } -uint32_t FontRenderer::getCacheSize() const { +static uint32_t calculateCacheSize(const Vector<CacheTexture*>& cacheTextures) { uint32_t size = 0; - for (uint32_t i = 0; i < mCacheTextures.size(); i++) { - CacheTexture* cacheTexture = mCacheTextures[i]; + for (uint32_t i = 0; i < cacheTextures.size(); i++) { + CacheTexture* cacheTexture = cacheTextures[i]; if (cacheTexture && cacheTexture->getPixelBuffer()) { size += cacheTexture->getPixelBuffer()->getSize(); } @@ -683,5 +782,19 @@ uint32_t FontRenderer::getCacheSize() const { return size; } +uint32_t FontRenderer::getCacheSize(GLenum format) const { + switch (format) { + case GL_ALPHA: { + return calculateCacheSize(mACacheTextures); + } + case GL_RGBA: { + return calculateCacheSize(mRGBACacheTextures); + } + default: { + return 0; + } + } +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 307a1d9..aa7e776 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -17,8 +17,10 @@ #ifndef ANDROID_HWUI_FONT_RENDERER_H #define ANDROID_HWUI_FONT_RENDERER_H +#include <utils/Functor.h> #include <utils/LruCache.h> #include <utils/Vector.h> +#include <utils/StrongPointer.h> #include <SkPaint.h> @@ -32,19 +34,55 @@ #include "Matrix.h" #include "Properties.h" +#ifdef ANDROID_ENABLE_RENDERSCRIPT +#include "RenderScript.h" namespace RSC { class Element; class RS; class ScriptIntrinsicBlur; + class sp; } +#endif class Functor; namespace android { namespace uirenderer { +class OpenGLRenderer; + /////////////////////////////////////////////////////////////////////////////// -// Renderer +// TextSetupFunctor +/////////////////////////////////////////////////////////////////////////////// +class TextSetupFunctor: public Functor { +public: + struct Data { + Data(GLenum glyphFormat) : glyphFormat(glyphFormat) { + } + + GLenum glyphFormat; + }; + + TextSetupFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate, + int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(), + renderer(renderer), x(x), y(y), pureTranslate(pureTranslate), + alpha(alpha), mode(mode), paint(paint) { + } + ~TextSetupFunctor() { } + + status_t operator ()(int what, void* data); + + OpenGLRenderer* renderer; + float x; + float y; + bool pureTranslate; + int alpha; + SkXfermode::Mode mode; + SkPaint* paint; +}; + +/////////////////////////////////////////////////////////////////////////////// +// FontRenderer /////////////////////////////////////////////////////////////////////////////// class FontRenderer { @@ -52,6 +90,7 @@ public: FontRenderer(); ~FontRenderer(); + void flushLargeCaches(Vector<CacheTexture*>& cacheTextures); void flushLargeCaches(); void setGammaTable(const uint8_t* gammaTable) { @@ -70,7 +109,8 @@ public: // 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); + uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds, + Functor* functor); struct DropShadow { DropShadow() { }; @@ -97,30 +137,29 @@ public: mLinearFiltering = linearFiltering; } - uint32_t getCacheSize() const; + uint32_t getCacheSize(GLenum format) const; private: friend class Font; - static const uint32_t gMaxNumberOfQuads = 2048; - const uint8_t* mGammaTable; void allocateTextureMemory(CacheTexture* cacheTexture); void deallocateTextureMemory(CacheTexture* cacheTexture); void initTextTexture(); - CacheTexture* createCacheTexture(int width, int height, bool allocate); + CacheTexture* createCacheTexture(int width, int height, GLenum format, bool allocate); void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, uint32_t *retOriginX, uint32_t *retOriginY, bool precaching); - CacheTexture* cacheBitmapInTexture(const SkGlyph& glyph, uint32_t* startX, uint32_t* startY); + CacheTexture* cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures, const SkGlyph& glyph, + uint32_t* startX, uint32_t* startY); void flushAllAndInvalidate(); - void initVertexArrayBuffers(); void checkInit(); void initRender(const Rect* clip, Rect* bounds, Functor* functor); void finishRender(); + void issueDrawCommand(Vector<CacheTexture*>& cacheTextures); void issueDrawCommand(); void appendMeshQuadNoClip(float x1, float y1, float u1, float v1, float x2, float y2, float u2, float v2, @@ -148,7 +187,8 @@ private: uint32_t mLargeCacheWidth; uint32_t mLargeCacheHeight; - Vector<CacheTexture*> mCacheTextures; + Vector<CacheTexture*> mACacheTextures; + Vector<CacheTexture*> mRGBACacheTextures; Font* mCurrentFont; LruCache<Font::FontDescription, Font*> mActiveFonts; @@ -157,8 +197,6 @@ private: bool mUploadTexture; - uint32_t mIndexBufferID; - Functor* mFunctor; const Rect* mClip; Rect* mBounds; @@ -168,10 +206,12 @@ private: bool mLinearFiltering; +#ifdef ANDROID_ENABLE_RENDERSCRIPT // RS constructs - sp<RSC::RS> mRs; - sp<const RSC::Element> mRsElement; - sp<RSC::ScriptIntrinsicBlur> mRsScript; + RSC::sp<RSC::RS> mRs; + RSC::sp<const RSC::Element> mRsElement; + RSC::sp<RSC::ScriptIntrinsicBlur> mRsScript; +#endif static void computeGaussianWeights(float* weights, int32_t radius); static void horizontalBlur(float* weights, int32_t radius, const uint8_t *source, uint8_t *dest, diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h index bbfa66d..46cfd04 100644 --- a/libs/hwui/GammaFontRenderer.h +++ b/libs/hwui/GammaFontRenderer.h @@ -35,7 +35,7 @@ public: virtual FontRenderer& getFontRenderer(const SkPaint* paint) = 0; virtual uint32_t getFontRendererCount() const = 0; - virtual uint32_t getFontRendererSize(uint32_t fontRenderer) const = 0; + virtual uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const = 0; virtual void describe(ProgramDescription& description, const SkPaint* paint) const = 0; virtual void setupProgram(ProgramDescription& description, Program* program) const = 0; @@ -81,8 +81,8 @@ public: return 1; } - uint32_t getFontRendererSize(uint32_t fontRenderer) const { - return mRenderer ? mRenderer->getCacheSize() : 0; + uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const { + return mRenderer ? mRenderer->getCacheSize(format) : 0; } void describe(ProgramDescription& description, const SkPaint* paint) const; @@ -128,8 +128,8 @@ public: return 1; } - uint32_t getFontRendererSize(uint32_t fontRenderer) const { - return mRenderer ? mRenderer->getCacheSize() : 0; + uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const { + return mRenderer ? mRenderer->getCacheSize(format) : 0; } void describe(ProgramDescription& description, const SkPaint* paint) const { @@ -162,13 +162,13 @@ public: return kGammaCount; } - uint32_t getFontRendererSize(uint32_t fontRenderer) const { + uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const { if (fontRenderer >= kGammaCount) return 0; FontRenderer* renderer = mRenderers[fontRenderer]; if (!renderer) return 0; - return renderer->getCacheSize(); + return renderer->getCacheSize(format); } void describe(ProgramDescription& description, const SkPaint* paint) const { diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index 507ed95..0916942 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -78,7 +78,7 @@ GradientCache::GradientCache(): mCache.setOnEntryRemovedListener(this); const Extensions& extensions = Extensions::getInstance(); - mUseFloatTexture = extensions.getMajorGlVersion() >= 3; + mUseFloatTexture = extensions.hasFloatTextures(); mHasNpot = extensions.hasNPot(); } @@ -120,7 +120,7 @@ void GradientCache::operator()(GradientCacheEntry& shader, Texture*& texture) { const uint32_t size = texture->width * texture->height * bytesPerPixel(); mSize -= size; - glDeleteTextures(1, &texture->id); + texture->deleteTexture(); delete texture; } } @@ -173,7 +173,7 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, GradientInfo info; getGradientInfo(colors, count, info); - Texture* texture = new Texture; + Texture* texture = new Texture(); texture->width = info.width; texture->height = 2; texture->blend = info.hasAlpha; @@ -286,7 +286,7 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, memcpy(pixels + rowBytes, pixels, rowBytes); glGenTextures(1, &texture->id); - glBindTexture(GL_TEXTURE_2D, texture->id); + Caches::getInstance().bindTexture(texture->id); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); if (mUseFloatTexture) { diff --git a/libs/hwui/Image.cpp b/libs/hwui/Image.cpp new file mode 100644 index 0000000..edf3930 --- /dev/null +++ b/libs/hwui/Image.cpp @@ -0,0 +1,63 @@ +/* + * 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 <utils/Log.h> + +#include "Caches.h" +#include "Image.h" + +namespace android { +namespace uirenderer { + +Image::Image(sp<GraphicBuffer> buffer) { + // Create the EGLImage object that maps the GraphicBuffer + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer(); + EGLint attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; + + mImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs); + + if (mImage == EGL_NO_IMAGE_KHR) { + ALOGW("Error creating image (%#x)", eglGetError()); + mTexture = 0; + } else { + // Create a 2D texture to sample from the EGLImage + glGenTextures(1, &mTexture); + Caches::getInstance().bindTexture(mTexture); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, mImage); + + GLenum status = GL_NO_ERROR; + while ((status = glGetError()) != GL_NO_ERROR) { + ALOGW("Error creating image (%#x)", status); + } + } +} + +Image::~Image() { + if (mImage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), mImage); + mImage = EGL_NO_IMAGE_KHR; + + Caches::getInstance().deleteTexture(mTexture); + mTexture = 0; + } +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/Image.h b/libs/hwui/Image.h new file mode 100644 index 0000000..2514535 --- /dev/null +++ b/libs/hwui/Image.h @@ -0,0 +1,67 @@ +/* + * 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_IMAGE_H +#define ANDROID_HWUI_IMAGE_H + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +#include <ui/GraphicBuffer.h> + +namespace android { +namespace uirenderer { + +/** + * A simple wrapper that creates an EGLImage and a texture for a GraphicBuffer. + */ +class Image { +public: + /** + * Creates a new image from the specified graphic buffer. If the image + * cannot be created, getTexture() will return 0 and getImage() will + * return EGL_NO_IMAGE_KHR. + */ + Image(sp<GraphicBuffer> buffer); + ~Image(); + + /** + * Returns the name of the GL texture that can be used to sample + * from this image. + */ + GLuint getTexture() const { + return mTexture; + } + + /** + * Returns the name of the EGL image represented by this object. + */ + EGLImageKHR getImage() const { + return mImage; + } + +private: + GLuint mTexture; + EGLImageKHR mImage; +}; // class Image + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_IMAGE_H diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 4adad05..bd371a3 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -28,9 +28,9 @@ namespace android { namespace uirenderer { -Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight) { +Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight): + caches(Caches::getInstance()), texture(caches) { mesh = NULL; - meshIndices = NULL; meshElementCount = 0; cacheable = true; dirty = false; @@ -47,16 +47,15 @@ Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight) { debugDrawUpdate = false; hasDrawnSinceUpdate = false; deferredList = NULL; - Caches::getInstance().resourceCache.incrementRefcount(this); + caches.resourceCache.incrementRefcount(this); } Layer::~Layer() { - if (colorFilter) Caches::getInstance().resourceCache.decrementRefcount(colorFilter); + if (colorFilter) caches.resourceCache.decrementRefcount(colorFilter); removeFbo(); deleteTexture(); delete[] mesh; - delete[] meshIndices; delete deferredList; } @@ -76,7 +75,7 @@ bool Layer::resize(const uint32_t width, const uint32_t height) { return true; } - const uint32_t maxTextureSize = Caches::getInstance().maxTextureSize; + 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)", desiredWidth, desiredHeight, maxTextureSize, maxTextureSize); @@ -89,7 +88,7 @@ bool Layer::resize(const uint32_t width, const uint32_t height) { setSize(desiredWidth, desiredHeight); if (fbo) { - Caches::getInstance().activeTexture(0); + caches.activeTexture(0); bindTexture(); allocateTexture(); @@ -120,14 +119,14 @@ void Layer::removeFbo(bool flush) { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0); if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo); - Caches::getInstance().renderBufferCache.put(stencil); + caches.renderBufferCache.put(stencil); stencil = NULL; } if (fbo) { if (flush) LayerRenderer::flushLayer(this); // If put fails the cache will delete the FBO - Caches::getInstance().fboCache.put(fbo); + caches.fboCache.put(fbo); fbo = 0; } } @@ -138,21 +137,55 @@ void Layer::setPaint(SkPaint* paint) { void Layer::setColorFilter(SkiaColorFilter* filter) { if (colorFilter) { - Caches::getInstance().resourceCache.decrementRefcount(colorFilter); + caches.resourceCache.decrementRefcount(colorFilter); } colorFilter = filter; if (colorFilter) { - Caches::getInstance().resourceCache.incrementRefcount(colorFilter); + caches.resourceCache.incrementRefcount(colorFilter); } } -void Layer::defer() { - if (!deferredList) { - deferredList = new DeferredDisplayList; +void Layer::bindTexture() const { + if (texture.id) { + caches.bindTexture(renderTarget, texture.id); } - DeferStateStruct deferredState(*deferredList, *renderer, - DisplayList::kReplayFlag_ClipChildren); +} + +void Layer::bindStencilRenderBuffer() const { + if (stencil) { + stencil->bind(); + } +} + +void Layer::generateTexture() { + if (!texture.id) { + glGenTextures(1, &texture.id); + } +} + +void Layer::deleteTexture() { + if (texture.id) { + texture.deleteTexture(); + texture.id = 0; + } +} + +void Layer::clearTexture() { + texture.id = 0; +} +void Layer::allocateTexture() { +#if DEBUG_LAYERS + ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight()); +#endif + if (texture.id) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + } +} + +void Layer::defer() { const float width = layer.getWidth(); const float height = layer.getHeight(); @@ -161,6 +194,14 @@ void Layer::defer() { dirtyRect.set(0, 0, width, height); } + if (deferredList) { + deferredList->reset(dirtyRect); + } else { + deferredList = new DeferredDisplayList(dirtyRect); + } + DeferStateStruct deferredState(*deferredList, *renderer, + DisplayList::kReplayFlag_ClipChildren); + renderer->initViewport(width, height); renderer->setupFrameState(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); @@ -170,8 +211,19 @@ void Layer::defer() { deferredUpdateScheduled = false; } -void Layer::flush() { +void Layer::cancelDefer() { + renderer = NULL; + displayList = NULL; + deferredUpdateScheduled = false; if (deferredList) { + delete deferredList; + deferredList = NULL; + } +} + +void Layer::flush() { + // renderer is checked as layer may be destroyed/put in layer cache with flush scheduled + if (deferredList && renderer) { renderer->setViewport(layer.getWidth(), layer.getHeight()); renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 7186603..b70042f 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -40,6 +40,7 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// // Forward declarations +class Caches; class OpenGLRenderer; class DisplayList; class DeferredDisplayList; @@ -221,50 +222,19 @@ struct Layer { ANDROID_API void setColorFilter(SkiaColorFilter* filter); - inline void bindTexture() const { - if (texture.id) { - glBindTexture(renderTarget, texture.id); - } - } - - inline void bindStencilRenderBuffer() const { - if (stencil) { - stencil->bind(); - } - } + void bindStencilRenderBuffer() const; - inline void generateTexture() { - if (!texture.id) { - glGenTextures(1, &texture.id); - } - } - - inline void deleteTexture() { - if (texture.id) { - glDeleteTextures(1, &texture.id); - texture.id = 0; - } - } + void bindTexture() const; + void generateTexture(); + void allocateTexture(); + void deleteTexture(); /** * When the caller frees the texture itself, the caller * must call this method to tell this layer that it lost * the texture. */ - void clearTexture() { - texture.id = 0; - } - - inline void allocateTexture() { -#if DEBUG_LAYERS - ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight()); -#endif - if (texture.id) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0, - GL_RGBA, GL_UNSIGNED_BYTE, NULL); - } - } + ANDROID_API void clearTexture(); inline mat4& getTexTransform() { return texTransform; @@ -275,6 +245,7 @@ struct Layer { } void defer(); + void cancelDefer(); void flush(); void render(); @@ -306,7 +277,6 @@ struct Layer { * If the layer can be rendered as a mesh, this is non-null. */ TextureVertex* mesh; - uint16_t* meshIndices; GLsizei meshElementCount; /** @@ -320,6 +290,8 @@ struct Layer { bool hasDrawnSinceUpdate; private: + Caches& caches; + /** * 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. diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp index a0709af..6be0146 100644 --- a/libs/hwui/LayerCache.cpp +++ b/libs/hwui/LayerCache.cpp @@ -155,9 +155,7 @@ bool LayerCache::put(Layer* layer) { victim->layer.getHeight()); } - layer->deferredUpdateScheduled = false; - layer->renderer = NULL; - layer->displayList = NULL; + layer->cancelDefer(); LayerEntry entry(layer); diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 3e55fff..f8076cc 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -130,10 +130,7 @@ void LayerRenderer::generateMesh() { if (mLayer->region.isRect() || mLayer->region.isEmpty()) { if (mLayer->mesh) { delete[] mLayer->mesh; - delete[] mLayer->meshIndices; - mLayer->mesh = NULL; - mLayer->meshIndices = NULL; mLayer->meshElementCount = 0; } @@ -154,17 +151,11 @@ void LayerRenderer::generateMesh() { if (mLayer->mesh && mLayer->meshElementCount < elementCount) { delete[] mLayer->mesh; - delete[] mLayer->meshIndices; - mLayer->mesh = NULL; - mLayer->meshIndices = NULL; } - bool rebuildIndices = false; if (!mLayer->mesh) { mLayer->mesh = new TextureVertex[count * 4]; - mLayer->meshIndices = new uint16_t[elementCount]; - rebuildIndices = true; } mLayer->meshElementCount = elementCount; @@ -173,7 +164,6 @@ void LayerRenderer::generateMesh() { const float height = mLayer->layer.getHeight(); TextureVertex* mesh = mLayer->mesh; - uint16_t* indices = mLayer->meshIndices; for (size_t i = 0; i < count; i++) { const android::Rect* r = &rects[i]; @@ -187,17 +177,6 @@ void LayerRenderer::generateMesh() { 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); - - if (rebuildIndices) { - uint16_t quad = i * 4; - int index = i * 6; - indices[index ] = quad; // top-left - indices[index + 1] = quad + 1; // top-right - indices[index + 2] = quad + 2; // bottom-left - indices[index + 3] = quad + 2; // bottom-left - indices[index + 4] = quad + 1; // top-right - indices[index + 5] = quad + 3; // bottom-right - } } } @@ -436,7 +415,7 @@ bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) { if ((error = glGetError()) != GL_NO_ERROR) goto error; caches.activeTexture(0); - glBindTexture(GL_TEXTURE_2D, texture); + caches.bindTexture(texture); glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel()); @@ -467,7 +446,7 @@ bool LayerRenderer::copyLayer(Layer* layer, SkBitmap* bitmap) { mat4 texTransform(layer->getTexTransform()); mat4 invert; - invert.translate(0.0f, 1.0f, 0.0f); + invert.translate(0.0f, 1.0f); invert.scale(1.0f, -1.0f, 1.0f); layer->getTexTransform().multiply(invert); @@ -498,7 +477,7 @@ error: glBindFramebuffer(GL_FRAMEBUFFER, previousFbo); layer->setAlpha(alpha, mode); layer->setFbo(previousLayerFbo); - glDeleteTextures(1, &texture); + caches.deleteTexture(texture); caches.fboCache.put(fbo); glViewport(previousViewport[0], previousViewport[1], previousViewport[2], previousViewport[3]); diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp index 6a5ea51..ba22071 100644 --- a/libs/hwui/Matrix.cpp +++ b/libs/hwui/Matrix.cpp @@ -72,7 +72,7 @@ static bool isZero(float f) { return fabs(f) <= EPSILON; } -uint32_t Matrix4::getType() const { +uint8_t Matrix4::getType() const { if (mType & kTypeUnknown) { mType = kTypeIdentity; @@ -114,7 +114,7 @@ uint32_t Matrix4::getType() const { return mType; } -uint32_t Matrix4::getGeometryType() const { +uint8_t Matrix4::getGeometryType() const { return getType() & sGeometryMask; } @@ -122,12 +122,16 @@ bool Matrix4::rectToRect() const { return getType() & kTypeRectToRect; } +bool Matrix4::positiveScale() const { + return (data[kScaleX] > 0.0f && data[kScaleY] > 0.0f); +} + bool Matrix4::changesBounds() const { return getType() & (kTypeScale | kTypeAffine | kTypePerspective); } bool Matrix4::isPureTranslate() const { - return getGeometryType() == kTypeTranslate; + return getGeometryType() <= kTypeTranslate; } bool Matrix4::isSimple() const { diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h index 75e280c..b861ba4 100644 --- a/libs/hwui/Matrix.h +++ b/libs/hwui/Matrix.h @@ -26,6 +26,12 @@ namespace android { namespace uirenderer { +#define MATRIX_STRING "[%.2f %.2f %.2f] [%.2f %.2f %.2f] [%.2f %.2f %.2f]" +#define 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) + /////////////////////////////////////////////////////////////////////////////// // Classes /////////////////////////////////////////////////////////////////////////////// @@ -118,7 +124,7 @@ public: void loadOrtho(float left, float right, float bottom, float top, float near, float far); - uint32_t getType() const; + uint8_t getType() const; void multiply(const Matrix4& v) { Matrix4 u; @@ -128,10 +134,27 @@ public: void multiply(float v); - void translate(float x, float y, float z) { - Matrix4 u; - u.loadTranslate(x, y, z); - multiply(u); + void translate(float x, float y) { + if ((getType() & sGeometryMask) <= kTypeTranslate) { + data[kTranslateX] += x; + data[kTranslateY] += y; + } 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); + multiply(u); + + // Restore the type and fix the translate bit + mType = type; + if (data[kTranslateX] != 0.0f || data[kTranslateY] != 0.0f) { + mType |= kTypeTranslate; + } else { + mType &= ~kTypeTranslate; + } + } } void scale(float sx, float sy, float sz) { @@ -160,6 +183,7 @@ public: bool isIdentity() const; bool isPerspective() const; bool rectToRect() const; + bool positiveScale() const; bool changesBounds() const; @@ -179,7 +203,7 @@ public: static const Matrix4& identity(); private: - mutable uint32_t mType; + mutable uint8_t mType; inline float get(int i, int j) const { return data[i * 4 + j]; @@ -189,7 +213,7 @@ private: data[i * 4 + j] = v; } - uint32_t getGeometryType() const; + uint8_t getGeometryType() const; }; // class Matrix4 diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 722cc63..4d76bed 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -21,7 +21,6 @@ #include <sys/types.h> #include <SkCanvas.h> -#include <SkPathMeasure.h> #include <SkTypeface.h> #include <utils/Log.h> @@ -34,6 +33,7 @@ #include "OpenGLRenderer.h" #include "DeferredDisplayList.h" #include "DisplayListRenderer.h" +#include "Fence.h" #include "PathTessellator.h" #include "Properties.h" #include "Vector.h" @@ -107,6 +107,15 @@ static const Blender gBlendsSwap[] = { }; /////////////////////////////////////////////////////////////////////////////// +// Functions +/////////////////////////////////////////////////////////////////////////////// + +template<typename T> +static inline T min(T a, T b) { + return a < b ? a : b; +} + +/////////////////////////////////////////////////////////////////////////////// // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// @@ -120,6 +129,7 @@ OpenGLRenderer::OpenGLRenderer(): mFirstSnapshot = new Snapshot; mFrameStarted = false; + mCountOverdraw = false; mScissorOptimizationDisabled = false; } @@ -222,6 +232,7 @@ status_t OpenGLRenderer::prepare(bool opaque) { status_t OpenGLRenderer::prepareDirty(float left, float top, float right, float bottom, bool opaque) { + setupFrameState(left, top, right, bottom, opaque); // Layer renderers will start the frame immediately @@ -253,7 +264,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) { + if (!opaque || mCountOverdraw) { mCaches.enableScissor(); mCaches.setScissor(left, mSnapshot->height - bottom, right - left, bottom - top); glClear(GL_COLOR_BUFFER_BIT); @@ -335,6 +346,10 @@ void OpenGLRenderer::finish() { #endif } + if (mCountOverdraw) { + countOverdraw(); + } + mFrameStarted = false; } @@ -345,6 +360,7 @@ void OpenGLRenderer::interrupt() { mCaches.currentProgram = NULL; } } + mCaches.resetActiveTexture(); mCaches.unbindMeshBuffer(); mCaches.unbindIndicesBuffer(); mCaches.resetVertexPointers(); @@ -366,6 +382,7 @@ void OpenGLRenderer::resume() { dirtyClip(); mCaches.activeTexture(0); + mCaches.resetBoundTextures(); mCaches.blend = true; glEnable(GL_BLEND); @@ -432,13 +449,8 @@ status_t OpenGLRenderer::invokeFunctors(Rect& dirty) { status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone; - interrupt(); detachFunctor(functor); - mCaches.enableScissor(); - if (mDirtyClip) { - setScissorFromClip(); - } Rect clip(*mSnapshot->clipRect); clip.snapToPixelBoundaries(); @@ -459,7 +471,18 @@ status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { info.height = getSnapshot()->height; getSnapshot()->transform->copyTo(&info.transform[0]); - status_t result = (*functor)(DrawGlInfo::kModeDraw, &info) | DrawGlInfo::kStatusDrew; + bool dirtyClip = mDirtyClip; + // setup GL state for functor + if (mDirtyClip) { + setStencilFromClip(); // can issue draws, so must precede enableScissor()/interrupt() + } + if (mCaches.enableScissor() || dirtyClip) { + 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); @@ -471,7 +494,7 @@ status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { } resume(); - return result; + return result | DrawGlInfo::kStatusDrew; } /////////////////////////////////////////////////////////////////////////////// @@ -512,18 +535,41 @@ void OpenGLRenderer::renderOverdraw() { mCaches.setScissor(clip->left, mFirstSnapshot->height - clip->bottom, clip->right - clip->left, clip->bottom - clip->top); + // 1x overdraw mCaches.stencil.enableDebugTest(2); - drawColor(0x2f0000ff, SkXfermode::kSrcOver_Mode); + drawColor(mCaches.getOverdrawColor(1), SkXfermode::kSrcOver_Mode); + + // 2x overdraw mCaches.stencil.enableDebugTest(3); - drawColor(0x2f00ff00, SkXfermode::kSrcOver_Mode); + drawColor(mCaches.getOverdrawColor(2), SkXfermode::kSrcOver_Mode); + + // 3x overdraw mCaches.stencil.enableDebugTest(4); - drawColor(0x3fff0000, SkXfermode::kSrcOver_Mode); + drawColor(mCaches.getOverdrawColor(3), SkXfermode::kSrcOver_Mode); + + // 4x overdraw and higher mCaches.stencil.enableDebugTest(4, true); - drawColor(0x7fff0000, SkXfermode::kSrcOver_Mode); + drawColor(mCaches.getOverdrawColor(4), SkXfermode::kSrcOver_Mode); + mCaches.stencil.disable(); } } +void OpenGLRenderer::countOverdraw() { + size_t count = mWidth * mHeight; + uint32_t* buffer = new uint32_t[count]; + glReadPixels(0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, &buffer[0]); + + size_t total = 0; + for (size_t i = 0; i < count; i++) { + total += buffer[i] & 0xff; + } + + mOverdraw = total / float(count); + + delete[] buffer; +} + /////////////////////////////////////////////////////////////////////////////// // Layers /////////////////////////////////////////////////////////////////////////////// @@ -531,6 +577,8 @@ void OpenGLRenderer::renderOverdraw() { bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { if (layer->deferredUpdateScheduled && layer->renderer && layer->displayList && layer->displayList->isRenderable()) { + ATRACE_CALL(); + Rect& dirty = layer->dirtyRect; if (inFrame) { @@ -598,8 +646,11 @@ void OpenGLRenderer::flushLayers() { sprintf(layerName, "Layer #%d", i); startMark(layerName); + ATRACE_BEGIN("flushLayer"); Layer* layer = mLayerUpdates.itemAt(i); layer->flush(); + ATRACE_END(); + mCaches.resourceCache.decrementRefcount(layer); endMark(); @@ -628,6 +679,18 @@ void OpenGLRenderer::pushLayerUpdate(Layer* layer) { } } +void OpenGLRenderer::cancelLayerUpdate(Layer* layer) { + if (layer) { + for (int i = mLayerUpdates.size() - 1; i >= 0; i--) { + if (mLayerUpdates.itemAt(i) == layer) { + mLayerUpdates.removeAt(i); + mCaches.resourceCache.decrementRefcount(layer); + break; + } + } + } +} + void OpenGLRenderer::clearLayerUpdates() { size_t count = mLayerUpdates.size(); if (count > 0) { @@ -640,6 +703,14 @@ void OpenGLRenderer::clearLayerUpdates() { } } +void OpenGLRenderer::flushLayerUpdates() { + syncState(); + updateLayers(); + flushLayers(); + // Wait for all the layer updates to be executed + AutoFence fence; +} + /////////////////////////////////////////////////////////////////////////////// // State management /////////////////////////////////////////////////////////////////////////////// @@ -780,6 +851,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float if (!mSnapshot->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()); } } @@ -961,6 +1033,10 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { const Rect& rect = layer->layer; const bool fboLayer = current->flags & Snapshot::kFlagIsFboLayer; + bool clipRequired = false; + quickRejectNoScissor(rect, &clipRequired); // safely ignore return, should never be rejected + mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired); + if (fboLayer) { endTiling(); @@ -1019,7 +1095,7 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { } void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) { - float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha; + float alpha = getLayerAlpha(layer); setupDraw(); if (layer->getRenderTarget() == GL_TEXTURE_2D) { @@ -1055,8 +1131,6 @@ void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) { setupDrawMesh(&mMeshVertices[0].position[0], &mMeshVertices[0].texture[0]); glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); - - finishDrawTexture(); } void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) { @@ -1125,8 +1199,6 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { return; } - // TODO: See LayerRenderer.cpp::generateMesh() for important - // information about this implementation if (CC_LIKELY(!layer->region.isEmpty())) { size_t count; const android::Rect* rects; @@ -1149,7 +1221,7 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { // after we setup drawing in case we need to mess with the // stencil buffer in setupDraw() TextureVertex* mesh = mCaches.getRegionMesh(); - GLsizei numQuads = 0; + uint32_t numQuads = 0; setupDrawWithTexture(); setupDrawColor(alpha, alpha, alpha, alpha); @@ -1188,7 +1260,7 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { numQuads++; - if (numQuads >= REGION_MESH_QUAD_COUNT) { + if (numQuads >= gMaxNumberOfQuads) { DRAW_DOUBLE_STENCIL(glDrawElements(GL_TRIANGLES, numQuads * 6, GL_UNSIGNED_SHORT, NULL)); numQuads = 0; @@ -1201,8 +1273,6 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { GL_UNSIGNED_SHORT, NULL)); } - finishDrawTexture(); - #if DEBUG_LAYERS_AS_REGIONS drawRegionRects(layer->region); #endif @@ -1239,7 +1309,6 @@ void OpenGLRenderer::drawRegionRects(const Region& region) { void OpenGLRenderer::drawRegionRects(const SkRegion& region, int color, SkXfermode::Mode mode, bool dirty) { - int count = 0; Vector<float> rects; SkRegion::Iterator it(region); @@ -1249,11 +1318,10 @@ void OpenGLRenderer::drawRegionRects(const SkRegion& region, int color, rects.push(r.fTop); rects.push(r.fRight); rects.push(r.fBottom); - count += 4; it.next(); } - drawColorRects(rects.array(), count, color, mode, true, dirty, false); + drawColorRects(rects.array(), rects.size(), color, mode, true, dirty, false); } void OpenGLRenderer::dirtyLayer(const float left, const float top, @@ -1283,6 +1351,21 @@ void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) { } } +void OpenGLRenderer::drawIndexedQuads(Vertex* mesh, GLsizei quadsCount) { + GLsizei elementsCount = quadsCount * 6; + while (elementsCount > 0) { + GLsizei drawCount = min(elementsCount, (GLsizei) gMaxNumberOfQuads * 6); + + setupDrawIndexedVertices(&mesh[0].position[0]); + glDrawElements(GL_TRIANGLES, drawCount, GL_UNSIGNED_SHORT, NULL); + + elementsCount -= drawCount; + // Though there are 4 vertices in a quad, we use 6 indices per + // quad to draw with GL_TRIANGLES + mesh += (drawCount / 6) * 4; + } +} + void OpenGLRenderer::clearLayerRegions() { const size_t count = mLayers.size(); if (count == 0) return; @@ -1297,17 +1380,15 @@ void OpenGLRenderer::clearLayerRegions() { // is likely different so we need to disable clipping here bool scissorChanged = mCaches.disableScissor(); - Vertex mesh[count * 6]; + Vertex mesh[count * 4]; Vertex* vertex = mesh; for (uint32_t i = 0; i < count; i++) { Rect* bounds = mLayers.itemAt(i); - Vertex::set(vertex++, bounds->left, bounds->bottom); Vertex::set(vertex++, bounds->left, bounds->top); Vertex::set(vertex++, bounds->right, bounds->top); Vertex::set(vertex++, bounds->left, bounds->bottom); - Vertex::set(vertex++, bounds->right, bounds->top); Vertex::set(vertex++, bounds->right, bounds->bottom); delete bounds; @@ -1323,9 +1404,8 @@ void OpenGLRenderer::clearLayerRegions() { setupDrawProgram(); setupDrawPureColorUniforms(); setupDrawModelViewTranslate(0.0f, 0.0f, 0.0f, 0.0f, true); - setupDrawVertices(&mesh[0].position[0]); - glDrawArrays(GL_TRIANGLES, 0, count * 6); + drawIndexedQuads(&mesh[0], count); if (scissorChanged) mCaches.enableScissor(); } else { @@ -1348,11 +1428,30 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef // state has bounds initialized in local coordinates if (!state.mBounds.isEmpty()) { currentMatrix.mapRect(state.mBounds); - if (!state.mBounds.intersect(currentClip)) { + 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)) { // quick rejected return true; } + + state.mClipSideFlags = kClipSide_None; + 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; + } + 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); } } @@ -1376,14 +1475,27 @@ void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool mSnapshot->alpha = state.mAlpha; if (state.mClipValid && !skipClipRestore) { - mSnapshot->setClip(state.mClip.left, state.mClip.top, state.mClip.right, state.mClip.bottom); + mSnapshot->setClip(state.mClip.left, state.mClip.top, + state.mClip.right, state.mClip.bottom); dirtyClip(); } } -void OpenGLRenderer::setFullScreenClip() { - mSnapshot->setClip(0, 0, mWidth, mHeight); +/** + * Merged multidraw (such as in drawText and drawBitmaps rely on the fact that no clipping is done + * in the draw path. Instead, clipping is done ahead of time - either as a single clip rect (when at + * least one op is clipped), or disabled entirely (because no merged op is clipped) + * + * This method should be called when restoreDisplayState() won't be restoring the clip + */ +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); + } dirtyClip(); + mCaches.setScissorEnabled(clipRect != NULL || mScissorOptimizationDisabled); } /////////////////////////////////////////////////////////////////////////////// @@ -1391,7 +1503,7 @@ void OpenGLRenderer::setFullScreenClip() { /////////////////////////////////////////////////////////////////////////////// void OpenGLRenderer::translate(float dx, float dy) { - currentTransform().translate(dx, dy, 0.0f); + currentTransform().translate(dx, dy); } void OpenGLRenderer::rotate(float degrees) { @@ -1514,65 +1626,49 @@ const Rect& OpenGLRenderer::getClipBounds() { return mSnapshot->getLocalClip(); } -bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, float bottom) { - if (mSnapshot->isIgnored()) { +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.snapToPixelBoundaries(); + r.snapGeometryToPixelBoundaries(snapOut); Rect clipRect(*mSnapshot->clipRect); clipRect.snapToPixelBoundaries(); - return !clipRect.intersects(r); -} - -bool OpenGLRenderer::quickRejectNoScissor(float left, float top, float right, float bottom, - Rect& transformed, Rect& clip) { - if (mSnapshot->isIgnored()) { - return true; - } - - transformed.set(left, top, right, bottom); - currentTransform().mapRect(transformed); - transformed.snapToPixelBoundaries(); + if (!clipRect.intersects(r)) return true; - clip.set(*mSnapshot->clipRect); - clip.snapToPixelBoundaries(); - - return !clip.intersects(transformed); + 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(); + if (paint->getStyle() != SkPaint::kFill_Style) { float outset = paint->getStrokeWidth() * 0.5f; - return quickReject(left - outset, top - outset, right + outset, bottom + outset); + return quickReject(left - outset, top - outset, right + outset, bottom + outset, snapOut); } else { - return quickReject(left, top, right, bottom); + return quickReject(left, top, right, bottom, snapOut); } } -bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) { - if (mSnapshot->isIgnored() || bottom <= top || right <= left) { +bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom, bool snapOut) { + bool clipRequired = false; + if (quickRejectNoScissor(left, top, right, bottom, snapOut, &clipRequired)) { return true; } - Rect r(left, top, right, bottom); - currentTransform().mapRect(r); - r.snapToPixelBoundaries(); - - Rect clipRect(*mSnapshot->clipRect); - clipRect.snapToPixelBoundaries(); - - bool rejected = !clipRect.intersects(r); - if (!isDeferred() && !rejected) { - mCaches.setScissorEnabled(mScissorOptimizationDisabled || !clipRect.contains(r)); + if (!isDeferred()) { + mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired); } - - return rejected; + return false; } void OpenGLRenderer::debugClip() { @@ -1595,7 +1691,7 @@ bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, SkPath path; path.addRect(left, top, right, bottom); - return clipPath(&path, op); + return OpenGLRenderer::clipPath(&path, op); } bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) { @@ -1606,11 +1702,15 @@ bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) { path->transform(transform, &transformed); SkRegion clip; - if (!mSnapshot->clipRegion->isEmpty()) { - clip.setRegion(*mSnapshot->clipRegion); + if (!mSnapshot->previous->clipRegion->isEmpty()) { + clip.setRegion(*mSnapshot->previous->clipRegion); } else { - Rect* bounds = mSnapshot->clipRect; - clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom); + 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; @@ -1665,6 +1765,8 @@ void OpenGLRenderer::setupDraw(bool clear) { mDescription.hasDebugHighlight = !mCaches.debugOverdraw && mCaches.debugStencilClip == Caches::kStencilShowHighlight && mCaches.stencil.isTestEnabled(); + + mDescription.emulateStencil = mCountOverdraw; } void OpenGLRenderer::setupDrawWithTexture(bool isAlpha8) { @@ -1690,11 +1792,6 @@ void OpenGLRenderer::setupDrawAA() { mDescription.isAA = true; } -void OpenGLRenderer::setupDrawPoint(float pointSize) { - mDescription.isPoint = true; - mDescription.pointSize = pointSize; -} - void OpenGLRenderer::setupDrawColor(int color, int alpha) { mColorA = alpha / 255.0f; mColorR = mColorA * ((color >> 16) & 0xFF) / 255.0f; @@ -1809,11 +1906,6 @@ void OpenGLRenderer::setupDrawModelView(float left, float top, float right, floa } } -void OpenGLRenderer::setupDrawPointUniforms() { - int slot = mCaches.currentProgram->getUniform("pointSize"); - glUniform1f(slot, mDescription.pointSize); -} - void OpenGLRenderer::setupDrawColorUniforms() { if ((mColorSet && !mDrawModifiers.mShader) || (mDrawModifiers.mShader && mSetShaderColor)) { mCaches.currentProgram->setColor(mColorR, mColorG, mColorB, mColorA); @@ -1882,7 +1974,7 @@ void OpenGLRenderer::setupDrawTextureTransformUniforms(mat4& transform) { void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) { bool force = false; - if (!vertices) { + if (!vertices || vbo) { force = mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo); } else { force = mCaches.unbindMeshBuffer(); @@ -1913,21 +2005,28 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLvoid* mCaches.unbindIndicesBuffer(); } -void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords) { - bool force = mCaches.unbindMeshBuffer(); +void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, 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 + // use the default VBO found in Caches + if (!vertices || vbo) { + force = mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo); + } else { + force = mCaches.unbindMeshBuffer(); + } + mCaches.bindIndicesBuffer(); + mCaches.bindPositionVertexPointer(force, vertices); if (mCaches.currentProgram->texCoords >= 0) { mCaches.bindTexCoordsVertexPointer(force, texCoords); } } -void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) { +void OpenGLRenderer::setupDrawIndexedVertices(GLvoid* vertices) { bool force = mCaches.unbindMeshBuffer(); + mCaches.bindIndicesBuffer(); mCaches.bindPositionVertexPointer(force, vertices, gVertexStride); - mCaches.unbindIndicesBuffer(); -} - -void OpenGLRenderer::finishDrawTexture() { } /////////////////////////////////////////////////////////////////////////////// @@ -1947,7 +2046,8 @@ status_t OpenGLRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty, return status | replayStruct.mDrawGlStatus; } - DeferredDisplayList deferredList; + bool avoidOverdraw = !mCaches.debugOverdraw && !mCountOverdraw; // shh, don't tell devs! + DeferredDisplayList deferredList(*(mSnapshot->clipRect), avoidOverdraw); DeferStateStruct deferStruct(deferredList, *this, replayFlags); displayList->defer(deferStruct, 0); @@ -1989,20 +2089,24 @@ void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, Sk texture->setFilter(FILTER(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, GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform); + paint != NULL, color, alpha, mode, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset, + GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform); } -status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureVertex* vertices, - const Rect& bounds, SkPaint* paint) { - - // merged draw operations don't need scissor, but clip should still be valid - mCaches.setScissorEnabled(mScissorOptimizationDisabled); - +/** + * Important note: this method is intended to draw batches of bitmaps and + * 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) { mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap); if (!texture) return DrawGlInfo::kStatusDone; + const AutoTexture autoCleanup(texture); int alpha; @@ -2010,7 +2114,7 @@ status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureV getAlphaAndMode(paint, &alpha, &mode); texture->setWrap(GL_CLAMP_TO_EDGE, true); - texture->setFilter(GL_NEAREST, true); // merged ops are always pure-translation for now + texture->setFilter(pureTranslate ? GL_NEAREST : FILTER(paint), true); const float x = (int) floorf(bounds.left + 0.5f); const float y = (int) floorf(bounds.top + 0.5f); @@ -2019,12 +2123,12 @@ status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureV 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); + GL_TRIANGLES, bitmapCount * 6, true, true, 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); + GL_TRIANGLES, bitmapCount * 6, false, true, 0, true, false); } return DrawGlInfo::kStatusDrew; @@ -2039,7 +2143,7 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkP } mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); @@ -2062,7 +2166,7 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* } mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); @@ -2107,6 +2211,9 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes return DrawGlInfo::kStatusDone; } + // TODO: use quickReject on bounds from vertices + mCaches.enableScissor(); + float left = FLT_MAX; float top = FLT_MAX; float right = FLT_MIN; @@ -2125,6 +2232,10 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes cleanupColors = true; } + mCaches.activeTexture(0); + Texture* texture = mCaches.assetAtlas.getEntryTexture(bitmap); + const UvMapper& mapper(getMapper(texture)); + for (int32_t y = 0; y < meshHeight; y++) { for (int32_t x = 0; x < meshWidth; x++) { uint32_t i = (y * (meshWidth + 1) + x) * 2; @@ -2134,6 +2245,8 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes float v1 = float(y) / meshHeight; float v2 = float(y + 1) / meshHeight; + mapper.map(u1, v1, u2, v2); + int ax = i + (meshWidth + 1) * 2; int ay = ax + 1; int bx = i; @@ -2163,11 +2276,12 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes return DrawGlInfo::kStatusDone; } - mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); if (!texture) { - if (cleanupColors) delete[] colors; - return DrawGlInfo::kStatusDone; + texture = mCaches.textureCache.get(bitmap); + if (!texture) { + if (cleanupColors) delete[] colors; + return DrawGlInfo::kStatusDone; + } } const AutoTexture autoCleanup(texture); @@ -2199,8 +2313,6 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes glDrawArrays(GL_TRIANGLES, 0, count); - finishDrawTexture(); - int slot = mCaches.currentProgram->getAttrib("colors"); if (slot >= 0) { glDisableVertexAttribArray(slot); @@ -2220,17 +2332,19 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, } mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); const float width = texture->width; const float height = texture->height; - const float u1 = fmax(0.0f, srcLeft / width); - const float v1 = fmax(0.0f, srcTop / height); - const float u2 = fmin(1.0f, srcRight / width); - const float v2 = fmin(1.0f, srcBottom / height); + float u1 = fmax(0.0f, srcLeft / width); + float v1 = fmax(0.0f, srcTop / height); + float u2 = fmin(1.0f, srcRight / width); + float v2 = fmin(1.0f, srcBottom / height); + + getMapper(texture).map(u1, v1, u2, v2); mCaches.unbindMeshBuffer(); resetDrawTextureTexCoords(u1, v1, u2, v2); @@ -2301,37 +2415,38 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, return DrawGlInfo::kStatusDrew; } -status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, +status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top, float right, float bottom, SkPaint* paint) { - int alpha; - SkXfermode::Mode mode; - getAlphaAndMode(paint, &alpha, &mode); + if (quickReject(left, top, right, bottom)) { + return DrawGlInfo::kStatusDone; + } + + AssetAtlas::Entry* entry = mCaches.assetAtlas.getEntry(bitmap); + const Patch* mesh = mCaches.patchCache.get(entry, bitmap->width(), bitmap->height(), + right - left, bottom - top, patch); - return drawPatch(bitmap, xDivs, yDivs, colors, width, height, numColors, - left, top, right, bottom, alpha, mode); + return drawPatch(bitmap, mesh, entry, left, top, right, bottom, paint); } -status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, - float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode) { +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)) { return DrawGlInfo::kStatusDone; } - alpha *= mSnapshot->alpha; - - const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(), - right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors); - if (CC_LIKELY(mesh && mesh->verticesCount > 0)) { mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); + 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(); // Mark the current layer dirty where we are going to draw the patch if (hasLayer() && mesh->hasEmptyQuads) { @@ -2355,24 +2470,52 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f); const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f); - drawTextureMesh(x, y, x + right - left, y + bottom - top, texture->id, alpha / 255.0f, - mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset, - GL_TRIANGLES, mesh->verticesCount, false, true, mesh->meshBuffer, - true, !mesh->hasEmptyQuads); + 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 { - drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, - mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset, - GL_TRIANGLES, mesh->verticesCount, false, false, mesh->meshBuffer, - true, !mesh->hasEmptyQuads); + 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); } } return DrawGlInfo::kStatusDrew; } +/** + * Important note: this method is intended to draw batches of 9-patch objects and + * 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) { + mCaches.activeTexture(0); + Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap); + if (!texture) return DrawGlInfo::kStatusDone; + const AutoTexture autoCleanup(texture); + + 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); + + return DrawGlInfo::kStatusDrew; +} + status_t OpenGLRenderer::drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPaint* paint, bool useOffset) { - if (!vertexBuffer.getSize()) { + if (!vertexBuffer.getVertexCount()) { // no vertices to draw return DrawGlInfo::kStatusDone; } @@ -2410,7 +2553,7 @@ status_t OpenGLRenderer::drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPa glVertexAttribPointer(alphaSlot, 1, GL_FLOAT, GL_FALSE, gAlphaVertexStride, alphaCoords); } - glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getSize()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexBuffer.getVertexCount()); if (isAA) { glDisableVertexAttribArray(alphaSlot); @@ -2473,65 +2616,22 @@ status_t OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { } status_t OpenGLRenderer::drawPoints(float* points, int count, SkPaint* paint) { - if (mSnapshot->isIgnored()) return DrawGlInfo::kStatusDone; - - // TODO: The paint's cap style defines whether the points are square or circular - // TODO: Handle AA for round points - - // A stroke width of 0 has a special meaning in Skia: - // it draws an unscaled 1px point - float strokeWidth = paint->getStrokeWidth(); - const bool isHairLine = paint->getStrokeWidth() == 0.0f; - if (isHairLine) { - // Now that we know it's hairline, we can set the effective width, to be used later - strokeWidth = 1.0f; - } - const float halfWidth = strokeWidth / 2; - - int alpha; - SkXfermode::Mode mode; - getAlphaAndMode(paint, &alpha, &mode); - - int verticesCount = count >> 1; - int generatedVerticesCount = 0; - - TextureVertex pointsData[verticesCount]; - TextureVertex* vertex = &pointsData[0]; - - // TODO: We should optimize this method to not generate vertices for points - // that lie outside of the clip. - mCaches.enableScissor(); - - setupDraw(); - setupDrawNoTexture(); - setupDrawPoint(strokeWidth); - setupDrawColor(paint->getColor(), alpha); - setupDrawColorFilter(); - setupDrawShader(); - setupDrawBlending(mode); - setupDrawProgram(); - setupDrawModelViewIdentity(true); - setupDrawColorUniforms(); - setupDrawColorFilterUniforms(); - setupDrawPointUniforms(); - setupDrawShaderIdentityUniforms(); - setupDrawMesh(vertex); + if (mSnapshot->isIgnored() || count < 2) return DrawGlInfo::kStatusDone; - for (int i = 0; i < count; i += 2) { - TextureVertex::set(vertex++, points[i], points[i + 1], 0.0f, 0.0f); - generatedVerticesCount++; + count &= ~0x1; // round down to nearest two - float left = points[i] - halfWidth; - float right = points[i] + halfWidth; - float top = points[i + 1] - halfWidth; - float bottom = points [i + 1] + halfWidth; + VertexBuffer buffer; + SkRect bounds; + PathTessellator::tessellatePoints(points, count, paint, mSnapshot->transform, bounds, buffer); - dirtyLayer(left, top, right, bottom, currentTransform()); + if (quickReject(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom)) { + return DrawGlInfo::kStatusDone; } - glDrawArrays(GL_POINTS, 0, generatedVerticesCount); + dirtyLayer(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, currentTransform()); - return DrawGlInfo::kStatusDrew; + bool useOffset = !paint->isAntiAlias(); + return drawVertexBuffer(buffer, paint, useOffset); } status_t OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) { @@ -2747,48 +2847,6 @@ bool OpenGLRenderer::canSkipText(const SkPaint* paint) const { return alpha == 0.0f && getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode; } -class TextSetupFunctor: public Functor { -public: - TextSetupFunctor(OpenGLRenderer& renderer, float x, float y, bool pureTranslate, - int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(), - renderer(renderer), x(x), y(y), pureTranslate(pureTranslate), - alpha(alpha), mode(mode), paint(paint) { - } - ~TextSetupFunctor() { } - - status_t operator ()(int what, void* data) { - renderer.setupDraw(); - renderer.setupDrawTextGamma(paint); - renderer.setupDrawDirtyRegionsDisabled(); - renderer.setupDrawWithTexture(true); - renderer.setupDrawAlpha8Color(paint->getColor(), alpha); - renderer.setupDrawColorFilter(); - renderer.setupDrawShader(); - renderer.setupDrawBlending(true, mode); - renderer.setupDrawProgram(); - renderer.setupDrawModelView(x, y, x, y, pureTranslate, true); - // 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.setupDrawTextGammaUniforms(); - - return NO_ERROR; - } - - OpenGLRenderer& renderer; - float x; - float y; - bool pureTranslate; - int alpha; - SkXfermode::Mode mode; - SkPaint* paint; -}; - 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)) { @@ -2800,6 +2858,8 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count return DrawGlInfo::kStatusDone; } + mCaches.enableScissor(); + float x = 0.0f; float y = 0.0f; const bool pureTranslate = currentTransform().isPureTranslate(); @@ -2832,7 +2892,7 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count const bool hasActiveLayer = hasLayer(); - TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint); + TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, positions, hasActiveLayer ? &bounds : NULL, &functor)) { if (hasActiveLayer) { @@ -2862,36 +2922,17 @@ mat4 OpenGLRenderer::findBestFontTransform(const mat4& transform) const { return fontTransform; } -status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, - float x, float y, const float* positions, SkPaint* paint, float length, +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, DrawOpMode drawOpMode) { - if (drawOpMode == kDrawOpMode_Immediate && - (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint))) { - return DrawGlInfo::kStatusDone; - } - - if (length < 0.0f) length = paint->measureText(text, bytesCount); - switch (paint->getTextAlign()) { - case SkPaint::kCenter_Align: - x -= length / 2.0f; - break; - case SkPaint::kRight_Align: - x -= length; - break; - default: - break; - } - - SkPaint::FontMetrics metrics; - paint->getFontMetrics(&metrics, 0.0f); if (drawOpMode == kDrawOpMode_Immediate) { - if (quickReject(x, y + metrics.fTop, x + length, y + metrics.fBottom)) { + // 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)) { return DrawGlInfo::kStatusDone; } - } else { - // merged draw operations don't need scissor, but clip should still be valid - mCaches.setScissorEnabled(mScissorOptimizationDisabled); } const float oldX = x; @@ -2939,10 +2980,10 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, // TODO: Implement better clipping for scaled/rotated text const Rect* clip = !pureTranslate ? NULL : mSnapshot->clipRect; - Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); + Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); bool status; - TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint); + TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); // don't call issuedrawcommand, do it at end of batch bool forceFinish = (drawOpMode != kDrawOpMode_Defer); @@ -2950,20 +2991,20 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count, SkPaint paintCopy(*paint); paintCopy.setTextAlign(SkPaint::kLeft_Align); status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y, - positions, hasActiveLayer ? &bounds : NULL, &functor, forceFinish); + positions, hasActiveLayer ? &layerBounds : NULL, &functor, forceFinish); } else { status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, - positions, hasActiveLayer ? &bounds : NULL, &functor, forceFinish); + positions, hasActiveLayer ? &layerBounds : NULL, &functor, forceFinish); } if ((status || drawOpMode != kDrawOpMode_Immediate) && hasActiveLayer) { if (!pureTranslate) { - transform.mapRect(bounds); + transform.mapRect(layerBounds); } - dirtyLayerUnchecked(bounds, getRegion()); + dirtyLayerUnchecked(layerBounds, getRegion()); } - drawTextDecorations(text, bytesCount, length, oldX, oldY, paint); + drawTextDecorations(text, bytesCount, totalAdvance, oldX, oldY, paint); return DrawGlInfo::kStatusDrew; } @@ -2974,6 +3015,9 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co return DrawGlInfo::kStatusDone; } + // TODO: avoid scissor by calculating maximum bounds using path bounds + font metrics + mCaches.enableScissor(); + FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); fontRenderer.setFont(paint, mat4::identity()); fontRenderer.setTextureFiltering(true); @@ -2981,26 +3025,7 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co int alpha; SkXfermode::Mode mode; getAlphaAndMode(paint, &alpha, &mode); - - setupDraw(); - setupDrawTextGamma(paint); - setupDrawDirtyRegionsDisabled(); - setupDrawWithTexture(true); - setupDrawAlpha8Color(paint->getColor(), alpha); - setupDrawColorFilter(); - setupDrawShader(); - setupDrawBlending(true, mode); - setupDrawProgram(); - setupDrawModelView(0.0f, 0.0f, 0.0f, 0.0f, false, true); - // 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 - setupDrawTexture(0); - setupDrawPureColorUniforms(); - setupDrawColorFilterUniforms(); - setupDrawShaderUniforms(false); - setupDrawTextGammaUniforms(); + TextSetupFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint); const Rect* clip = &mSnapshot->getLocalClip(); Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); @@ -3008,7 +3033,7 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co const bool hasActiveLayer = hasLayer(); if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path, - hOffset, vOffset, hasActiveLayer ? &bounds : NULL)) { + hOffset, vOffset, hasActiveLayer ? &bounds : NULL, &functor)) { if (hasActiveLayer) { currentTransform().mapRect(bounds); dirtyLayerUnchecked(bounds, getRegion()); @@ -3049,10 +3074,9 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { } } - Rect transformed; - Rect clip; + bool clipRequired = false; const bool rejected = quickRejectNoScissor(x, y, - x + layer->layer.getWidth(), y + layer->layer.getHeight(), transformed, clip); + x + layer->layer.getWidth(), y + layer->layer.getHeight(), false, &clipRequired); if (rejected) { if (transform && !transform->isIdentity()) { @@ -3063,7 +3087,7 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { updateLayer(layer, true); - mCaches.setScissorEnabled(mScissorOptimizationDisabled || !clip.contains(transformed)); + mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired); mCaches.activeTexture(0); if (CC_LIKELY(!layer->region.isEmpty())) { @@ -3096,13 +3120,22 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { setupDrawModelViewTranslate(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight()); } - setupDrawMesh(&layer->mesh[0].position[0], &layer->mesh[0].texture[0]); - DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, - glDrawElements(GL_TRIANGLES, layer->meshElementCount, - GL_UNSIGNED_SHORT, layer->meshIndices)); + TextureVertex* mesh = &layer->mesh[0]; + GLsizei elementsCount = layer->meshElementCount; + + while (elementsCount > 0) { + GLsizei drawCount = min(elementsCount, (GLsizei) gMaxNumberOfQuads * 6); - finishDrawTexture(); + setupDrawMeshIndices(&mesh[0].position[0], &mesh[0].texture[0]); + DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, + glDrawElements(GL_TRIANGLES, drawCount, GL_UNSIGNED_SHORT, NULL)); + + elementsCount -= drawCount; + // Though there are 4 vertices in a quad, we use 6 indices per + // quad to draw with GL_TRIANGLES + mesh += (drawCount / 6) * 4; + } #if DEBUG_LAYERS_AS_REGIONS drawRegionRects(layer->region); @@ -3137,7 +3170,7 @@ void OpenGLRenderer::resetShader() { void OpenGLRenderer::setupShader(SkiaShader* shader) { mDrawModifiers.mShader = shader; if (mDrawModifiers.mShader) { - mDrawModifiers.mShader->set(&mCaches.textureCache, &mCaches.gradientCache); + mDrawModifiers.mShader->setCaches(mCaches); } } @@ -3205,6 +3238,14 @@ SkPaint* OpenGLRenderer::filterPaint(SkPaint* paint) { // Drawing implementation /////////////////////////////////////////////////////////////////////////////// +Texture* OpenGLRenderer::getTexture(SkBitmap* bitmap) { + Texture* texture = mCaches.assetAtlas.getEntryTexture(bitmap); + if (!texture) { + return mCaches.textureCache.get(bitmap); + } + return texture; +} + void OpenGLRenderer::drawPathTexture(const PathTexture* texture, float x, float y, SkPaint* paint) { if (quickReject(x, y, x + texture->width, y + texture->height)) { @@ -3230,8 +3271,6 @@ void OpenGLRenderer::drawPathTexture(const PathTexture* texture, setupDrawMesh(NULL, (GLvoid*) gMeshTextureOffset); glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); - - finishDrawTexture(); } // Same values used by Skia @@ -3239,17 +3278,12 @@ 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 length, +void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float underlineWidth, float x, float y, SkPaint* paint) { // Handle underline and strike-through uint32_t flags = paint->getFlags(); if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { SkPaint paintCopy(*paint); - float underlineWidth = length; - // If length is > 0.0f, we already measured the text for the text alignment - if (length <= 0.0f) { - underlineWidth = paintCopy.measureText(text, bytesCount); - } if (CC_LIKELY(underlineWidth > 0.0f)) { const float textSize = paintCopy.getTextSize(); @@ -3315,8 +3349,7 @@ status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color float right = FLT_MIN; float bottom = FLT_MIN; - int vertexCount = 0; - Vertex mesh[count * 6]; + Vertex mesh[count]; Vertex* vertex = mesh; for (int index = 0; index < count; index += 4) { @@ -3325,15 +3358,11 @@ status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color float r = rects[index + 2]; float b = rects[index + 3]; - Vertex::set(vertex++, l, b); Vertex::set(vertex++, l, t); Vertex::set(vertex++, r, t); Vertex::set(vertex++, l, b); - Vertex::set(vertex++, r, t); Vertex::set(vertex++, r, b); - vertexCount += 6; - left = fminf(left, l); top = fminf(top, t); right = fmaxf(right, r); @@ -3356,13 +3385,12 @@ status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color setupDrawColorUniforms(); setupDrawShaderUniforms(); setupDrawColorFilterUniforms(); - setupDrawVertices((GLvoid*) &mesh[0].position[0]); if (dirty && hasLayer()) { dirtyLayer(left, top, right, bottom, currentTransform()); } - glDrawArrays(GL_TRIANGLES, 0, vertexCount); + drawIndexedQuads(&mesh[0], count / 4); return DrawGlInfo::kStatusDrew; } @@ -3398,19 +3426,35 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b 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]; + + Rect uvs(0.0f, 0.0f, 1.0f, 1.0f); + texture->uvMapper->map(uvs); + + 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); texture->setFilter(GL_NEAREST, true); drawTextureMesh(x, y, x + texture->width, y + texture->height, texture->id, - alpha / 255.0f, mode, texture->blend, (GLvoid*) NULL, - (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, false, true); + alpha / 255.0f, mode, 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->blend, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset, - GL_TRIANGLE_STRIP, gMeshCount); + texture->blend, vertices, texCoords, GL_TRIANGLE_STRIP, gMeshCount); + } + + if (texture->uvMapper) { + resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f); } } @@ -3443,8 +3487,31 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b 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, + GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount, + bool swapSrcDst, bool ignoreTransform, GLuint vbo, bool ignoreScale, bool dirty) { - finishDrawTexture(); + setupDraw(); + setupDrawWithTexture(); + setupDrawColor(alpha, alpha, alpha, alpha); + setupDrawColorFilter(); + setupDrawBlending(blend, mode, swapSrcDst); + setupDrawProgram(); + if (!dirty) setupDrawDirtyRegionsDisabled(); + if (!ignoreScale) { + setupDrawModelView(left, top, right, bottom, ignoreTransform); + } else { + setupDrawModelViewTranslate(left, top, right, bottom, ignoreTransform); + } + setupDrawTexture(texture); + setupDrawPureColorUniforms(); + setupDrawColorFilterUniforms(); + setupDrawMeshIndices(vertices, texCoords, vbo); + + glDrawElements(drawMode, elementsCount, GL_UNSIGNED_SHORT, NULL); } void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, float bottom, @@ -3474,12 +3541,23 @@ void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, f setupDrawMesh(vertices, texCoords); glDrawArrays(drawMode, 0, elementsCount); - - finishDrawTexture(); } void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description, bool swapSrcDst) { + if (mCountOverdraw) { + if (!mCaches.blend) glEnable(GL_BLEND); + if (mCaches.lastSrcMode != GL_ONE || mCaches.lastDstMode != GL_ONE) { + glBlendFunc(GL_ONE, GL_ONE); + } + + mCaches.blend = true; + mCaches.lastSrcMode = GL_ONE; + mCaches.lastDstMode = GL_ONE; + + return; + } + blend = blend || mode != SkXfermode::kSrcOver_Mode; if (blend) { diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index a0ad888..9afb7ad 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -34,6 +34,8 @@ #include <cutils/compiler.h> +#include <androidfw/ResourceTypes.h> + #include "Debug.h" #include "Extensions.h" #include "Matrix.h" @@ -43,12 +45,21 @@ #include "Vertex.h" #include "SkiaShader.h" #include "SkiaColorFilter.h" +#include "UvMapper.h" #include "Caches.h" namespace android { namespace uirenderer { struct DrawModifiers { + DrawModifiers() { + reset(); + } + + void reset() { + memset(this, 0, sizeof(DrawModifiers)); + } + SkiaShader* mShader; SkiaColorFilter* mColorFilter; float mOverrideLayerAlpha; @@ -77,21 +88,21 @@ enum DrawOpMode { kDrawOpMode_Flush }; -struct DeferredDisplayState { - Rect mBounds; // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped. - - // the below are set and used by the OpenGLRenderer at record and deferred playback - bool mClipValid; - Rect mClip; - mat4 mMatrix; - DrawModifiers mDrawModifiers; - float mAlpha; +enum ClipSideFlags { + kClipSide_None = 0x0, + kClipSide_Left = 0x1, + kClipSide_Top = 0x2, + kClipSide_Right = 0x4, + kClipSide_Bottom = 0x8, + kClipSide_Full = 0xF, + kClipSide_ConservativeFull = 0x1F }; /////////////////////////////////////////////////////////////////////////////// // Renderer /////////////////////////////////////////////////////////////////////////////// +class DeferredDisplayState; class DisplayList; class TextSetupFunctor; class VertexBuffer; @@ -188,13 +199,23 @@ public: */ virtual void resume(); + ANDROID_API void setCountOverdrawEnabled(bool enabled) { + mCountOverdraw = enabled; + } + + ANDROID_API 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); @@ -228,8 +249,32 @@ public: virtual void concatMatrix(SkMatrix* matrix); ANDROID_API const Rect& getClipBounds(); - ANDROID_API bool quickReject(float left, float top, float right, float bottom); - bool quickRejectNoScissor(float left, float top, float right, float bottom); + + /** + * 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); + + /** + * 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); + } + + /** + * 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); + } + 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); @@ -239,8 +284,8 @@ public: virtual void outputDisplayList(DisplayList* displayList); 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, int bitmapCount, TextureVertex* vertices, - const Rect& bounds, 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, float srcRight, float srcBottom, float dstLeft, float dstTop, @@ -248,12 +293,12 @@ public: 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, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, + 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); - status_t drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, - float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode); 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, @@ -270,7 +315,7 @@ public: 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 length = -1.0f, + const float* positions, SkPaint* paint, float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode = kDrawOpMode_Immediate); virtual status_t drawRects(const float* rects, int count, SkPaint* paint); @@ -291,9 +336,15 @@ public: SkPaint* filterPaint(SkPaint* paint); + /** + * Store the current display state (most importantly, the current clip and transform), and + * additionally map the state's bounds from local to window coordinates. + * + * Returns true if quick-rejected + */ bool storeDisplayState(DeferredDisplayState& state, int stateDeferFlags); void restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore = false); - void setFullScreenClip(); + void setupMergedMultiDraw(const Rect* clipRect); const DrawModifiers& getDrawModifiers() { return mDrawModifiers; } void setDrawModifiers(const DrawModifiers& drawModifiers) { mDrawModifiers = drawModifiers; } @@ -311,6 +362,9 @@ public: return mSnapshot->clipRegion->isEmpty(); } + int getViewportWidth() { return getSnapshot()->viewport.getWidth(); } + int getViewportHeight() { return getSnapshot()->viewport.getHeight(); } + /** * Scales the alpha on the current snapshot. This alpha value will be modulated * with other alpha values when drawing primitives. @@ -356,7 +410,7 @@ public: return getXfermode(paint->getXfermode()); } - static inline int getAlphaDirect(SkPaint* paint) { + static inline int getAlphaDirect(const SkPaint* paint) { if (!paint) return 255; return paint->getAlpha(); } @@ -579,18 +633,6 @@ private: void setStencilFromClip(); /** - * Performs a quick reject but does not affect the scissor. Returns - * the transformed rect to test and the current clip. - */ - bool quickRejectNoScissor(float left, float top, float right, float bottom, - Rect& transformed, Rect& clip); - - /** - * Performs a quick reject but adjust the bounds to account for stroke width if necessary - */ - bool quickRejectPreStroke(float left, float top, float right, float bottom, SkPaint* paint); - - /** * Given the local bounds of the layer, calculates ... */ void calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool fboLayer); @@ -798,22 +840,35 @@ private: bool swapSrcDst = false, bool ignoreTransform = false, GLuint vbo = 0, bool ignoreScale = false, bool dirty = true); + void drawIndexedTextureMesh(float left, float top, float right, float bottom, GLuint texture, + float alpha, SkXfermode::Mode mode, 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); + void drawAlpha8TextureMesh(float left, float top, float right, float bottom, GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode, GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount, bool ignoreTransform, bool ignoreScale = false, 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); + + /** * Draws text underline and strike-through if needed. * * @param text The text to decor * @param bytesCount The number of bytes in the text - * @param length The length in pixels of the text, can be <= 0.0f to force a measurement + * @param totalAdvance The total advance in pixels, defines underline/strikethrough length * @param x The x coordinate where the text will be drawn * @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 length, + void drawTextDecorations(const char* text, int bytesCount, float totalAdvance, float x, float y, SkPaint* paint); /** @@ -868,7 +923,7 @@ private: * prior to calling this method. */ inline void bindTexture(GLuint texture) { - glBindTexture(GL_TEXTURE_2D, texture); + mCaches.bindTexture(texture); } /** @@ -876,7 +931,7 @@ private: * prior to calling this method. */ inline void bindExternalTexture(GLuint texture) { - glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture); + mCaches.bindTexture(GL_TEXTURE_EXTERNAL_OES, texture); } /** @@ -911,7 +966,6 @@ private: void setupDrawWithExternalTexture(); void setupDrawNoTexture(); void setupDrawAA(); - void setupDrawPoint(float pointSize); void setupDrawColor(int color, int alpha); void setupDrawColor(float r, float g, float b, float a); void setupDrawAlpha8Color(int color, int alpha); @@ -929,7 +983,6 @@ private: bool ignoreTransform = false, bool ignoreModelView = false); void setupDrawModelViewTranslate(float left, float top, float right, float bottom, bool ignoreTransform = false); - void setupDrawPointUniforms(); void setupDrawColorUniforms(); void setupDrawPureColorUniforms(); void setupDrawShaderIdentityUniforms(); @@ -943,9 +996,8 @@ private: 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); - void setupDrawVertices(GLvoid* vertices); - void finishDrawTexture(); + void setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, GLuint vbo = 0); + void setupDrawIndexedVertices(GLvoid* vertices); void accountForClear(SkXfermode::Mode mode); bool updateLayer(Layer* layer, bool inFrame); @@ -973,6 +1025,7 @@ private: void debugOverdraw(bool enable, bool clear); void renderOverdraw(); + void countOverdraw(); /** * Should be invoked every time the glScissor is modified. @@ -985,6 +1038,17 @@ private: return *mSnapshot->transform; } + inline const UvMapper& getMapper(const Texture* texture) { + return texture && texture->uvMapper ? *texture->uvMapper : mUvMapper; + } + + /** + * Returns a texture object for the specified bitmap. The texture can + * 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); + // Dimensions of the drawing surface int mWidth, mHeight; @@ -1010,6 +1074,9 @@ private: // Used to draw textured quads TextureVertex mMeshVertices[4]; + // Default UV mapper + const UvMapper mUvMapper; + // shader, filters, and shadow DrawModifiers mDrawModifiers; SkPaint mFilteredPaint; @@ -1050,12 +1117,19 @@ private: // No-ops start/endTiling when set bool mSuppressTiling; + // If true, this renderer will setup drawing to emulate + // an increment stencil buffer in the color buffer + bool mCountOverdraw; + float mOverdraw; + // Optional name of the renderer String8 mName; friend class DisplayListRenderer; friend class Layer; friend class TextSetupFunctor; + friend class DrawBitmapOp; + friend class DrawPatchOp; }; // class OpenGLRenderer diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp index 45c619e..9b023f9 100644 --- a/libs/hwui/Patch.cpp +++ b/libs/hwui/Patch.cpp @@ -20,9 +20,10 @@ #include <utils/Log.h> -#include "Patch.h" #include "Caches.h" +#include "Patch.h" #include "Properties.h" +#include "UvMapper.h" namespace android { namespace uirenderer { @@ -31,90 +32,58 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -Patch::Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQuads): - mXCount(xCount), mYCount(yCount), mEmptyQuads(emptyQuads) { - // Initialized with the maximum number of vertices we will need - // 2 triangles per patch, 3 vertices per triangle - uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 2 * 3; - mVertices = new TextureVertex[maxVertices]; - mAllocatedVerticesCount = 0; - - verticesCount = 0; - hasEmptyQuads = emptyQuads > 0; - - mColorKey = 0; - mXDivs = new int32_t[mXCount]; - mYDivs = new int32_t[mYCount]; - - PATCH_LOGD(" patch: xCount = %d, yCount = %d, emptyQuads = %d, max vertices = %d", - xCount, yCount, emptyQuads, maxVertices); - - glGenBuffers(1, &meshBuffer); +Patch::Patch(): vertices(NULL), verticesCount(0), indexCount(0), hasEmptyQuads(false) { } Patch::~Patch() { - delete[] mVertices; - delete[] mXDivs; - delete[] mYDivs; - glDeleteBuffers(1, &meshBuffer); } /////////////////////////////////////////////////////////////////////////////// -// Patch management +// Vertices management /////////////////////////////////////////////////////////////////////////////// -void Patch::copy(const int32_t* xDivs, const int32_t* yDivs) { - memcpy(mXDivs, xDivs, mXCount * sizeof(int32_t)); - memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t)); +uint32_t Patch::getSize() const { + return verticesCount * sizeof(TextureVertex); } -void Patch::updateColorKey(const uint32_t colorKey) { - mColorKey = colorKey; +TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight, + float width, float height, const Res_png_9patch* patch) { + UvMapper mapper; + return createMesh(bitmapWidth, bitmapHeight, width, height, mapper, patch); } -bool Patch::matches(const int32_t* xDivs, const int32_t* yDivs, - const uint32_t colorKey, const int8_t emptyQuads) { +TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight, + float width, float height, const UvMapper& mapper, const Res_png_9patch* patch) { + if (vertices) return vertices; - bool matches = true; + int8_t emptyQuads = 0; + mColors = patch->colors; - if (mEmptyQuads != emptyQuads) { - mEmptyQuads = emptyQuads; - hasEmptyQuads = emptyQuads > 0; - matches = false; - } - - if (mColorKey != colorKey) { - updateColorKey(colorKey); - matches = false; - } - - if (memcmp(mXDivs, xDivs, mXCount * sizeof(int32_t))) { - memcpy(mXDivs, xDivs, mXCount * sizeof(int32_t)); - matches = false; + const int8_t numColors = patch->numColors; + if (uint8_t(numColors) < sizeof(uint32_t) * 4) { + for (int8_t i = 0; i < numColors; i++) { + if (mColors[i] == 0x0) { + emptyQuads++; + } + } } - if (memcmp(mYDivs, yDivs, mYCount * sizeof(int32_t))) { - memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t)); - matches = false; - } + hasEmptyQuads = emptyQuads > 0; - return matches; -} + uint32_t xCount = patch->numXDivs; + uint32_t yCount = patch->numYDivs; -/////////////////////////////////////////////////////////////////////////////// -// Vertices management -/////////////////////////////////////////////////////////////////////////////// + uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 4; + if (maxVertices == 0) return NULL; -void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, - float left, float top, float right, float bottom) { - if (hasEmptyQuads) quads.clear(); + TextureVertex* tempVertices = new TextureVertex[maxVertices]; + TextureVertex* vertex = tempVertices; - // Reset the vertices count here, we will count exactly how many - // vertices we actually need when generating the quads - verticesCount = 0; + const int32_t* xDivs = patch->xDivs; + const int32_t* yDivs = patch->yDivs; - const uint32_t xStretchCount = (mXCount + 1) >> 1; - const uint32_t yStretchCount = (mYCount + 1) >> 1; + const uint32_t xStretchCount = (xCount + 1) >> 1; + const uint32_t yStretchCount = (yCount + 1) >> 1; float stretchX = 0.0f; float stretchY = 0.0f; @@ -124,29 +93,28 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, if (xStretchCount > 0) { uint32_t stretchSize = 0; - for (uint32_t i = 1; i < mXCount; i += 2) { - stretchSize += mXDivs[i] - mXDivs[i - 1]; + for (uint32_t i = 1; i < xCount; i += 2) { + stretchSize += xDivs[i] - xDivs[i - 1]; } const float xStretchTex = stretchSize; const float fixed = bitmapWidth - stretchSize; - const float xStretch = fmaxf(right - left - fixed, 0.0f); + const float xStretch = fmaxf(width - fixed, 0.0f); stretchX = xStretch / xStretchTex; - rescaleX = fixed == 0.0f ? 0.0f : fminf(fmaxf(right - left, 0.0f) / fixed, 1.0f); + rescaleX = fixed == 0.0f ? 0.0f : fminf(fmaxf(width, 0.0f) / fixed, 1.0f); } if (yStretchCount > 0) { uint32_t stretchSize = 0; - for (uint32_t i = 1; i < mYCount; i += 2) { - stretchSize += mYDivs[i] - mYDivs[i - 1]; + for (uint32_t i = 1; i < yCount; i += 2) { + stretchSize += yDivs[i] - yDivs[i - 1]; } const float yStretchTex = stretchSize; const float fixed = bitmapHeight - stretchSize; - const float yStretch = fmaxf(bottom - top - fixed, 0.0f); + const float yStretch = fmaxf(height - fixed, 0.0f); stretchY = yStretch / yStretchTex; - rescaleY = fixed == 0.0f ? 0.0f : fminf(fmaxf(bottom - top, 0.0f) / fixed, 1.0f); + rescaleY = fixed == 0.0f ? 0.0f : fminf(fmaxf(height, 0.0f) / fixed, 1.0f); } - TextureVertex* vertex = mVertices; uint32_t quadCount = 0; float previousStepY = 0.0f; @@ -155,8 +123,10 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, float y2 = 0.0f; float v1 = 0.0f; - for (uint32_t i = 0; i < mYCount; i++) { - float stepY = mYDivs[i]; + mUvMapper = mapper; + + for (uint32_t i = 0; i < yCount; i++) { + float stepY = yDivs[i]; const float segment = stepY - previousStepY; if (i & 1) { @@ -170,15 +140,8 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, v1 += vOffset / bitmapHeight; if (stepY > 0.0f) { -#if DEBUG_EXPLODE_PATCHES - y1 += i * EXPLODE_GAP; - y2 += i * EXPLODE_GAP; -#endif - generateRow(vertex, y1, y2, v1, v2, stretchX, rescaleX, right - left, - bitmapWidth, quadCount); -#if DEBUG_EXPLODE_PATCHES - y2 -= i * EXPLODE_GAP; -#endif + generateRow(xDivs, xCount, vertex, y1, y2, v1, v2, stretchX, rescaleX, + width, bitmapWidth, quadCount); } y1 = y2; @@ -188,34 +151,25 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, } if (previousStepY != bitmapHeight) { - y2 = bottom - top; -#if DEBUG_EXPLODE_PATCHES - y1 += mYCount * EXPLODE_GAP; - y2 += mYCount * EXPLODE_GAP; -#endif - generateRow(vertex, y1, y2, v1, 1.0f, stretchX, rescaleX, right - left, - bitmapWidth, quadCount); + y2 = height; + generateRow(xDivs, xCount, vertex, y1, y2, v1, 1.0f, stretchX, rescaleX, + width, bitmapWidth, quadCount); } - if (verticesCount > 0) { - Caches& caches = Caches::getInstance(); - caches.bindMeshBuffer(meshBuffer); - if (mAllocatedVerticesCount < verticesCount) { - glBufferData(GL_ARRAY_BUFFER, sizeof(TextureVertex) * verticesCount, - mVertices, GL_DYNAMIC_DRAW); - mAllocatedVerticesCount = verticesCount; - } else { - glBufferSubData(GL_ARRAY_BUFFER, 0, - sizeof(TextureVertex) * verticesCount, mVertices); - } - caches.resetVertexPointers(); + if (verticesCount == maxVertices) { + vertices = tempVertices; + } else { + vertices = new TextureVertex[verticesCount]; + memcpy(vertices, tempVertices, verticesCount * sizeof(TextureVertex)); + delete[] tempVertices; } - PATCH_LOGD(" patch: new vertices count = %d", verticesCount); + return vertices; } -void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, float v2, - float stretchX, float rescaleX, float width, float bitmapWidth, uint32_t& quadCount) { +void Patch::generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex, + float y1, float y2, float v1, float v2, float stretchX, float rescaleX, + float width, float bitmapWidth, uint32_t& quadCount) { float previousStepX = 0.0f; float x1 = 0.0f; @@ -223,8 +177,8 @@ void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, fl float u1 = 0.0f; // Generate the row quad by quad - for (uint32_t i = 0; i < mXCount; i++) { - float stepX = mXDivs[i]; + for (uint32_t i = 0; i < xCount; i++) { + float stepX = xDivs[i]; const float segment = stepX - previousStepX; if (i & 1) { @@ -238,14 +192,7 @@ void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, fl u1 += uOffset / bitmapWidth; if (stepX > 0.0f) { -#if DEBUG_EXPLODE_PATCHES - x1 += i * EXPLODE_GAP; - x2 += i * EXPLODE_GAP; -#endif generateQuad(vertex, x1, y1, x2, y2, u1, v1, u2, v2, quadCount); -#if DEBUG_EXPLODE_PATCHES - x2 -= i * EXPLODE_GAP; -#endif } x1 = x2; @@ -256,10 +203,6 @@ void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, fl if (previousStepX != bitmapWidth) { x2 = width; -#if DEBUG_EXPLODE_PATCHES - x1 += mXCount * EXPLODE_GAP; - x2 += mXCount * EXPLODE_GAP; -#endif generateQuad(vertex, x1, y1, x2, y2, u1, v1, 1.0f, v2, quadCount); } } @@ -275,11 +218,11 @@ void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, f if (y2 < 0.0f) y2 = 0.0f; // Skip degenerate and transparent (empty) quads - if (((mColorKey >> oldQuadCount) & 0x1) || x1 >= x2 || y1 >= y2) { + if ((mColors[oldQuadCount] == 0) || x1 >= x2 || y1 >= y2) { #if DEBUG_PATCHES_EMPTY_VERTICES PATCH_LOGD(" quad %d (empty)", oldQuadCount); - PATCH_LOGD(" left, top = %.2f, %.2f\t\tu1, v1 = %.4f, %.4f", x1, y1, u1, v1); - PATCH_LOGD(" right, bottom = %.2f, %.2f\t\tu2, v2 = %.4f, %.4f", x2, y2, u2, v2); + PATCH_LOGD(" left, top = %.2f, %.2f\t\tu1, v1 = %.8f, %.8f", x1, y1, u1, v1); + PATCH_LOGD(" right, bottom = %.2f, %.2f\t\tu2, v2 = %.8f, %.8f", x2, y2, u2, v2); #endif return; } @@ -290,23 +233,20 @@ void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, f quads.add(bounds); } - // Left triangle + mUvMapper.map(u1, v1, u2, v2); + TextureVertex::set(vertex++, x1, y1, u1, v1); TextureVertex::set(vertex++, x2, y1, u2, v1); TextureVertex::set(vertex++, x1, y2, u1, v2); - - // Right triangle - TextureVertex::set(vertex++, x1, y2, u1, v2); - TextureVertex::set(vertex++, x2, y1, u2, v1); TextureVertex::set(vertex++, x2, y2, u2, v2); - // A quad is made of 2 triangles, 6 vertices - verticesCount += 6; + verticesCount += 4; + indexCount += 6; #if DEBUG_PATCHES_VERTICES PATCH_LOGD(" quad %d", oldQuadCount); - PATCH_LOGD(" left, top = %.2f, %.2f\t\tu1, v1 = %.4f, %.4f", x1, y1, u1, v1); - PATCH_LOGD(" right, bottom = %.2f, %.2f\t\tu2, v2 = %.4f, %.4f", x2, y2, u2, v2); + PATCH_LOGD(" left, top = %.2f, %.2f\t\tu1, v1 = %.8f, %.8f", x1, y1, u1, v1); + PATCH_LOGD(" right, bottom = %.2f, %.2f\t\tu2, v2 = %.8f, %.8f", x2, y2, u2, v2); #endif } diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h index ee7bf70..763a785 100644 --- a/libs/hwui/Patch.h +++ b/libs/hwui/Patch.h @@ -23,62 +23,51 @@ #include <utils/Vector.h> +#include <androidfw/ResourceTypes.h> + #include "Rect.h" +#include "UvMapper.h" #include "Vertex.h" namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -#define EXPLODE_GAP 4 - -/////////////////////////////////////////////////////////////////////////////// // 9-patch structures /////////////////////////////////////////////////////////////////////////////// -/** - * An OpenGL patch. This contains an array of vertices and an array of - * indices to render the vertices. - */ struct Patch { - Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQuads); + Patch(); ~Patch(); - void updateVertices(const float bitmapWidth, const float bitmapHeight, - float left, float top, float right, float bottom); + /** + * Returns the size of this patch's mesh in bytes. + */ + uint32_t getSize() const; - void updateColorKey(const uint32_t colorKey); - void copy(const int32_t* xDivs, const int32_t* yDivs); - bool matches(const int32_t* xDivs, const int32_t* yDivs, - const uint32_t colorKey, const int8_t emptyQuads); - - GLuint meshBuffer; + TextureVertex* vertices; uint32_t verticesCount; + uint32_t indexCount; bool hasEmptyQuads; Vector<Rect> quads; -private: - TextureVertex* mVertices; - uint32_t mAllocatedVerticesCount; - - int32_t* mXDivs; - int32_t* mYDivs; - uint32_t mColorKey; + GLintptr offset; + GLintptr textureOffset; - uint32_t mXCount; - uint32_t mYCount; - int8_t mEmptyQuads; + TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight, + float width, float height, const Res_png_9patch* patch); + TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight, + float width, float height, const UvMapper& mapper, const Res_png_9patch* patch); - void generateRow(TextureVertex*& vertex, float y1, float y2, - float v1, float v2, float stretchX, float rescaleX, +private: + void generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex, + float y1, float y2, float v1, float v2, float stretchX, float rescaleX, float width, float bitmapWidth, uint32_t& quadCount); - void generateQuad(TextureVertex*& vertex, - float x1, float y1, float x2, float y2, - float u1, float v1, float u2, float v2, - uint32_t& quadCount); + void generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, float y2, + float u1, float v1, float u2, float v2, uint32_t& quadCount); + + uint32_t* mColors; + UvMapper mUvMapper; }; // struct Patch }; // namespace uirenderer diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp index 62e38d3..dc0d98c 100644 --- a/libs/hwui/PatchCache.cpp +++ b/libs/hwui/PatchCache.cpp @@ -16,8 +16,10 @@ #define LOG_TAG "OpenGLRenderer" +#include <utils/JenkinsHash.h> #include <utils/Log.h> +#include "Caches.h" #include "PatchCache.h" #include "Properties.h" @@ -28,111 +30,243 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -PatchCache::PatchCache(): mMaxEntries(DEFAULT_PATCH_CACHE_SIZE) { -} - -PatchCache::PatchCache(uint32_t maxEntries): mMaxEntries(maxEntries) { +PatchCache::PatchCache(): + mSize(0), mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity), + mMeshBuffer(0), mFreeBlocks(NULL), mGenerationId(0) { + char property[PROPERTY_VALUE_MAX]; + if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, NULL) > 0) { + INIT_LOGD(" Setting patch cache size to %skB", property); + mMaxSize = KB(atoi(property)); + } else { + INIT_LOGD(" Using default patch cache size of %.2fkB", DEFAULT_PATCH_CACHE_SIZE); + mMaxSize = KB(DEFAULT_PATCH_CACHE_SIZE); + } } PatchCache::~PatchCache() { clear(); } +void PatchCache::init(Caches& caches) { + bool created = false; + if (!mMeshBuffer) { + glGenBuffers(1, &mMeshBuffer); + created = true; + } + + caches.bindMeshBuffer(mMeshBuffer); + caches.resetVertexPointers(); + + if (created) { + createVertexBuffer(); + } +} + /////////////////////////////////////////////////////////////////////////////// // Caching /////////////////////////////////////////////////////////////////////////////// -int PatchCache::PatchDescription::compare( - const PatchCache::PatchDescription& lhs, const PatchCache::PatchDescription& rhs) { - int deltaInt = lhs.bitmapWidth - rhs.bitmapWidth; - if (deltaInt != 0) return deltaInt; - - deltaInt = lhs.bitmapHeight - rhs.bitmapHeight; - if (deltaInt != 0) return deltaInt; +hash_t PatchCache::PatchDescription::hash() const { + uint32_t hash = JenkinsHashMix(0, android::hash_type(mPatch)); + hash = JenkinsHashMix(hash, mBitmapWidth); + hash = JenkinsHashMix(hash, mBitmapHeight); + hash = JenkinsHashMix(hash, mPixelWidth); + hash = JenkinsHashMix(hash, mPixelHeight); + return JenkinsHashWhiten(hash); +} - if (lhs.pixelWidth < rhs.pixelWidth) return -1; - if (lhs.pixelWidth > rhs.pixelWidth) return +1; +int PatchCache::PatchDescription::compare(const PatchCache::PatchDescription& lhs, + const PatchCache::PatchDescription& rhs) { + return memcmp(&lhs, &rhs, sizeof(PatchDescription)); +} - if (lhs.pixelHeight < rhs.pixelHeight) return -1; - if (lhs.pixelHeight > rhs.pixelHeight) return +1; +void PatchCache::clear() { + clearCache(); - deltaInt = lhs.xCount - rhs.xCount; - if (deltaInt != 0) return deltaInt; + if (mMeshBuffer) { + Caches::getInstance().unbindMeshBuffer(); + glDeleteBuffers(1, &mMeshBuffer); + mMeshBuffer = 0; + mSize = 0; + } +} - deltaInt = lhs.yCount - rhs.yCount; - if (deltaInt != 0) return deltaInt; +void PatchCache::clearCache() { + LruCache<PatchDescription, Patch*>::Iterator i(mCache); + while (i.next()) { + delete i.value(); + } + mCache.clear(); - deltaInt = lhs.emptyCount - rhs.emptyCount; - if (deltaInt != 0) return deltaInt; + BufferBlock* block = mFreeBlocks; + while (block) { + BufferBlock* next = block->next; + delete block; + block = next; + } + mFreeBlocks = NULL; +} - deltaInt = lhs.colorKey - rhs.colorKey; - if (deltaInt != 0) return deltaInt; +void PatchCache::remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch) { + LruCache<PatchDescription, Patch*>::Iterator i(mCache); + while (i.next()) { + const PatchDescription& key = i.key(); + if (key.getPatch() == patch) { + patchesToRemove.push(patch_pair_t(&key, i.value())); + } + } +} - return 0; +void PatchCache::removeDeferred(Res_png_9patch* patch) { + Mutex::Autolock _l(mLock); + mGarbage.push(patch); } -void PatchCache::clear() { - size_t count = mCache.size(); - for (size_t i = 0; i < count; i++) { - delete mCache.valueAt(i); +void PatchCache::clearGarbage() { + Vector<patch_pair_t> patchesToRemove; + + { // scope for the mutex + Mutex::Autolock _l(mLock); + size_t count = mGarbage.size(); + for (size_t i = 0; i < count; i++) { + remove(patchesToRemove, mGarbage[i]); + } + mGarbage.clear(); } - mCache.clear(); + + // TODO: We could sort patchesToRemove by offset to merge + // adjacent free blocks + for (size_t i = 0; i < patchesToRemove.size(); i++) { + const patch_pair_t& pair = patchesToRemove[i]; + + // Add a new free block to the list + const Patch* patch = pair.getSecond(); + BufferBlock* block = new BufferBlock(patch->offset, patch->getSize()); + block->next = mFreeBlocks; + mFreeBlocks = block; + + mSize -= patch->getSize(); + + mCache.remove(*pair.getFirst()); + } + +#if DEBUG_PATCHES + if (patchesToRemove.size() > 0) { + dumpFreeBlocks("Removed garbage"); + } +#endif +} + +void PatchCache::createVertexBuffer() { + glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW); + mSize = 0; + mFreeBlocks = new BufferBlock(0, mMaxSize); + mGenerationId++; } -Patch* PatchCache::get(const uint32_t bitmapWidth, const uint32_t bitmapHeight, - const float pixelWidth, const float pixelHeight, - const int32_t* xDivs, const int32_t* yDivs, const uint32_t* colors, - const uint32_t width, const uint32_t height, const int8_t numColors) { +/** + * Sets the mesh's offsets and copies its associated vertices into + * the mesh buffer (VBO). + */ +void PatchCache::setupMesh(Patch* newMesh, TextureVertex* vertices) { + // This call ensures the VBO exists and that it is bound + init(Caches::getInstance()); - int8_t transparentQuads = 0; - uint32_t colorKey = 0; + // If we're running out of space, let's clear the entire cache + uint32_t size = newMesh->getSize(); + if (mSize + size > mMaxSize) { + clearCache(); + createVertexBuffer(); + } - if (uint8_t(numColors) < sizeof(uint32_t) * 4) { - for (int8_t i = 0; i < numColors; i++) { - if (colors[i] == 0x0) { - transparentQuads++; - colorKey |= 0x1 << i; - } + // Find a block where we can fit the mesh + BufferBlock* previous = NULL; + BufferBlock* block = mFreeBlocks; + while (block) { + // The mesh fits + if (block->size >= size) { + break; } + previous = block; + block = block->next; } - // If the 9patch is made of only transparent quads - if (transparentQuads == int8_t((width + 1) * (height + 1))) { - return NULL; + // We have enough space left in the buffer, but it's + // too fragmented, let's clear the cache + if (!block) { + clearCache(); + createVertexBuffer(); + previous = NULL; + block = mFreeBlocks; } - const PatchDescription description(bitmapWidth, bitmapHeight, - pixelWidth, pixelHeight, width, height, transparentQuads, colorKey); + // Copy the 9patch mesh in the VBO + newMesh->offset = (GLintptr) (block->offset); + newMesh->textureOffset = newMesh->offset + gMeshTextureOffset; + glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices); - ssize_t index = mCache.indexOfKey(description); - Patch* mesh = NULL; - if (index >= 0) { - mesh = mCache.valueAt(index); + // Remove the block since we've used it entirely + if (block->size == size) { + if (previous) { + previous->next = block->next; + } else { + mFreeBlocks = block->next; + } + } else { + // Resize the block now that it's occupied + block->offset += size; + block->size -= size; } + mSize += size; +} + +const Patch* PatchCache::get(const AssetAtlas::Entry* entry, + const uint32_t bitmapWidth, const uint32_t bitmapHeight, + const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) { + + const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch); + const Patch* mesh = mCache.get(description); + if (!mesh) { - PATCH_LOGD("New patch mesh " - "xCount=%d yCount=%d, w=%.2f h=%.2f, bw=%.2f bh=%.2f", - width, height, pixelWidth, pixelHeight, bitmapWidth, bitmapHeight); - - mesh = new Patch(width, height, transparentQuads); - mesh->updateColorKey(colorKey); - mesh->copy(xDivs, yDivs); - mesh->updateVertices(bitmapWidth, bitmapHeight, 0.0f, 0.0f, pixelWidth, pixelHeight); - - if (mCache.size() >= mMaxEntries) { - delete mCache.valueAt(mCache.size() - 1); - mCache.removeItemsAt(mCache.size() - 1, 1); + Patch* newMesh = new Patch(); + TextureVertex* vertices; + + if (entry) { + // An atlas entry has a UV mapper + vertices = newMesh->createMesh(bitmapWidth, bitmapHeight, + pixelWidth, pixelHeight, entry->uvMapper, patch); + } else { + vertices = newMesh->createMesh(bitmapWidth, bitmapHeight, + pixelWidth, pixelHeight, patch); } - mCache.add(description, mesh); - } else if (!mesh->matches(xDivs, yDivs, colorKey, transparentQuads)) { - PATCH_LOGD("Patch mesh does not match, refreshing vertices"); - mesh->updateVertices(bitmapWidth, bitmapHeight, 0.0f, 0.0f, pixelWidth, pixelHeight); + if (vertices) { + setupMesh(newMesh, vertices); + } + +#if DEBUG_PATCHES + dumpFreeBlocks("Adding patch"); +#endif + + mCache.put(description, newMesh); + return newMesh; } return mesh; } +#if DEBUG_PATCHES +void PatchCache::dumpFreeBlocks(const char* prefix) { + String8 dump; + BufferBlock* block = mFreeBlocks; + while (block) { + dump.appendFormat("->(%d, %d)", block->offset, block->size); + block = block->next; + } + ALOGD("%s: Free blocks%s", prefix, dump.string()); +} +#endif + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h index 0822cba..9f2c9a5 100644 --- a/libs/hwui/PatchCache.h +++ b/libs/hwui/PatchCache.h @@ -17,10 +17,16 @@ #ifndef ANDROID_HWUI_PATCH_CACHE_H #define ANDROID_HWUI_PATCH_CACHE_H -#include <utils/KeyedVector.h> +#include <GLES2/gl2.h> +#include <utils/LruCache.h> + +#include <androidfw/ResourceTypes.h> + +#include "AssetAtlas.h" #include "Debug.h" #include "Patch.h" +#include "utils/Pair.h" namespace android { namespace uirenderer { @@ -40,45 +46,64 @@ namespace uirenderer { // Cache /////////////////////////////////////////////////////////////////////////////// +class Caches; + class PatchCache { public: PatchCache(); - PatchCache(uint32_t maxCapacity); ~PatchCache(); + void init(Caches& caches); - Patch* get(const uint32_t bitmapWidth, const uint32_t bitmapHeight, - const float pixelWidth, const float pixelHeight, - const int32_t* xDivs, const int32_t* yDivs, const uint32_t* colors, - const uint32_t width, const uint32_t height, const int8_t numColors); + const Patch* get(const AssetAtlas::Entry* entry, + const uint32_t bitmapWidth, const uint32_t bitmapHeight, + const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch); void clear(); uint32_t getSize() const { - return mCache.size(); + return mSize; } uint32_t getMaxSize() const { - return mMaxEntries; + return mMaxSize; + } + + GLuint getMeshBuffer() const { + return mMeshBuffer; + } + + uint32_t getGenerationId() const { + return mGenerationId; } -private: /** - * Description of a patch. + * Removes the entries associated with the specified 9-patch. This is meant + * to be called from threads that are not the EGL context thread (GC thread + * on the VM side for instance.) */ + void removeDeferred(Res_png_9patch* patch); + + /** + * Process deferred removals. + */ + void clearGarbage(); + + +private: struct PatchDescription { - PatchDescription(): bitmapWidth(0), bitmapHeight(0), pixelWidth(0), pixelHeight(0), - xCount(0), yCount(0), emptyCount(0), colorKey(0) { + PatchDescription(): mPatch(NULL), mBitmapWidth(0), mBitmapHeight(0), + mPixelWidth(0), mPixelHeight(0) { } PatchDescription(const uint32_t bitmapWidth, const uint32_t bitmapHeight, - const float pixelWidth, const float pixelHeight, - const uint32_t xCount, const uint32_t yCount, - const int8_t emptyCount, const uint32_t colorKey): - bitmapWidth(bitmapWidth), bitmapHeight(bitmapHeight), - pixelWidth(pixelWidth), pixelHeight(pixelHeight), - xCount(xCount), yCount(yCount), - emptyCount(emptyCount), colorKey(colorKey) { + const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch): + mPatch(patch), mBitmapWidth(bitmapWidth), mBitmapHeight(bitmapHeight), + mPixelWidth(pixelWidth), mPixelHeight(pixelHeight) { } + hash_t hash() const; + + const Res_png_9patch* getPatch() const { return mPatch; } + static int compare(const PatchDescription& lhs, const PatchDescription& rhs); bool operator==(const PatchDescription& other) const { @@ -99,21 +124,63 @@ private: return PatchDescription::compare(lhs, rhs); } + friend inline hash_t hash_type(const PatchDescription& entry) { + return entry.hash(); + } + private: - uint32_t bitmapWidth; - uint32_t bitmapHeight; - float pixelWidth; - float pixelHeight; - uint32_t xCount; - uint32_t yCount; - int8_t emptyCount; - uint32_t colorKey; + const Res_png_9patch* mPatch; + uint32_t mBitmapWidth; + uint32_t mBitmapHeight; + float mPixelWidth; + float mPixelHeight; }; // struct PatchDescription - uint32_t mMaxEntries; - KeyedVector<PatchDescription, Patch*> mCache; + /** + * A buffer block represents an empty range in the mesh buffer + * that can be used to store vertices. + * + * The patch cache maintains a linked-list of buffer blocks + * to track available regions of memory in the VBO. + */ + struct BufferBlock { + BufferBlock(uint32_t offset, uint32_t size): offset(offset), size(size), next(NULL) { + } + + uint32_t offset; + uint32_t size; + + BufferBlock* next; + }; // struct BufferBlock + + typedef Pair<const PatchDescription*, Patch*> patch_pair_t; + + void clearCache(); + void createVertexBuffer(); + + void setupMesh(Patch* newMesh, TextureVertex* vertices); + + void remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch); + +#if DEBUG_PATCHES + void dumpFreeBlocks(const char* prefix); +#endif + + uint32_t mMaxSize; + uint32_t mSize; + + LruCache<PatchDescription, Patch*> mCache; + + GLuint mMeshBuffer; + // First available free block inside the mesh buffer + BufferBlock* mFreeBlocks; + + uint32_t mGenerationId; + // Garbage tracking, required to handle GC events on the VM side + Vector<Res_png_9patch*> mGarbage; + mutable Mutex mLock; }; // class PatchCache }; // namespace uirenderer diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index fdb10e2..5df6408 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -139,7 +139,7 @@ static void drawPath(const SkPath *path, const SkPaint* paint, SkBitmap& bitmap, static PathTexture* createTexture(float left, float top, float offset, uint32_t width, uint32_t height, uint32_t id) { - PathTexture* texture = new PathTexture(); + PathTexture* texture = new PathTexture(Caches::getInstance()); texture->left = left; texture->top = top; texture->offset = offset; @@ -214,7 +214,22 @@ void PathCache::operator()(PathDescription& entry, PathTexture*& texture) { void PathCache::removeTexture(PathTexture* texture) { if (texture) { const uint32_t size = texture->width * texture->height; - mSize -= size; + + // If there is a pending task we must wait for it to return + // before attempting our cleanup + const sp<Task<SkBitmap*> >& task = texture->task(); + if (task != NULL) { + SkBitmap* bitmap = task->getResult(); + texture->clearTask(); + } else { + // If there is a pending task, the path was not added + // to the cache and the size wasn't increased + if (size > mSize) { + ALOGE("Removing path texture of size %d will leave " + "the cache in an inconsistent state", size); + } + mSize -= size; + } PATH_LOGD("PathCache::delete name, size, mSize = %d, %d, %d", texture->id, size, mSize); @@ -223,7 +238,7 @@ void PathCache::removeTexture(PathTexture* texture) { } if (texture->id) { - glDeleteTextures(1, &texture->id); + Caches::getInstance().deleteTexture(texture->id); } delete texture; } @@ -283,6 +298,11 @@ void PathCache::generateTexture(const PathDescription& entry, SkBitmap* bitmap, mCache.put(entry, texture); } } else { + // It's okay to add a texture that's bigger than the cache since + // we'll trim the cache later when addToCache is set to false + if (!addToCache) { + mSize += size; + } texture->cleanup = true; } } @@ -300,7 +320,7 @@ void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) { glGenTextures(1, &texture->id); - glBindTexture(GL_TEXTURE_2D, texture->id); + Caches::getInstance().bindTexture(texture->id); // Textures are Alpha8 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); @@ -350,8 +370,7 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { // Paths /////////////////////////////////////////////////////////////////////////////// -void PathCache::remove(const path_pair_t& pair) { - Vector<PathDescription> pathsToRemove; +void PathCache::remove(Vector<PathDescription>& pathsToRemove, const path_pair_t& pair) { LruCache<PathDescription, PathTexture*>::Iterator i(mCache); while (i.next()) { @@ -362,10 +381,6 @@ void PathCache::remove(const path_pair_t& pair) { pathsToRemove.push(key); } } - - for (size_t i = 0; i < pathsToRemove.size(); i++) { - mCache.remove(pathsToRemove.itemAt(i)); - } } void PathCache::removeDeferred(SkPath* path) { @@ -374,12 +389,20 @@ void PathCache::removeDeferred(SkPath* path) { } void PathCache::clearGarbage() { - Mutex::Autolock l(mLock); - size_t count = mGarbage.size(); - for (size_t i = 0; i < count; i++) { - remove(mGarbage.itemAt(i)); + Vector<PathDescription> pathsToRemove; + + { // scope for the mutex + Mutex::Autolock l(mLock); + size_t count = mGarbage.size(); + for (size_t i = 0; i < count; i++) { + remove(pathsToRemove, mGarbage.itemAt(i)); + } + mGarbage.clear(); + } + + for (size_t i = 0; i < pathsToRemove.size(); i++) { + mCache.remove(pathsToRemove.itemAt(i)); } - mGarbage.clear(); } /** diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h index dd1f996..16d20a8 100644 --- a/libs/hwui/PathCache.h +++ b/libs/hwui/PathCache.h @@ -58,7 +58,7 @@ class Caches; * Alpha texture used to represent a path. */ struct PathTexture: public Texture { - PathTexture(): Texture() { + PathTexture(Caches& caches): Texture(caches) { } ~PathTexture() { @@ -269,7 +269,7 @@ private: * Removes an entry. * The pair must define first=path, second=sourcePath */ - void remove(const path_pair_t& pair); + void remove(Vector<PathDescription>& pathsToRemove, const path_pair_t& pair); /** * Ensures there is enough space in the cache for a texture of the specified diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp index 0879b1b..3970913 100644 --- a/libs/hwui/PathTessellator.cpp +++ b/libs/hwui/PathTessellator.cpp @@ -66,11 +66,11 @@ void PathTessellator::expandBoundsForStroke(SkRect& bounds, const SkPaint* paint } } -inline void copyVertex(Vertex* destPtr, const Vertex* srcPtr) { +inline static void copyVertex(Vertex* destPtr, const Vertex* srcPtr) { Vertex::set(destPtr, srcPtr->position[0], srcPtr->position[1]); } -inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) { +inline static void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) { AlphaVertex::set(destPtr, srcPtr->position[0], srcPtr->position[1], srcPtr->alpha); } @@ -84,7 +84,7 @@ inline void copyAlphaVertex(AlphaVertex* destPtr, const AlphaVertex* srcPtr) { * * NOTE: assumes angles between normals 90 degrees or less */ -inline vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& normalB) { +inline static vec2 totalOffsetFromNormals(const vec2& normalA, const vec2& normalB) { return (normalA + normalB) / (1 + fabs(normalA.dot(normalB))); } @@ -224,6 +224,20 @@ void getStrokeVerticesFromPerimeter(const PaintInfo& paintInfo, const Vector<Ver 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; + paintInfo.scaleOffsetForStrokeWidth(strokeOffset); + + vec2 referencePoint(center.position[0], center.position[1]); + if (paintInfo.cap == SkPaint::kSquare_Cap) { + referencePoint += vec2(-strokeOffset.y, strokeOffset.x) * (begin ? -1 : 1); + } + + Vertex::set(&buffer[currentIndex++], referencePoint + strokeOffset); + Vertex::set(&buffer[currentIndex++], referencePoint - strokeOffset); +} + /** * Fills a vertexBuffer with non-alpha vertices similar to getStrokeVerticesFromPerimeter, except: * @@ -235,19 +249,17 @@ void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo, const Vector<Vertex>& vertices, VertexBuffer& vertexBuffer) { const int extra = paintInfo.capExtraDivisions(); const int allocSize = (vertices.size() + extra) * 2; - Vertex* buffer = vertexBuffer.alloc<Vertex>(allocSize); + const int lastIndex = vertices.size() - 1; if (extra > 0) { // tessellate both round caps - const int last = vertices.size() - 1; float beginTheta = atan2( - - (vertices[0].position[0] - vertices[1].position[0]), - vertices[0].position[1] - vertices[1].position[1]); + - (vertices[0].position[0] - vertices[1].position[0]), + vertices[0].position[1] - vertices[1].position[1]); float endTheta = atan2( - - (vertices[last].position[0] - vertices[last - 1].position[0]), - vertices[last].position[1] - vertices[last - 1].position[1]); - + - (vertices[lastIndex].position[0] - vertices[lastIndex - 1].position[0]), + vertices[lastIndex].position[1] - vertices[lastIndex - 1].position[1]); const float dTheta = PI / (extra + 1); const float radialScale = 2.0f / (1 + cos(dTheta)); @@ -270,56 +282,45 @@ void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo, vec2 endRadialOffset(cos(endTheta), sin(endTheta)); paintInfo.scaleOffsetForStrokeWidth(endRadialOffset); Vertex::set(&buffer[allocSize - 1 - capOffset], - vertices[last].position[0] + endRadialOffset.x, - vertices[last].position[1] + endRadialOffset.y); + vertices[lastIndex].position[0] + endRadialOffset.x, + vertices[lastIndex].position[1] + endRadialOffset.y); } } int currentIndex = extra; - const Vertex* current = &(vertices[0]); - vec2 lastNormal; - for (unsigned int i = 0; i < vertices.size() - 1; i++) { + const Vertex* last = &(vertices[0]); + const Vertex* current = &(vertices[1]); + vec2 lastNormal(current->position[1] - last->position[1], + last->position[0] - current->position[0]); + 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]); nextNormal.normalize(); - vec2 totalOffset; - if (i == 0) { - totalOffset = nextNormal; - } else { - totalOffset = totalOffsetFromNormals(lastNormal, nextNormal); - } - paintInfo.scaleOffsetForStrokeWidth(totalOffset); + vec2 strokeOffset = totalOffsetFromNormals(lastNormal, nextNormal); + paintInfo.scaleOffsetForStrokeWidth(strokeOffset); - Vertex::set(&buffer[currentIndex++], - current->position[0] + totalOffset.x, - current->position[1] + totalOffset.y); - - Vertex::set(&buffer[currentIndex++], - current->position[0] - totalOffset.x, - current->position[1] - totalOffset.y); + vec2 center(current->position[0], current->position[1]); + Vertex::set(&buffer[currentIndex++], center + strokeOffset); + Vertex::set(&buffer[currentIndex++], center - strokeOffset); current = next; lastNormal = nextNormal; } - vec2 totalOffset = lastNormal; - paintInfo.scaleOffsetForStrokeWidth(totalOffset); - - Vertex::set(&buffer[currentIndex++], - current->position[0] + totalOffset.x, - current->position[1] + totalOffset.y); - Vertex::set(&buffer[currentIndex++], - current->position[0] - totalOffset.x, - current->position[1] - totalOffset.y); + storeBeginEnd(paintInfo, vertices[lastIndex], lastNormal, buffer, currentIndex, false); DEBUG_DUMP_BUFFER(); } /** * Populates a vertexBuffer with AlphaVertices to create an anti-aliased fill shape tessellation - * + * * 1 - create the AA perimeter of unit width, by zig-zagging at each point around the perimeter of * the shape (using 2 * perimeter.size() vertices) * @@ -389,7 +390,7 @@ void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Ver * For explanation of constants and general methodoloyg, see comments for * getStrokeVerticesFromUnclosedVerticesAA() below. */ -inline void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>& vertices, +inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>& vertices, AlphaVertex* buffer, bool isFirst, vec2 normal, int offset) { const int extra = paintInfo.capExtraDivisions(); const int extraOffset = (extra + 1) / 2; @@ -772,11 +773,67 @@ void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint, } } +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) { - rect.fLeft = fminf(rect.fLeft, vertex.position[0]); - rect.fTop = fminf(rect.fTop, vertex.position[1]); - rect.fRight = fmaxf(rect.fRight, vertex.position[0]); - rect.fBottom = fmaxf(rect.fBottom, vertex.position[1]); + expandRectToCoverVertex(rect, vertex.position[0], vertex.position[1]); +} + +template <class TYPE> +static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer, + const float* points, int count, SkRect& bounds) { + bounds.set(points[0], points[1], points[0], points[1]); + + int numPoints = count / 2; + int verticesPerPoint = srcBuffer.getVertexCount(); + dstBuffer.alloc<TYPE>(numPoints * verticesPerPoint + (numPoints - 1) * 2); + + for (int i = 0; i < count; i += 2) { + expandRectToCoverVertex(bounds, 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) { + const PaintInfo paintInfo(paint, transform); + + // determine point shape + SkPath path; + float radius = paintInfo.halfStrokeWidth; + if (radius == 0.0f) radius = 0.25f; + + if (paintInfo.cap == SkPaint::kRound_Cap) { + path.addCircle(0, 0, radius); + } else { + path.addRect(-radius, -radius, radius, radius); + } + + // calculate outline + Vector<Vertex> outlineVertices; + approximatePathOutlineVertices(path, true, + paintInfo.inverseScaleX * paintInfo.inverseScaleX, + paintInfo.inverseScaleY * paintInfo.inverseScaleY, outlineVertices); + + if (!outlineVertices.size()) return; + + // tessellate, then duplicate outline across points + int numPoints = count / 2; + VertexBuffer tempBuffer; + if (!paintInfo.isAA) { + getFillVerticesFromPerimeter(outlineVertices, tempBuffer); + instanceVertices<Vertex>(tempBuffer, vertexBuffer, points, count, bounds); + } else { + getFillVerticesFromPerimeterAA(paintInfo, outlineVertices, tempBuffer); + instanceVertices<AlphaVertex>(tempBuffer, vertexBuffer, points, count, bounds); + } + + expandBoundsForStroke(bounds, paint, true); // force-expand bounds to incorporate stroke } void PathTessellator::tessellateLines(const float* points, int count, SkPaint* paint, diff --git a/libs/hwui/PathTessellator.h b/libs/hwui/PathTessellator.h index 596d49d..85797fc 100644 --- a/libs/hwui/PathTessellator.h +++ b/libs/hwui/PathTessellator.h @@ -30,7 +30,7 @@ class VertexBuffer { public: VertexBuffer(): mBuffer(0), - mSize(0), + mVertexCount(0), mCleanupMethod(NULL) {} @@ -44,30 +44,42 @@ public: multiple regions within a single VertexBuffer, such as with PathTessellator::tesselateLines() */ template <class TYPE> - TYPE* alloc(int size) { - if (mSize) { + 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 + size; + mReallocBuffer = reallocBuffer + vertexCount; return reallocBuffer; } - mSize = size; - mReallocBuffer = mBuffer = (void*)new TYPE[size]; + mVertexCount = vertexCount; + mReallocBuffer = mBuffer = (void*)new TYPE[vertexCount]; mCleanupMethod = &(cleanup<TYPE>); return (TYPE*)mBuffer; } - void* getBuffer() const { return mBuffer; } - unsigned int getSize() const { return mSize; } + 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 + mSize; + 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)); @@ -81,7 +93,7 @@ private: } void* mBuffer; - unsigned int mSize; + unsigned int mVertexCount; void* mReallocBuffer; // used for multi-allocation @@ -95,6 +107,9 @@ public: static void tessellatePath(const SkPath& path, const SkPaint* paint, const mat4 *transform, VertexBuffer& vertexBuffer); + static void tessellatePoints(const float* points, int count, SkPaint* paint, + const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer); + static void tessellateLines(const float* points, int count, SkPaint* paint, const mat4* transform, SkRect& bounds, VertexBuffer& vertexBuffer); diff --git a/libs/hwui/PixelBuffer.cpp b/libs/hwui/PixelBuffer.cpp index 8280370..36e89c6 100644 --- a/libs/hwui/PixelBuffer.cpp +++ b/libs/hwui/PixelBuffer.cpp @@ -19,6 +19,7 @@ #include <utils/Log.h> #include "Caches.h" +#include "Debug.h" #include "Extensions.h" #include "PixelBuffer.h" #include "Properties.h" @@ -113,6 +114,14 @@ uint8_t* GpuPixelBuffer::map(AccessMode mode) { if (mAccessMode == kAccessMode_None) { mCaches.bindPixelBuffer(mBuffer); mMappedPointer = (uint8_t*) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, getSize(), mode); +#if DEBUG_OPENGL + if (!mMappedPointer) { + GLenum status = GL_NO_ERROR; + while ((status = glGetError()) != GL_NO_ERROR) { + ALOGE("Could not map GPU pixel buffer: 0x%x", status); + } + } +#endif mAccessMode = mode; } @@ -123,7 +132,10 @@ void GpuPixelBuffer::unmap() { if (mAccessMode != kAccessMode_None) { if (mMappedPointer) { mCaches.bindPixelBuffer(mBuffer); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + GLboolean status = glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + if (status == GL_FALSE) { + ALOGE("Corrupted GPU pixel buffer"); + } } mAccessMode = kAccessMode_None; mMappedPointer = NULL; @@ -147,14 +159,8 @@ void GpuPixelBuffer::upload(uint32_t x, uint32_t y, uint32_t width, uint32_t hei /////////////////////////////////////////////////////////////////////////////// PixelBuffer* PixelBuffer::create(GLenum format, uint32_t width, uint32_t height, BufferType type) { - bool gpuBuffer = type == kBufferType_Auto && Extensions::getInstance().getMajorGlVersion() >= 3; - if (gpuBuffer) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_ENABLE_GPU_PIXEL_BUFFERS, property, "false") > 0) { - if (!strcmp(property, "true")) { - return new GpuPixelBuffer(format, width, height); - } - } + if (type == kBufferType_Auto && Caches::getInstance().gpuPixelBuffersEnabled) { + return new GpuPixelBuffer(format, width, height); } return new CpuPixelBuffer(format, width, height); } diff --git a/libs/hwui/PixelBuffer.h b/libs/hwui/PixelBuffer.h index 32d5417..9725a61 100644 --- a/libs/hwui/PixelBuffer.h +++ b/libs/hwui/PixelBuffer.h @@ -112,13 +112,25 @@ public: virtual uint8_t* getMappedPointer() const = 0; /** - * Upload the specified rectangle of this pixe buffer as a + * Upload the specified rectangle of this pixel buffer as a * GL_TEXTURE_2D texture. Calling this method will trigger * an unmap() if necessary. */ virtual void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) = 0; /** + * Upload the specified rectangle of this pixel buffer as a + * GL_TEXTURE_2D texture. Calling this method will trigger + * an unmap() if necessary. + * + * This is a convenience function provided to save callers the + * trouble of computing the offset parameter. + */ + void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + upload(x, y, width, height, getOffset(x, y)); + } + + /** * Returns the width of the render buffer in pixels. */ uint32_t getWidth() const { @@ -140,6 +152,13 @@ public: } /** + * Returns the offset of a pixel in this pixel buffer, in bytes. + */ + uint32_t getOffset(uint32_t x, uint32_t y) const { + return (y * mWidth + x) * formatSize(mFormat); + } + + /** * Returns the number of bytes per pixel in the specified format. * * Supported formats: diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp index 14a2376..7814a01 100644 --- a/libs/hwui/Program.cpp +++ b/libs/hwui/Program.cpp @@ -15,8 +15,12 @@ */ #define LOG_TAG "OpenGLRenderer" +#define ATRACE_TAG ATRACE_TAG_VIEW + +#include <utils/Trace.h> #include "Program.h" +#include "Vertex.h" namespace android { namespace uirenderer { @@ -25,7 +29,6 @@ namespace uirenderer { // Base program /////////////////////////////////////////////////////////////////////////////// -// TODO: Program instance should be created from a factory method Program::Program(const ProgramDescription& description, const char* vertex, const char* fragment) { mInitialized = false; mHasColorUniform = false; @@ -50,7 +53,9 @@ Program::Program(const ProgramDescription& description, const char* vertex, cons texCoords = -1; } + ATRACE_BEGIN("linkProgram"); glLinkProgram(mProgramId); + ATRACE_END(); GLint status; glGetProgramiv(mProgramId, GL_LINK_STATUS, &status); @@ -87,6 +92,9 @@ Program::Program(const ProgramDescription& description, const char* vertex, cons Program::~Program() { if (mInitialized) { + // This would ideally happen after linking the program + // but Tegra drivers, especially when perfhud is enabled, + // sometimes crash if we do so glDetachShader(mProgramId, mVertexShader); glDetachShader(mProgramId, mFragmentShader); @@ -132,6 +140,8 @@ int Program::getUniform(const char* name) { } GLuint Program::buildShader(const char* source, GLenum type) { + ATRACE_CALL(); + GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, 0); glCompileShader(shader); @@ -153,20 +163,24 @@ GLuint Program::buildShader(const char* source, GLenum type) { void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix, const mat4& transformMatrix, bool offset) { - mat4 p(projectionMatrix); - if (offset) { - // offset screenspace xy by an amount that compensates for typical precision - // issues in GPU hardware that tends to paint hor/vert lines in pixels shifted - // 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(.065, .065, 0); + if (projectionMatrix != mProjection) { + if (CC_LIKELY(!offset)) { + glUniformMatrix4fv(projection, 1, GL_FALSE, &projectionMatrix.data[0]); + } else { + mat4 p(projectionMatrix); + // offset screenspace xy by an amount that compensates for typical precision + // issues in GPU hardware that tends to paint hor/vert lines in pixels shifted + // 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); + glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]); + } + mProjection = projectionMatrix; } mat4 t(transformMatrix); t.multiply(modelViewMatrix); - - glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]); glUniformMatrix4fv(transform, 1, GL_FALSE, &t.data[0]); } diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h index e8b6d47..4f94afc 100644 --- a/libs/hwui/Program.h +++ b/libs/hwui/Program.h @@ -68,23 +68,22 @@ namespace uirenderer { #define PROGRAM_BITMAP_WRAPS_SHIFT 9 #define PROGRAM_BITMAP_WRAPT_SHIFT 11 -#define PROGRAM_GRADIENT_TYPE_SHIFT 33 +#define PROGRAM_GRADIENT_TYPE_SHIFT 33 // 2 bits for gradient type #define PROGRAM_MODULATE_SHIFT 35 -#define PROGRAM_IS_POINT_SHIFT 36 +#define PROGRAM_HAS_AA_SHIFT 36 -#define PROGRAM_HAS_AA_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 43 +#define PROGRAM_HAS_DEBUG_HIGHLIGHT 42 +#define PROGRAM_EMULATE_STENCIL 43 /////////////////////////////////////////////////////////////////////////////// // Types @@ -156,13 +155,11 @@ struct ProgramDescription { SkXfermode::Mode framebufferMode; bool swapSrcDst; - bool isPoint; - float pointSize; - bool hasGammaCorrection; float gamma; bool hasDebugHighlight; + bool emulateStencil; /** * Resets this description. All fields are reset back to the default @@ -199,9 +196,6 @@ struct ProgramDescription { framebufferMode = SkXfermode::kClear_Mode; swapSrcDst = false; - isPoint = false; - pointSize = 0.0f; - hasGammaCorrection = false; gamma = 2.2f; @@ -267,7 +261,6 @@ 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 (isPoint) key |= programid(0x1) << PROGRAM_IS_POINT_SHIFT; if (isAA) key |= programid(0x1) << PROGRAM_HAS_AA_SHIFT; if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT; if (hasTextureTransform) key |= programid(0x1) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT; @@ -275,6 +268,7 @@ struct ProgramDescription { if (isSimpleGradient) key |= programid(0x1) << PROGRAM_IS_SIMPLE_GRADIENT; 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; return key; } @@ -430,10 +424,13 @@ private: bool mUse; bool mInitialized; + // Uniforms caching bool mHasColorUniform; int mColorUniform; bool mHasSampler; + + mat4 mProjection; }; // class Program }; // namespace uirenderer diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index 8eb85e5..a5ce6f6 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -53,8 +53,6 @@ const char* gVS_Header_Uniforms_TextureTransform = const char* gVS_Header_Uniforms = "uniform mat4 projection;\n" \ "uniform mat4 transform;\n"; -const char* gVS_Header_Uniforms_IsPoint = - "uniform mediump float pointSize;\n"; const char* gVS_Header_Uniforms_HasGradient = "uniform mat4 screenSpace;\n"; const char* gVS_Header_Uniforms_HasBitmap = @@ -68,8 +66,6 @@ const char* gVS_Header_Varyings_IsAAVertexShape = "varying float alpha;\n"; const char* gVS_Header_Varyings_HasBitmap = "varying highp vec2 outBitmapTexCoords;\n"; -const char* gVS_Header_Varyings_PointHasBitmap = - "varying highp vec2 outPointBitmapTexCoords;\n"; const char* gVS_Header_Varyings_HasGradient[6] = { // Linear "varying highp vec2 linear;\n" @@ -118,12 +114,8 @@ const char* gVS_Main_OutGradient[6] = { }; const char* gVS_Main_OutBitmapTexCoords = " outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n"; -const char* gVS_Main_OutPointBitmapTexCoords = - " outPointBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n"; const char* gVS_Main_Position = " gl_Position = projection * transform * position;\n"; -const char* gVS_Main_PointSize = - " gl_PointSize = pointSize;\n"; const char* gVS_Main_AAVertexShape = " alpha = vtxAlpha;\n"; const char* gVS_Footer = @@ -141,9 +133,6 @@ const char* gFS_Header = "precision mediump float;\n\n"; const char* gFS_Uniforms_Color = "uniform vec4 color;\n"; -const char* gFS_Header_Uniforms_PointHasBitmap = - "uniform vec2 textureDimension;\n" - "uniform float pointSize;\n"; const char* gFS_Uniforms_TextureSampler = "uniform sampler2D baseSampler;\n"; const char* gFS_Uniforms_ExternalTextureSampler = @@ -178,10 +167,6 @@ const char* gFS_Main = "\nvoid main(void) {\n" " lowp vec4 fragColor;\n"; -const char* gFS_Main_PointBitmapTexCoords = - " highp vec2 outBitmapTexCoords = outPointBitmapTexCoords + " - "((gl_PointCoord - vec2(0.5, 0.5)) * textureDimension * vec2(pointSize, pointSize));\n"; - const char* gFS_Main_Dither[2] = { // ES 2.0 "texture2D(ditherSampler, ditherTexCoords).a * " STR(DITHER_KERNEL_SIZE_INV_SQUARE), @@ -342,6 +327,12 @@ const char* gFS_Main_ApplyColorOp[4] = { }; const char* gFS_Main_DebugHighlight = " gl_FragColor.rgb = vec3(0.0, gl_FragColor.a, 0.0);\n"; +const char* gFS_Main_EmulateStencil = + " gl_FragColor.rgba = vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 1.0);\n" + " return;\n" + " /*\n"; +const char* gFS_Footer_EmulateStencil = + " */\n"; const char* gFS_Footer = "}\n\n"; @@ -478,9 +469,6 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description if (description.hasBitmap) { shader.append(gVS_Header_Uniforms_HasBitmap); } - if (description.isPoint) { - shader.append(gVS_Header_Uniforms_IsPoint); - } // Varyings if (description.hasTexture || description.hasExternalTexture) { shader.append(gVS_Header_Varyings_HasTexture); @@ -495,9 +483,7 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description shader.append(gVS_Header_Varyings_HasGradient[gradientIndex(description)]); } if (description.hasBitmap) { - shader.append(description.isPoint ? - gVS_Header_Varyings_PointHasBitmap : - gVS_Header_Varyings_HasBitmap); + shader.append(gVS_Header_Varyings_HasBitmap); } // Begin the shader @@ -514,12 +500,7 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description shader.append(gVS_Main_OutColors); } if (description.hasBitmap) { - shader.append(description.isPoint ? - gVS_Main_OutPointBitmapTexCoords : - gVS_Main_OutBitmapTexCoords); - } - if (description.isPoint) { - shader.append(gVS_Main_PointSize); + shader.append(gVS_Main_OutBitmapTexCoords); } // Output transformed position shader.append(gVS_Main_Position); @@ -570,9 +551,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.append(gVS_Header_Varyings_HasGradient[gradientIndex(description)]); } if (description.hasBitmap) { - shader.append(description.isPoint ? - gVS_Header_Varyings_PointHasBitmap : - gVS_Header_Varyings_HasBitmap); + shader.append(gVS_Header_Varyings_HasBitmap); } // Uniforms @@ -593,9 +572,6 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.appendFormat(gFS_Uniforms_GradientSampler[description.isSimpleGradient], gFS_Uniforms_Dither); } - if (description.hasBitmap && description.isPoint) { - shader.append(gFS_Header_Uniforms_PointHasBitmap); - } if (description.hasGammaCorrection) { shader.append(gFS_Uniforms_Gamma); } @@ -603,7 +579,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti // Optimization for common cases if (!description.isAA && !blendFramebuffer && !description.hasColors && description.colorOp == ProgramDescription::kColorNone && - !description.isPoint && !description.hasDebugHighlight) { + !description.hasDebugHighlight && !description.emulateStencil) { bool fast = false; const bool noShader = !description.hasGradient && !description.hasBitmap; @@ -683,6 +659,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti // Begin the shader shader.append(gFS_Main); { + if (description.emulateStencil) { + shader.append(gFS_Main_EmulateStencil); + } // Stores the result in fragColor directly if (description.hasTexture || description.hasExternalTexture) { if (description.hasAlpha8Texture) { @@ -703,9 +682,6 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.appendFormat(gFS_Main_AddDitherToGradient, gFS_Main_Dither[mHasES3]); } if (description.hasBitmap) { - if (description.isPoint) { - shader.append(gFS_Main_PointBitmapTexCoords); - } if (!description.isBitmapNpot) { shader.append(gFS_Main_FetchBitmap); } else { @@ -757,6 +733,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.append(gFS_Main_DebugHighlight); } } + if (description.emulateStencil) { + shader.append(gFS_Footer_EmulateStencil); + } // End the shader shader.append(gFS_Footer); diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 6eea00c..20b8f2f 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -70,10 +70,23 @@ enum DebugLevel { #define PROPERTY_DEBUG_LAYERS_UPDATES "debug.hwui.show_layers_updates" /** - * Used to enable/disable overdraw debugging. The accepted values are - * "true" and "false". The default value is "false". + * Used to enable/disable overdraw debugging. + * + * The accepted values are + * "show", to show overdraw + * "show_deuteranomaly", to show overdraw if you suffer from Deuteranomaly + * "count", to show an overdraw counter + * "false", to disable overdraw debugging + * + * The default value is "false". + */ +#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.overdraw" + +/** + * Used to enable/disable PerfHUD ES profiling. The accepted values + * are "true" and "false". The default value is "false". */ -#define PROPERTY_DEBUG_OVERDRAW "debug.hwui.show_overdraw" +#define PROPERTY_DEBUG_NV_PROFILING "debug.hwui.nv_profiling" /** * Used to enable/disable non-rectangular clipping debugging. @@ -123,9 +136,9 @@ enum DebugLevel { /** * Indicates whether PBOs can be used to back pixel buffers. - * Accepted values are "true" and "false". + * Accepted values are "true" and "false". Default is true. */ -#define PROPERTY_ENABLE_GPU_PIXEL_BUFFERS "hwui.use_gpu_pixel_buffers" +#define PROPERTY_ENABLE_GPU_PIXEL_BUFFERS "ro.hwui.use_gpu_pixel_buffers" // These properties are defined in mega-bytes #define PROPERTY_TEXTURE_CACHE_SIZE "ro.hwui.texture_cache_size" @@ -133,6 +146,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_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" @@ -178,7 +192,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_PATCH_CACHE_SIZE 512 +#define DEFAULT_PATCH_CACHE_SIZE 128 // in kB #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f #define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f #define DEFAULT_FBO_CACHE_SIZE 16 @@ -195,6 +209,8 @@ enum DebugLevel { // Converts a number of mega-bytes into bytes #define MB(s) s * 1024 * 1024 +// Converts a number of kilo-bytes into bytes +#define KB(s) s * 1024 static DebugLevel readDebugLevel() { char property[PROPERTY_VALUE_MAX]; diff --git a/libs/hwui/Query.h b/libs/hwui/Query.h new file mode 100644 index 0000000..e25b16b --- /dev/null +++ b/libs/hwui/Query.h @@ -0,0 +1,152 @@ +/* + * 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_QUERY_H +#define ANDROID_HWUI_QUERY_H + +#include <GLES3/gl3.h> + +#include "Extensions.h" + +namespace android { +namespace uirenderer { + +/** + * A Query instance can be used to perform occlusion queries. If the device + * does not support occlusion queries, the result of a query will always be + * 0 and the result will always be marked available. + * + * To run an occlusion query successfully, you must start end end the query: + * + * Query query; + * query.begin(); + * // execute OpenGL calls + * query.end(); + * GLuint result = query.getResult(); + */ +class Query { +public: + /** + * Possible query targets. + */ + enum Target { + /** + * Indicates if any sample passed the depth & stencil tests. + */ + kTargetSamples = GL_ANY_SAMPLES_PASSED, + /** + * Indicates if any sample passed the depth & stencil tests. + * The implementation may choose to use a less precise version + * of the test, potentially resulting in false positives. + */ + kTargetConservativeSamples = GL_ANY_SAMPLES_PASSED_CONSERVATIVE, + }; + + /** + * Creates a new query with the specified target. The default + * target is kTargetSamples (of GL_ANY_SAMPLES_PASSED in OpenGL.) + */ + Query(Target target = kTargetSamples): mActive(false), mTarget(target), + mCanQuery(Extensions::getInstance().hasOcclusionQueries()), + mQuery(0) { + } + + ~Query() { + if (mQuery) { + glDeleteQueries(1, &mQuery); + } + } + + /** + * Begins the query. If the query has already begun or if the device + * does not support occlusion queries, calling this method as no effect. + * After calling this method successfully, the query is marked active. + */ + void begin() { + if (!mActive && mCanQuery) { + if (!mQuery) { + glGenQueries(1, &mQuery); + } + + glBeginQuery(mTarget, mQuery); + mActive = true; + } + } + + /** + * Ends the query. If the query has already begun or if the device + * does not support occlusion queries, calling this method as no effect. + * After calling this method successfully, the query is marked inactive. + */ + void end() { + if (mQuery && mActive) { + glEndQuery(mTarget); + mActive = false; + } + } + + /** + * Returns true if the query is active, false otherwise. + */ + bool isActive() { + return mActive; + } + + /** + * Returns true if the result of the query is available, + * false otherwise. Calling getResult() before the result + * is available may result in the calling thread being blocked. + * If the device does not support queries, this method always + * returns true. + */ + bool isResultAvailable() { + if (!mQuery) return true; + + GLuint result; + glGetQueryObjectuiv(mQuery, GL_QUERY_RESULT_AVAILABLE, &result); + return result == GL_TRUE; + } + + /** + * Returns the result of the query. If the device does not + * support queries this method will return 0. + * + * Calling this method implicitely calls end() if the query + * is currently active. + */ + GLuint getResult() { + if (!mQuery) return 0; + + end(); + + GLuint result; + glGetQueryObjectuiv(mQuery, GL_QUERY_RESULT, &result); + return result; + } + + +private: + bool mActive; + GLenum mTarget; + bool mCanQuery; + GLuint mQuery; + +}; // class Query + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_QUERY_H diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index f50ac3c..dabd8d4 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -21,9 +21,15 @@ #include <utils/Log.h> +#include "Vertex.h" + namespace android { namespace uirenderer { +#define RECT_STRING "%7.2f %7.2f %7.2f %7.2f" +#define RECT_ARGS(r) \ + (r).left, (r).top, (r).right, (r).bottom + /////////////////////////////////////////////////////////////////////////////// // Structs /////////////////////////////////////////////////////////////////////////////// @@ -125,11 +131,11 @@ public: return intersect(r.left, r.top, r.right, r.bottom); } - inline bool contains(float l, float t, float r, float b) { + inline bool contains(float l, float t, float r, float b) const { return l >= left && t >= top && r <= right && b <= bottom; } - inline bool contains(const Rect& r) { + inline bool contains(const Rect& r) const { return contains(r.left, r.top, r.right, r.bottom); } @@ -166,6 +172,40 @@ public: bottom += delta; } + /** + * Similar to snapToPixelBoundaries, but estimates bounds conservatively to handle GL rounding + * errors. + * + * This function should be used whenever estimating the damage rect of geometry already mapped + * into layer space. + */ + void snapGeometryToPixelBoundaries(bool snapOut) { + if (snapOut) { + /* For AA geometry with a ramp perimeter, don't snap by rounding - AA geometry will have + * a 0.5 pixel perimeter not accounted for in its bounds. Instead, snap by + * conservatively rounding out the bounds with floor/ceil. + * + * In order to avoid changing integer bounds with floor/ceil due to rounding errors + * inset the bounds first by the fudge factor. Very small fraction-of-a-pixel errors + * 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); + } 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); + } + } + void snapToPixelBoundaries() { left = floorf(left + 0.5f); top = floorf(top + 0.5f); diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp index 347bd78..3f77021 100644 --- a/libs/hwui/ResourceCache.cpp +++ b/libs/hwui/ResourceCache.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#define LOG_TAG "OpenGLRenderer" + #include <SkPixelRef.h> #include "ResourceCache.h" #include "Caches.h" @@ -60,7 +62,7 @@ void ResourceCache::incrementRefcount(void* resource, ResourceType resourceType) } void ResourceCache::incrementRefcount(SkBitmap* bitmapResource) { - SkSafeRef(bitmapResource->pixelRef()); + bitmapResource->pixelRef()->globalRef(); SkSafeRef(bitmapResource->getColorTable()); incrementRefcount((void*) bitmapResource, kBitmap); } @@ -79,6 +81,10 @@ void ResourceCache::incrementRefcount(SkiaColorFilter* filterResource) { incrementRefcount((void*) filterResource, kColorFilter); } +void ResourceCache::incrementRefcount(Res_png_9patch* patchResource) { + incrementRefcount((void*) patchResource, kNinePatch); +} + void ResourceCache::incrementRefcount(Layer* layerResource) { incrementRefcount((void*) layerResource, kLayer); } @@ -94,7 +100,7 @@ void ResourceCache::incrementRefcountLocked(void* resource, ResourceType resourc } void ResourceCache::incrementRefcountLocked(SkBitmap* bitmapResource) { - SkSafeRef(bitmapResource->pixelRef()); + bitmapResource->pixelRef()->globalRef(); SkSafeRef(bitmapResource->getColorTable()); incrementRefcountLocked((void*) bitmapResource, kBitmap); } @@ -113,6 +119,10 @@ void ResourceCache::incrementRefcountLocked(SkiaColorFilter* filterResource) { incrementRefcountLocked((void*) filterResource, kColorFilter); } +void ResourceCache::incrementRefcountLocked(Res_png_9patch* patchResource) { + incrementRefcountLocked((void*) patchResource, kNinePatch); +} + void ResourceCache::incrementRefcountLocked(Layer* layerResource) { incrementRefcountLocked((void*) layerResource, kLayer); } @@ -123,7 +133,7 @@ void ResourceCache::decrementRefcount(void* resource) { } void ResourceCache::decrementRefcount(SkBitmap* bitmapResource) { - SkSafeUnref(bitmapResource->pixelRef()); + bitmapResource->pixelRef()->globalUnref(); SkSafeUnref(bitmapResource->getColorTable()); decrementRefcount((void*) bitmapResource); } @@ -142,6 +152,10 @@ void ResourceCache::decrementRefcount(SkiaColorFilter* filterResource) { decrementRefcount((void*) filterResource); } +void ResourceCache::decrementRefcount(Res_png_9patch* patchResource) { + decrementRefcount((void*) patchResource); +} + void ResourceCache::decrementRefcount(Layer* layerResource) { decrementRefcount((void*) layerResource); } @@ -160,7 +174,7 @@ void ResourceCache::decrementRefcountLocked(void* resource) { } void ResourceCache::decrementRefcountLocked(SkBitmap* bitmapResource) { - SkSafeUnref(bitmapResource->pixelRef()); + bitmapResource->pixelRef()->globalUnref(); SkSafeUnref(bitmapResource->getColorTable()); decrementRefcountLocked((void*) bitmapResource); } @@ -179,6 +193,10 @@ void ResourceCache::decrementRefcountLocked(SkiaColorFilter* filterResource) { decrementRefcountLocked((void*) filterResource); } +void ResourceCache::decrementRefcountLocked(Res_png_9patch* patchResource) { + decrementRefcountLocked((void*) patchResource); +} + void ResourceCache::decrementRefcountLocked(Layer* layerResource) { decrementRefcountLocked((void*) layerResource); } @@ -265,6 +283,30 @@ void ResourceCache::destructorLocked(SkiaColorFilter* resource) { } } +void ResourceCache::destructor(Res_png_9patch* resource) { + Mutex::Autolock _l(mLock); + destructorLocked(resource); +} + +void ResourceCache::destructorLocked(Res_png_9patch* resource) { + ssize_t index = mCache->indexOfKey(resource); + ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL; + if (ref == NULL) { + if (Caches::hasInstance()) { + Caches::getInstance().patchCache.removeDeferred(resource); + } + // If we're not tracking this resource, just delete it + // A Res_png_9patch is actually an array of byte that's larger + // than sizeof(Res_png_9patch). It must be freed as an array. + delete[] (int8_t*) resource; + return; + } + ref->destroyed = true; + if (ref->refCount == 0) { + deleteResourceReferenceLocked(resource, ref); + } +} + /** * Return value indicates whether resource was actually recycled, which happens when RefCnt * reaches 0. @@ -335,6 +377,16 @@ void ResourceCache::deleteResourceReferenceLocked(void* resource, ResourceRefere delete filter; } break; + case kNinePatch: { + if (Caches::hasInstance()) { + Caches::getInstance().patchCache.removeDeferred((Res_png_9patch*) resource); + } + // A Res_png_9patch is actually an array of byte that's larger + // than sizeof(Res_png_9patch). It must be freed as an array. + int8_t* patch = (int8_t*) resource; + delete[] patch; + } + break; case kLayer: { Layer* layer = (Layer*) resource; Caches::getInstance().deleteLayerDeferred(layer); diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h index ab493e5..ea0c1b5 100644 --- a/libs/hwui/ResourceCache.h +++ b/libs/hwui/ResourceCache.h @@ -22,7 +22,11 @@ #include <SkBitmap.h> #include <SkiaColorFilter.h> #include <SkiaShader.h> + #include <utils/KeyedVector.h> + +#include <androidfw/ResourceTypes.h> + #include "Layer.h" namespace android { @@ -35,6 +39,7 @@ enum ResourceType { kBitmap, kShader, kColorFilter, + kNinePatch, kPath, kLayer }; @@ -69,35 +74,41 @@ public: void incrementRefcount(SkBitmap* resource); void incrementRefcount(SkiaShader* resource); void incrementRefcount(SkiaColorFilter* resource); + void incrementRefcount(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(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(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(Layer* resource); void destructor(SkPath* resource); void destructor(SkBitmap* resource); void destructor(SkiaShader* resource); void destructor(SkiaColorFilter* 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(Res_png_9patch* resource); bool recycle(SkBitmap* resource); bool recycleLocked(SkBitmap* resource); diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index c38eedb..797ed10 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -69,9 +69,13 @@ void SkiaShader::copyFrom(const SkiaShader& shader) { mGenerationId = shader.mGenerationId; } +SkiaShader::SkiaShader(): mCaches(NULL) { +} + 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) { + mType(type), mKey(key), mTileX(tileX), mTileY(tileY), mBlend(blend), + mCaches(NULL) { setMatrix(matrix); mGenerationId = 0; } @@ -87,7 +91,7 @@ void SkiaShader::setupProgram(Program* program, const mat4& modelView, const Sna } void SkiaShader::bindTexture(Texture* texture, GLenum wrapS, GLenum wrapT) { - glBindTexture(GL_TEXTURE_2D, texture->id); + mCaches->bindTexture(texture->id); texture->setWrapST(wrapS, wrapT); } @@ -114,7 +118,7 @@ SkiaShader* SkiaBitmapShader::copy() { } void SkiaBitmapShader::describe(ProgramDescription& description, const Extensions& extensions) { - Texture* texture = mTextureCache->get(mBitmap); + Texture* texture = mCaches->textureCache.get(mBitmap); if (!texture) return; mTexture = texture; @@ -229,7 +233,7 @@ void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelV GLuint textureSlot = (*textureUnit)++; Caches::getInstance().activeTexture(textureSlot); - Texture* texture = mGradientCache->get(mColors, mPositions, mCount); + Texture* texture = mCaches->gradientCache.get(mColors, mPositions, mCount); // Uniforms bindTexture(texture, gTileModes[mTileX], gTileModes[mTileY]); @@ -349,7 +353,7 @@ void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelVi GLuint textureSlot = (*textureUnit)++; Caches::getInstance().activeTexture(textureSlot); - Texture* texture = mGradientCache->get(mColors, mPositions, mCount); + Texture* texture = mCaches->gradientCache.get(mColors, mPositions, mCount); // Uniforms bindTexture(texture, gTileModes[mTileX], gTileModes[mTileY]); @@ -359,7 +363,7 @@ void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelVi bindUniformColor(program->getUniform("endColor"), mColors[1]); } - Caches::getInstance().dither.setupProgram(program, textureUnit); + mCaches->dither.setupProgram(program, textureUnit); mat4 screenSpace; computeScreenSpaceMatrix(screenSpace, modelView); @@ -394,12 +398,6 @@ SkiaShader* SkiaComposeShader::copy() { return copy; } -void SkiaComposeShader::set(TextureCache* textureCache, GradientCache* gradientCache) { - SkiaShader::set(textureCache, gradientCache); - mFirst->set(textureCache, gradientCache); - mSecond->set(textureCache, gradientCache); -} - void SkiaComposeShader::describe(ProgramDescription& description, const Extensions& extensions) { mFirst->describe(description, extensions); mSecond->describe(description, extensions); diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h index bc12b0d..a63431c 100644 --- a/libs/hwui/SkiaShader.h +++ b/libs/hwui/SkiaShader.h @@ -33,6 +33,8 @@ namespace android { namespace uirenderer { +class Caches; + /////////////////////////////////////////////////////////////////////////////// // Base shader /////////////////////////////////////////////////////////////////////////////// @@ -77,9 +79,8 @@ struct SkiaShader { return mType; } - virtual void set(TextureCache* textureCache, GradientCache* gradientCache) { - mTextureCache = textureCache; - mGradientCache = gradientCache; + virtual void setCaches(Caches& caches) { + mCaches = &caches; } uint32_t getGenerationId() { @@ -103,8 +104,7 @@ struct SkiaShader { void computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView); protected: - SkiaShader() { - } + SkiaShader(); /** * The appropriate texture unit must have been activated prior to invoking @@ -118,8 +118,7 @@ protected: SkShader::TileMode mTileY; bool mBlend; - TextureCache* mTextureCache; - GradientCache* mGradientCache; + Caches* mCaches; mat4 mUnitMatrix; mat4 mShaderMatrix; @@ -229,7 +228,11 @@ struct SkiaComposeShader: public SkiaShader { ~SkiaComposeShader(); SkiaShader* copy(); - void set(TextureCache* textureCache, GradientCache* gradientCache); + 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, diff --git a/libs/hwui/Stencil.cpp b/libs/hwui/Stencil.cpp index ba2e6f2..2764523 100644 --- a/libs/hwui/Stencil.cpp +++ b/libs/hwui/Stencil.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "Debug.h" #include "Extensions.h" #include "Properties.h" #include "Stencil.h" diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp index 6976eaa..0b2c130 100644 --- a/libs/hwui/TextDropShadowCache.cpp +++ b/libs/hwui/TextDropShadowCache.cpp @@ -18,6 +18,7 @@ #include <utils/JenkinsHash.h> +#include "Caches.h" #include "Debug.h" #include "TextDropShadowCache.h" #include "Properties.h" @@ -154,7 +155,7 @@ void TextDropShadowCache::operator()(ShadowText& text, ShadowTexture*& texture) ALOGD("Shadow texture deleted, size = %d", texture->bitmapSize); } - glDeleteTextures(1, &texture->id); + texture->deleteTexture(); delete texture; } } @@ -182,7 +183,9 @@ ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32 return NULL; } - texture = new ShadowTexture; + Caches& caches = Caches::getInstance(); + + texture = new ShadowTexture(caches); texture->left = shadow.penX; texture->top = shadow.penY; texture->width = shadow.width; @@ -202,7 +205,7 @@ ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32 glGenTextures(1, &texture->id); - glBindTexture(GL_TEXTURE_2D, texture->id); + caches.bindTexture(texture->id); // Textures are Alpha8 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h index 0bed72b6..04d7357 100644 --- a/libs/hwui/TextDropShadowCache.h +++ b/libs/hwui/TextDropShadowCache.h @@ -30,6 +30,8 @@ namespace android { namespace uirenderer { +class Caches; + struct ShadowText { ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(NULL), flags(0), italicStyle(0.0f), scaleX(0), text(NULL), positions(NULL) { @@ -114,7 +116,7 @@ inline hash_t hash_type(const ShadowText& entry) { * Alpha texture used to represent a shadow. */ struct ShadowTexture: public Texture { - ShadowTexture(): Texture() { + ShadowTexture(Caches& caches): Texture(caches) { } float left; diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp new file mode 100644 index 0000000..7923ce7 --- /dev/null +++ b/libs/hwui/Texture.cpp @@ -0,0 +1,84 @@ +/* + * 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 <utils/Log.h> + +#include "Caches.h" +#include "Texture.h" + +namespace android { +namespace uirenderer { + +Texture::Texture(): id(0), generation(0), blend(false), width(0), height(0), + cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL), + 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), + mWrapS(GL_CLAMP_TO_EDGE), mWrapT(GL_CLAMP_TO_EDGE), + mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST), + mFirstFilter(true), mFirstWrap(true), mCaches(caches) { +} + +void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force, + GLenum renderTarget) { + + if (mFirstWrap || force || wrapS != mWrapS || wrapT != mWrapT) { + mFirstWrap = false; + + mWrapS = wrapS; + mWrapT = wrapT; + + if (bindTexture) { + mCaches.bindTexture(renderTarget, id); + } + + glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS); + glTexParameteri(renderTarget, GL_TEXTURE_WRAP_T, wrapT); + } +} + +void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force, + GLenum renderTarget) { + + if (mFirstFilter || force || min != mMinFilter || mag != mMagFilter) { + mFirstFilter = false; + + mMinFilter = min; + mMagFilter = mag; + + if (bindTexture) { + mCaches.bindTexture(renderTarget, id); + } + + if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR; + + glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min); + glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag); + } +} + +void Texture::deleteTexture() const { + mCaches.deleteTexture(id); +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index 8d88bdc..d48ec59 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -22,75 +22,39 @@ namespace android { namespace uirenderer { +class Caches; +class UvMapper; + /** * Represents an OpenGL texture. */ -struct Texture { - Texture() { - cleanup = false; - bitmapSize = 0; - - wrapS = GL_CLAMP_TO_EDGE; - wrapT = GL_CLAMP_TO_EDGE; - - minFilter = GL_NEAREST; - magFilter = GL_NEAREST; - - mipMap = false; - - firstFilter = true; - firstWrap = true; +class Texture { +public: + Texture(); + Texture(Caches& caches); - id = 0; - } + virtual ~Texture() { } - void setWrap(GLenum wrap, bool bindTexture = false, bool force = false, + inline void setWrap(GLenum wrap, bool bindTexture = false, bool force = false, GLenum renderTarget = GL_TEXTURE_2D) { setWrapST(wrap, wrap, bindTexture, force, renderTarget); } - void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, bool force = false, - GLenum renderTarget = GL_TEXTURE_2D) { - - if (firstWrap || force || wrapS != this->wrapS || wrapT != this->wrapT) { - firstWrap = false; - - this->wrapS = wrapS; - this->wrapT = wrapT; - - if (bindTexture) { - glBindTexture(renderTarget, id); - } - - glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS); - glTexParameteri(renderTarget, GL_TEXTURE_WRAP_T, wrapT); - } - } + virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, + bool force = false, GLenum renderTarget = GL_TEXTURE_2D); - void setFilter(GLenum filter, bool bindTexture = false, bool force = false, + inline void setFilter(GLenum filter, bool bindTexture = false, bool force = false, GLenum renderTarget = GL_TEXTURE_2D) { setFilterMinMag(filter, filter, bindTexture, force, renderTarget); } - void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false, bool force = false, - GLenum renderTarget = GL_TEXTURE_2D) { + virtual void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false, + bool force = false, GLenum renderTarget = GL_TEXTURE_2D); - if (firstFilter || force || min != minFilter || mag != magFilter) { - firstFilter = false; - - minFilter = min; - magFilter = mag; - - if (bindTexture) { - glBindTexture(renderTarget, id); - } - - if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR; - - glTexParameteri(renderTarget, GL_TEXTURE_MIN_FILTER, min); - glTexParameteri(renderTarget, GL_TEXTURE_MAG_FILTER, mag); - } - } + /** + * Convenience method to call glDeleteTextures() on this texture's id. + */ + void deleteTexture() const; /** * Name of the texture. @@ -125,21 +89,28 @@ struct Texture { */ bool mipMap; + /** + * Optional, pointer to a texture coordinates mapper. + */ + const UvMapper* uvMapper; + private: /** * Last wrap modes set on this texture. Defaults to GL_CLAMP_TO_EDGE. */ - GLenum wrapS; - GLenum wrapT; + GLenum mWrapS; + GLenum mWrapT; /** * Last filters set on this texture. Defaults to GL_NEAREST. */ - GLenum minFilter; - GLenum magFilter; + GLenum mMinFilter; + GLenum mMagFilter; + + bool mFirstFilter; + bool mFirstWrap; - bool firstFilter; - bool firstWrap; + Caches& mCaches; }; // struct Texture class AutoTexture { @@ -147,7 +118,7 @@ public: AutoTexture(const Texture* texture): mTexture(texture) { } ~AutoTexture() { if (mTexture && mTexture->cleanup) { - glDeleteTextures(1, &mTexture->id); + mTexture->deleteTexture(); delete mTexture; } } diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index 2378eb5..ed0a79a 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -112,7 +112,7 @@ void TextureCache::operator()(SkBitmap*& bitmap, Texture*& texture) { if (mDebugEnabled) { ALOGD("Texture deleted, size = %d", texture->bitmapSize); } - glDeleteTextures(1, &texture->id); + texture->deleteTexture(); delete texture; } } @@ -139,7 +139,7 @@ Texture* TextureCache::get(SkBitmap* bitmap) { } } - texture = new Texture; + texture = new Texture(); texture->bitmapSize = size; generateTexture(bitmap, texture, false); @@ -162,7 +162,7 @@ Texture* TextureCache::get(SkBitmap* bitmap) { } Texture* TextureCache::getTransient(SkBitmap* bitmap) { - Texture* texture = new Texture; + Texture* texture = new Texture(); texture->bitmapSize = bitmap->rowBytes() * bitmap->height(); texture->cleanup = true; @@ -235,25 +235,25 @@ void TextureCache::generateTexture(SkBitmap* bitmap, Texture* texture, bool rege texture->width = bitmap->width(); texture->height = bitmap->height(); - glBindTexture(GL_TEXTURE_2D, texture->id); + Caches::getInstance().bindTexture(texture->id); switch (bitmap->getConfig()) { case SkBitmap::kA8_Config: glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), texture->height, - GL_UNSIGNED_BYTE, bitmap->getPixels()); + uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), + texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels()); texture->blend = true; break; case SkBitmap::kRGB_565_Config: glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); - uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(), texture->height, - GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels()); + uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(), + texture->width, texture->height, GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels()); texture->blend = false; break; case SkBitmap::kARGB_8888_Config: glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); - uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), texture->height, - GL_UNSIGNED_BYTE, bitmap->getPixels()); + uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), + texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels()); // Do this after calling getPixels() to make sure Skia's deferred // decoding happened texture->blend = !bitmap->isOpaque(); @@ -293,17 +293,28 @@ void TextureCache::uploadLoFiTexture(bool resize, SkBitmap* bitmap, SkCanvas canvas(rgbaBitmap); canvas.drawBitmap(*bitmap, 0.0f, 0.0f, NULL); - uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), height, + uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), width, height, GL_UNSIGNED_BYTE, rgbaBitmap.getPixels()); } -void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height, - GLenum type, const GLvoid * data) { +void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei stride, + GLsizei width, GLsizei height, GLenum type, const GLvoid * data) { + // TODO: With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer + // if the stride doesn't match the width + const bool useStride = stride != width && Extensions::getInstance().hasUnpackRowLength(); + if (useStride) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); + } + if (resize) { glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data); } else { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data); } + + if (useStride) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + } } }; // namespace uirenderer diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h index 80bb22e..57fc19a 100644 --- a/libs/hwui/TextureCache.h +++ b/libs/hwui/TextureCache.h @@ -125,8 +125,8 @@ private: void generateTexture(SkBitmap* bitmap, Texture* texture, bool regenerate = false); void uploadLoFiTexture(bool resize, SkBitmap* bitmap, uint32_t width, uint32_t height); - void uploadToTexture(bool resize, GLenum format, GLsizei width, GLsizei height, - GLenum type, const GLvoid * data); + void uploadToTexture(bool resize, GLenum format, GLsizei stride, + GLsizei width, GLsizei height, GLenum type, const GLvoid * data); void init(); diff --git a/libs/hwui/UvMapper.h b/libs/hwui/UvMapper.h new file mode 100644 index 0000000..70428d2 --- /dev/null +++ b/libs/hwui/UvMapper.h @@ -0,0 +1,133 @@ +/* + * 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_UV_MAPPER_H +#define ANDROID_HWUI_UV_MAPPER_H + +#include "Rect.h" + +namespace android { +namespace uirenderer { + +/** + * This class can be used to map UV coordinates from the [0..1] + * range to other arbitrary ranges. All the methods below assume + * that the input values lie in the [0..1] range already. + */ +class UvMapper { +public: + /** + * Using this constructor is equivalent to not using any mapping at all. + * UV coordinates in the [0..1] range remain in the [0..1] range. + */ + UvMapper(): mIdentity(true), mMinU(0.0f), mMaxU(1.0f), mMinV(0.0f), mMaxV(1.0f) { + } + + /** + * Creates a new mapper with the specified ranges for U and V coordinates. + * The parameter minU must be < maxU and minV must be < maxV. + */ + UvMapper(float minU, float maxU, float minV, float maxV): + mMinU(minU), mMaxU(maxU), mMinV(minV), mMaxV(maxV) { + checkIdentity(); + } + + /** + * Returns true if calling the map*() methods has no effect (that is, + * texture coordinates remain in the 0..1 range.) + */ + bool isIdentity() const { + return mIdentity; + } + + /** + * Changes the U and V mapping ranges. + * The parameter minU must be < maxU and minV must be < maxV. + */ + void setMapping(float minU, float maxU, float minV, float maxV) { + mMinU = minU; + mMaxU = maxU; + mMinV = minV; + mMaxV = maxV; + checkIdentity(); + } + + /** + * Maps a single value in the U range. + */ + void mapU(float& u) const { + if (!mIdentity) u = lerp(mMinU, mMaxU, u); + } + + /** + * Maps a single value in the V range. + */ + void mapV(float& v) const { + if (!mIdentity) v = lerp(mMinV, mMaxV, v); + } + + /** + * Maps the specified rectangle in place. This method assumes: + * - left = min. U + * - top = min. V + * - right = max. U + * - bottom = max. V + */ + void map(Rect& texCoords) const { + if (!mIdentity) { + texCoords.left = lerp(mMinU, mMaxU, texCoords.left); + texCoords.right = lerp(mMinU, mMaxU, texCoords.right); + texCoords.top = lerp(mMinV, mMaxV, texCoords.top); + texCoords.bottom = lerp(mMinV, mMaxV, texCoords.bottom); + } + } + + /** + * Maps the specified UV coordinates in place. + */ + void map(float& u1, float& v1, float& u2, float& v2) const { + if (!mIdentity) { + u1 = lerp(mMinU, mMaxU, u1); + u2 = lerp(mMinU, mMaxU, u2); + v1 = lerp(mMinV, mMaxV, v1); + v2 = lerp(mMinV, mMaxV, v2); + } + } + + void dump() const { + ALOGD("mapper[minU=%.2f maxU=%.2f minV=%.2f maxV=%.2f]", mMinU, mMaxU, mMinV, mMaxV); + } + +private: + static float lerp(float start, float stop, float amount) { + return start + (stop - start) * amount; + } + + void checkIdentity() { + mIdentity = mMinU == 0.0f && mMaxU == 1.0f && mMinV == 0.0f && mMaxV == 1.0f; + } + + bool mIdentity; + float mMinU; + float mMaxU; + float mMinV; + float mMaxV; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_UV_MAPPER_H diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h index 523120e..790d4fc 100644 --- a/libs/hwui/Vertex.h +++ b/libs/hwui/Vertex.h @@ -17,6 +17,8 @@ #ifndef ANDROID_HWUI_VERTEX_H #define ANDROID_HWUI_VERTEX_H +#include "Vector.h" + namespace android { namespace uirenderer { @@ -24,12 +26,30 @@ namespace uirenderer { * Simple structure to describe a vertex with a position and a texture. */ struct Vertex { + /** + * Fudge-factor used to disambiguate geometry pixel positioning. + * + * Used to offset lines and points to avoid ambiguous intersection with pixel centers (see + * Program::set()), and used to make geometry damage rect calculation conservative (see + * Rect::snapGeometryToPixelBoundaries()) + */ + static const float gGeometryFudgeFactor = 0.0656f; + float position[2]; static inline void set(Vertex* vertex, float x, float y) { vertex[0].position[0] = x; vertex[0].position[1] = y; } + + static inline void set(Vertex* vertex, vec2 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); + } + }; // struct Vertex /** @@ -81,6 +101,12 @@ struct AlphaVertex : Vertex { vertex[0].alpha = alpha; } + 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[0].alpha = src.alpha; + } + static inline void setColor(AlphaVertex* vertex, float alpha) { vertex[0].alpha = alpha; } diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp index 6c5267d..d5f38b5 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -17,6 +17,7 @@ #include <SkGlyph.h> #include "CacheTexture.h" +#include "../Caches.h" #include "../Debug.h" #include "../Extensions.h" #include "../PixelBuffer.h" @@ -107,17 +108,18 @@ CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) // CacheTexture /////////////////////////////////////////////////////////////////////////////// -CacheTexture::CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount) : - mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), +CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount) : + mTexture(NULL), mTextureId(0), mWidth(width), mHeight(height), mFormat(format), mLinearFiltering(false), mDirty(false), mNumGlyphs(0), - mMesh(NULL), mCurrentQuad(0), mMaxQuadCount(maxQuadCount) { + 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); // 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. // With OpenGL ES 2.0 we have to upload entire stripes instead. - mHasES3 = Extensions::getInstance().getMajorGlVersion() >= 3; + mHasUnpackRowLength = Extensions::getInstance().hasUnpackRowLength(); } CacheTexture::~CacheTexture() { @@ -154,7 +156,7 @@ void CacheTexture::releaseTexture() { mTexture = NULL; } if (mTextureId) { - glDeleteTextures(1, &mTextureId); + mCaches.deleteTexture(mTextureId); mTextureId = 0; } mDirty = false; @@ -166,7 +168,7 @@ void CacheTexture::setLinearFiltering(bool linearFiltering, bool bind) { mLinearFiltering = linearFiltering; const GLenum filtering = linearFiltering ? GL_LINEAR : GL_NEAREST; - if (bind) glBindTexture(GL_TEXTURE_2D, getTextureId()); + if (bind) mCaches.bindTexture(getTextureId()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); } @@ -180,17 +182,17 @@ void CacheTexture::allocateMesh() { void CacheTexture::allocateTexture() { if (!mTexture) { - mTexture = PixelBuffer::create(GL_ALPHA, mWidth, mHeight); + mTexture = PixelBuffer::create(mFormat, mWidth, mHeight); } if (!mTextureId) { glGenTextures(1, &mTextureId); - glBindTexture(GL_TEXTURE_2D, mTextureId); + mCaches.bindTexture(mTextureId); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Initialize texture dimensions - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mWidth, mHeight, 0, - GL_ALPHA, GL_UNSIGNED_BYTE, 0); + glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, + mFormat, GL_UNSIGNED_BYTE, 0); const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); @@ -204,22 +206,21 @@ void CacheTexture::allocateTexture() { bool CacheTexture::upload() { const Rect& dirtyRect = mDirtyRect; - uint32_t x = mHasES3 ? dirtyRect.left : 0; + uint32_t x = mHasUnpackRowLength ? dirtyRect.left : 0; uint32_t y = dirtyRect.top; - uint32_t width = mHasES3 ? dirtyRect.getWidth() : mWidth; + uint32_t width = mHasUnpackRowLength ? dirtyRect.getWidth() : mWidth; uint32_t height = dirtyRect.getHeight(); // The unpack row length only needs to be specified when a new // texture is bound - if (mHasES3) { + if (mHasUnpackRowLength) { glPixelStorei(GL_UNPACK_ROW_LENGTH, mWidth); } - mTexture->upload(x, y, width, height, y * mWidth + x); - + mTexture->upload(x, y, width, height); setDirty(false); - return mHasES3; + return mHasUnpackRowLength; } void CacheTexture::setDirty(bool dirty) { @@ -230,6 +231,32 @@ void CacheTexture::setDirty(bool dirty) { } bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) { + switch (glyph.fMaskFormat) { + case SkMask::kA8_Format: + case SkMask::kBW_Format: + if (mFormat != GL_ALPHA) { +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs", + mFormat); +#endif + return false; + } + break; + case SkMask::kARGB32_Format: + if (mFormat != GL_RGBA) { +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat); +#endif + return false; + } + break; + default: +#if DEBUG_FONT_RENDERER + ALOGD("fitBitmap: unknown glyph format %x encountered", glyph.fMaskFormat); +#endif + return false; + } + if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > mHeight) { return false; } diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h index ddcc836..61b38f8 100644 --- a/libs/hwui/font/CacheTexture.h +++ b/libs/hwui/font/CacheTexture.h @@ -24,13 +24,14 @@ #include <utils/Log.h> #include "FontUtil.h" +#include "../PixelBuffer.h" #include "../Rect.h" #include "../Vertex.h" namespace android { namespace uirenderer { -class PixelBuffer; +class Caches; /** * CacheBlock is a node in a linked list of current free space areas in a CacheTexture. @@ -73,7 +74,7 @@ struct CacheBlock { class CacheTexture { public: - CacheTexture(uint16_t width, uint16_t height, uint32_t maxQuadCount); + CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount); ~CacheTexture(); void reset(); @@ -99,6 +100,14 @@ public: return mHeight; } + inline GLenum getFormat() const { + return mFormat; + } + + inline uint32_t getOffset(uint16_t x, uint16_t y) const { + return (y * mWidth + x) * PixelBuffer::formatSize(mFormat); + } + inline const Rect* getDirtyRect() const { return &mDirtyRect; } @@ -150,9 +159,9 @@ public: float x3, float y3, float u3, float v3, float x4, float y4, float u4, float v4) { TextureVertex* mesh = mMesh + mCurrentQuad * 4; - TextureVertex::set(mesh++, x1, y1, u1, v1); TextureVertex::set(mesh++, x2, y2, u2, v2); TextureVertex::set(mesh++, x3, y3, u3, v3); + TextureVertex::set(mesh++, x1, y1, u1, v1); TextureVertex::set(mesh++, x4, y4, u4, v4); mCurrentQuad++; } @@ -172,15 +181,17 @@ private: GLuint mTextureId; uint16_t mWidth; uint16_t mHeight; + GLenum mFormat; bool mLinearFiltering; bool mDirty; uint16_t mNumGlyphs; TextureVertex* mMesh; uint32_t mCurrentQuad; uint32_t mMaxQuadCount; + Caches& mCaches; CacheBlock* mCacheBlocks; + bool mHasUnpackRowLength; Rect mDirtyRect; - bool mHasES3; }; }; // namespace uirenderer diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index 011cfc1..18983d8 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -55,6 +55,7 @@ Font::FontDescription::FontDescription(const SkPaint* paint, const mat4& matrix) mStyle = paint->getStyle(); 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])); @@ -80,6 +81,7 @@ hash_t Font::FontDescription::hash() const { hash = JenkinsHashMix(hash, android::hash_type(mStyle)); hash = JenkinsHashMix(hash, android::hash_type(mStrokeWidth)); hash = JenkinsHashMix(hash, int(mAntiAliasing)); + hash = JenkinsHashMix(hash, android::hash_type(mHinting)); hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleX])); hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleY])); return JenkinsHashWhiten(hash); @@ -111,6 +113,9 @@ int Font::FontDescription::compare(const Font::FontDescription& lhs, deltaInt = int(lhs.mAntiAliasing) - int(rhs.mAntiAliasing); if (deltaInt != 0) return deltaInt; + deltaInt = int(lhs.mHinting) - int(rhs.mHinting); + if (deltaInt != 0) return deltaInt; + if (lhs.mLookupTransform[SkMatrix::kMScaleX] < rhs.mLookupTransform[SkMatrix::kMScaleX]) return -1; if (lhs.mLookupTransform[SkMatrix::kMScaleX] > diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h index 52cca1c..9e7ec2d 100644 --- a/libs/hwui/font/Font.h +++ b/libs/hwui/font/Font.h @@ -69,6 +69,7 @@ public: uint8_t mStyle; float mStrokeWidth; bool mAntiAliasing; + uint8_t mHinting; SkMatrix mLookupTransform; SkMatrix mInverseLookupTransform; }; diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h index f758666..cdcb23c 100644 --- a/libs/hwui/font/FontUtil.h +++ b/libs/hwui/font/FontUtil.h @@ -31,6 +31,9 @@ #define DEFAULT_TEXT_LARGE_CACHE_HEIGHT 512 #define TEXTURE_BORDER_SIZE 1 +#if TEXTURE_BORDER_SIZE != 1 +# error TEXTURE_BORDER_SIZE other than 1 is not currently supported +#endif #define CACHE_BLOCK_ROUNDING_SIZE 4 diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp index c8bfd9c..189895c 100644 --- a/libs/hwui/thread/TaskManager.cpp +++ b/libs/hwui/thread/TaskManager.cpp @@ -29,7 +29,7 @@ namespace uirenderer { TaskManager::TaskManager() { // Get the number of available CPUs. This value does not change over time. - int cpuCount = sysconf(_SC_NPROCESSORS_ONLN); + int cpuCount = sysconf(_SC_NPROCESSORS_CONF); for (int i = 0; i < cpuCount / 2; i++) { String8 name; diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h index 477314b..f2a216f 100644 --- a/libs/hwui/thread/TaskManager.h +++ b/libs/hwui/thread/TaskManager.h @@ -43,7 +43,7 @@ public: /** * Returns true if this task manager can run tasks, * false otherwise. This method will typically return - * true on a single CPU core device. + * false on a single CPU core device. */ bool canRunTasks() const; |