diff options
113 files changed, 3102 insertions, 111 deletions
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index c8b45c78c5b5..c6ae14ea9bc1 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -67,4 +67,6 @@ interface IInputManager { // Input device vibrator control. void vibrate(int deviceId, in long[] pattern, int repeat, IBinder token); void cancelVibrate(int deviceId, IBinder token); + + void setPointerIconShape(int shapeId); } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index bae5757b03a7..3949d97e4088 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -757,6 +757,22 @@ public final class InputManager { } } + /** + * Changes the mouse pointer's icon shape into the specified id. + * + * @param iconId The id of the pointer graphic, as a value between + * {@link PointerIcon.STYLE_ARROW} and {@link PointerIcon.STYLE_GRABBING}. + * + * @hide + */ + public void setPointerIconShape(int iconId) { + try { + mIm.setPointerIconShape(iconId); + } catch (RemoteException ex) { + // Do nothing. + } + } + private void populateInputDevicesLocked() { if (mInputDevicesChangedListener == null) { final InputDevicesChangedListener listener = new InputDevicesChangedListener(); diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 85041adbe975..f7c8662515fc 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -577,7 +577,7 @@ public class Build { public static final int KITKAT = 19; /** - * Android 4.4W: KitKat for watches, snacks on the run. + * June 2014: Android 4.4W. KitKat for watches, snacks on the run. * * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> @@ -595,7 +595,7 @@ public class Build { public static final int L = 21; /** - * Lollipop. A flat one with beautiful shadows. But still tasty. + * November 2014: Lollipop. A flat one with beautiful shadows. But still tasty. * * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> @@ -626,12 +626,38 @@ public class Build { public static final int LOLLIPOP = 21; /** - * Lollipop with an extra sugar coating on the outside! + * March 2015: Lollipop with an extra sugar coating on the outside! */ public static final int LOLLIPOP_MR1 = 22; /** - * M comes after L. + * M is for Marshmallow! + * + * <p>Applications targeting this or a later release will get these + * new changes in behavior:</p> + * <ul> + * <li> Runtime permissions. Dangerous permissions are no longer granted at + * install time, but must be requested by the application at runtime through + * {@link android.app.Activity#requestPermissions}.</li> + * <li> Bluetooth and Wi-Fi scanning now requires holding the location permission.</li> + * <li> {@link android.app.AlarmManager#setTimeZone AlarmManager.setTimeZone} will fail if + * the given timezone is non-Olson.</li> + * <li> Activity transitions will only return shared + * elements mapped in the returned view hierarchy back to the calling activity.</li> + * <li> {@link android.view.View} allows a number of behaviors that may break + * existing apps: Canvas throws an exception if restore() is called too many times, + * widgets may return a hint size when returning UNSPECIFIED measure specs, and it + * will respect the attributes {@link android.R.attr#foreground}, + * {@link android.R.attr#foregroundGravity}, {@link android.R.attr#foregroundTint}, and + * {@link android.R.attr#foregroundTintMode}.</li> + * <li> {@link android.view.MotionEvent#getButtonState MotionEvent.getButtonState} + * will no longer report {@link android.view.MotionEvent#BUTTON_PRIMARY} + * and {@link android.view.MotionEvent#BUTTON_SECONDARY} as synonyms for + * {@link android.view.MotionEvent#BUTTON_STYLUS_PRIMARY} and + * {@link android.view.MotionEvent#BUTTON_STYLUS_SECONDARY}.</li> + * <li> {@link android.widget.ScrollView} now respects the layout param margins + * when measuring.</li> + * </ul> */ public static final int M = 23; diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 979c828c476e..2445bc28abd8 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -383,6 +383,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION); filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED); + filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION); mContext.registerReceiver(this, filter); } else { mContext.unregisterReceiver(this); @@ -395,13 +396,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) { int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); - final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType) - : (streamType == mStreamType); - if (mSeekBar != null && streamMatch && streamValue != -1) { - final boolean muted = mAudioManager.isStreamMute(mStreamType) - || streamValue == 0; - mUiHandler.postUpdateSlider(streamValue, mLastAudibleStreamVolume, muted); - } + updateVolumeSlider(streamType, streamValue); } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { if (mNotificationOrRing) { mRingerMode = mAudioManager.getRingerModeInternal(); @@ -409,10 +404,24 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba if (mAffectedByRingerMode) { updateSlider(); } + } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) { + int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + int streamVolume = mAudioManager.getStreamVolume(streamType); + updateVolumeSlider(streamType, streamVolume); } else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) { mZenMode = mNotificationManager.getZenMode(); updateSlider(); } } + + private void updateVolumeSlider(int streamType, int streamValue) { + final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType) + : (streamType == mStreamType); + if (mSeekBar != null && streamMatch && streamValue != -1) { + final boolean muted = mAudioManager.isStreamMute(mStreamType) + || streamValue == 0; + mUiHandler.postUpdateSlider(streamValue, mLastAudibleStreamVolume, muted); + } + } } } diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index cc4598d16de9..bae51d3b9d1f 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -766,6 +766,15 @@ public final class InputDevice implements Parcelable { } /** + * Sets the current pointer shape. + * @param pointerShape the id of the pointer icon. + * @hide + */ + public void setPointerShape(int pointerShape) { + InputManager.getInstance().setPointerIconShape(pointerShape); + } + + /** * Provides information about the range of values for a particular {@link MotionEvent} axis. * * @see InputDevice#getMotionRange(int) diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java index cf35ce534a97..88e9a952f778 100644 --- a/core/java/android/view/PointerIcon.java +++ b/core/java/android/view/PointerIcon.java @@ -48,6 +48,12 @@ public final class PointerIcon implements Parcelable { /** Style constant: Null icon. It has no bitmap. */ public static final int STYLE_NULL = 0; + /** Style constant: no icons are specified. If all views uses this, then falls back + * to the default style, but this is helpful to distinguish a view explicitly want + * to have the default icon. + */ + public static final int STYLE_NOT_SPECIFIED = 1; + /** Style constant: Arrow icon. (Default mouse pointer) */ public static final int STYLE_ARROW = 1000; @@ -60,12 +66,74 @@ public final class PointerIcon implements Parcelable { /** {@hide} Style constant: Spot anchor icon for touchpads. */ public static final int STYLE_SPOT_ANCHOR = 2002; + // Style constants for additional predefined icons for mice. + /** Style constant: context-menu. */ + public static final int STYLE_CONTEXT_MENU = 1001; + + /** Style constant: hand. */ + public static final int STYLE_HAND = 1002; + + /** Style constant: help. */ + public static final int STYLE_HELP = 1003; + + /** Style constant: wait. */ + public static final int STYLE_WAIT = 1004; + + /** Style constant: cell. */ + public static final int STYLE_CELL = 1006; + + /** Style constant: crosshair. */ + public static final int STYLE_CROSSHAIR = 1007; + + /** Style constant: text. */ + public static final int STYLE_TEXT = 1008; + + /** Style constant: vertical-text. */ + public static final int STYLE_VERTICAL_TEXT = 1009; + + /** Style constant: alias (indicating an alias of/shortcut to something is + * to be created. */ + public static final int STYLE_ALIAS = 1010; + + /** Style constant: copy. */ + public static final int STYLE_COPY = 1011; + + /** Style constant: no-drop. */ + public static final int STYLE_NO_DROP = 1012; + + /** Style constant: all-scroll. */ + public static final int STYLE_ALL_SCROLL = 1013; + + /** Style constant: horizontal double arrow mainly for resizing. */ + public static final int STYLE_HORIZONTAL_DOUBLE_ARROW = 1014; + + /** Style constant: vertical double arrow mainly for resizing. */ + public static final int STYLE_VERTICAL_DOUBLE_ARROW = 1015; + + /** Style constant: diagonal double arrow -- top-right to bottom-left. */ + public static final int STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; + + /** Style constant: diagonal double arrow -- top-left to bottom-right. */ + public static final int STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; + + /** Style constant: zoom-in. */ + public static final int STYLE_ZOOM_IN = 1018; + + /** Style constant: zoom-out. */ + public static final int STYLE_ZOOM_OUT = 1019; + + /** Style constant: grab. */ + public static final int STYLE_GRAB = 1020; + + /** Style constant: grabbing. */ + public static final int STYLE_GRABBING = 1021; + // OEM private styles should be defined starting at this range to avoid // conflicts with any system styles that may be defined in the future. private static final int STYLE_OEM_FIRST = 10000; - // The default pointer icon. - private static final int STYLE_DEFAULT = STYLE_ARROW; + /** {@hide} The default pointer icon. */ + public static final int STYLE_DEFAULT = STYLE_ARROW; private static final PointerIcon gNullIcon = new PointerIcon(STYLE_NULL); @@ -434,6 +502,49 @@ public final class PointerIcon implements Parcelable { return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch; case STYLE_SPOT_ANCHOR: return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor; + case STYLE_HAND: + return com.android.internal.R.styleable.Pointer_pointerIconHand; + case STYLE_CONTEXT_MENU: + return com.android.internal.R.styleable.Pointer_pointerIconContextMenu; + case STYLE_HELP: + return com.android.internal.R.styleable.Pointer_pointerIconHelp; + case STYLE_WAIT: + // falls back to the default icon because no animation support. + return com.android.internal.R.styleable.Pointer_pointerIconArrow; + case STYLE_CELL: + return com.android.internal.R.styleable.Pointer_pointerIconCell; + case STYLE_CROSSHAIR: + return com.android.internal.R.styleable.Pointer_pointerIconCrosshair; + case STYLE_TEXT: + return com.android.internal.R.styleable.Pointer_pointerIconText; + case STYLE_VERTICAL_TEXT: + return com.android.internal.R.styleable.Pointer_pointerIconVerticalText; + case STYLE_ALIAS: + return com.android.internal.R.styleable.Pointer_pointerIconAlias; + case STYLE_COPY: + return com.android.internal.R.styleable.Pointer_pointerIconCopy; + case STYLE_ALL_SCROLL: + return com.android.internal.R.styleable.Pointer_pointerIconAllScroll; + case STYLE_NO_DROP: + return com.android.internal.R.styleable.Pointer_pointerIconNodrop; + case STYLE_HORIZONTAL_DOUBLE_ARROW: + return com.android.internal.R.styleable.Pointer_pointerIconHorizontalDoubleArrow; + case STYLE_VERTICAL_DOUBLE_ARROW: + return com.android.internal.R.styleable.Pointer_pointerIconVerticalDoubleArrow; + case STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW: + return com.android.internal.R.styleable. + Pointer_pointerIconTopRightDiagonalDoubleArrow; + case STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW: + return com.android.internal.R.styleable. + Pointer_pointerIconTopLeftDiagonalDoubleArrow; + case STYLE_ZOOM_IN: + return com.android.internal.R.styleable.Pointer_pointerIconZoomIn; + case STYLE_ZOOM_OUT: + return com.android.internal.R.styleable.Pointer_pointerIconZoomOut; + case STYLE_GRAB: + return com.android.internal.R.styleable.Pointer_pointerIconGrab; + case STYLE_GRABBING: + return com.android.internal.R.styleable.Pointer_pointerIconGrabbing; default: return 0; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 7752ed890dc0..4bc211220d36 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -20995,6 +20995,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + /** @hide */ + public int getPointerShape(MotionEvent event, float x, float y) { + return PointerIcon.STYLE_NOT_SPECIFIED; + } + // // Properties // diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 742e924008a3..7c7ad9102695 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1715,6 +1715,36 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return false; } + /** @hide */ + @Override + public int getPointerShape(MotionEvent event, float x, float y) { + // Check what the child under the pointer says about the pointer. + final int childrenCount = mChildrenCount; + if (childrenCount != 0) { + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); + final View[] children = mChildren; + for (int i = childrenCount - 1; i >= 0; i--) { + final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; + final View child = (preorderedList == null) + ? children[childIndex] : preorderedList.get(childIndex); + PointF point = getLocalPoint(); + if (isTransformedTouchPointInView(x, y, child, point)) { + final int pointerShape = child.getPointerShape(event, point.x, point.y); + if (pointerShape != PointerIcon.STYLE_NOT_SPECIFIED) { + return pointerShape; + } + break; + } + } + } + + // The pointer is not a child or the child has no preferences, returning the default + // implementation. + return super.getPointerShape(event, x, y); + } + /** * {@inheritDoc} */ diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3d10289ac8e9..13b6a42e8d02 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -37,6 +37,7 @@ import android.graphics.Region; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; +import android.hardware.input.InputManager; import android.media.AudioManager; import android.os.Binder; import android.os.Build; @@ -324,6 +325,8 @@ public final class ViewRootImpl implements ViewParent, private long mFpsPrevTime = -1; private int mFpsNumFrames; + private int mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED; + /** * see {@link #playSoundEffect(int)} */ @@ -4168,6 +4171,30 @@ public final class ViewRootImpl implements ViewParent, private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; + if (event.getPointerCount() == 1 + && event.isFromSource(InputDevice.SOURCE_MOUSE)) { + if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER + || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) { + // Other apps or the window manager may change the icon shape outside of + // this app, therefore the icon shape has to be reset on enter/exit event. + mPointerIconShape = PointerIcon.STYLE_NOT_SPECIFIED; + } + + final float x = event.getX(); + final float y = event.getY(); + if (x >= 0 && x < mView.getWidth() && y >= 0 && y < mView.getHeight()) { + int pointerShape = mView.getPointerShape(event, x, y); + if (pointerShape == PointerIcon.STYLE_NOT_SPECIFIED) { + pointerShape = PointerIcon.STYLE_DEFAULT; + } + + if (mPointerIconShape != pointerShape) { + mPointerIconShape = pointerShape; + event.getDevice().setPointerShape(pointerShape); + } + } + } + mAttachInfo.mUnbufferedDispatchRequested = false; boolean handled = mView.dispatchPointerEvent(event); if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 071205248c91..1aeb9c938c0c 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -116,6 +116,7 @@ import android.view.HapticFeedbackConstants; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.PointerIcon; import android.view.View; import android.view.ViewParent; import android.view.ViewStructure; @@ -5905,6 +5906,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mLayout != null ? mLayout.getHeight() : 0; } + /** + * @hide + */ + @Override + public int getPointerShape(MotionEvent event, float x, float y) { + if (isTextSelectable() || isTextEditable()) { + return PointerIcon.STYLE_TEXT; + } + return super.getPointerShape(event, x, y); + } + @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 255e23a56a30..f462a478b717 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -1017,7 +1017,15 @@ public class ChooserActivity extends ResolverActivity { final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f) .setInterpolator(mInterpolator); mServiceTargetScale[i] = rs; - rs.startAnimation(); + } + + // Start the animations in a separate loop. + // The process of starting animations will result in + // binding views to set up initial values, and we must + // have ALL of the new RowScale objects created above before + // we get started. + for (int i = oldRCount; i < rcount; i++) { + mServiceTargetScale[i].startAnimation(); } } @@ -1097,17 +1105,19 @@ public class ChooserActivity extends ResolverActivity { for (int i = 0; i < mColumnCount; i++) { final View v = mChooserListAdapter.createView(row); + final int column = i; v.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - startSelected(holder.itemIndex, false, true); + startSelected(holder.itemIndices[column], false, true); } }); v.setOnLongClickListener(new OnLongClickListener() { @Override public boolean onLongClick(View v) { showAppDetails( - mChooserListAdapter.resolveInfoForPosition(holder.itemIndex, true)); + mChooserListAdapter.resolveInfoForPosition( + holder.itemIndices[column], true)); return true; } }); @@ -1165,8 +1175,8 @@ public class ChooserActivity extends ResolverActivity { final View v = holder.cells[i]; if (start + i <= end) { v.setVisibility(View.VISIBLE); - holder.itemIndex = start + i; - mChooserListAdapter.bindView(holder.itemIndex, v); + holder.itemIndices[i] = start + i; + mChooserListAdapter.bindView(holder.itemIndices[i], v); } else { v.setVisibility(View.GONE); } @@ -1197,11 +1207,12 @@ public class ChooserActivity extends ResolverActivity { final View[] cells; final ViewGroup row; int measuredRowHeight; - int itemIndex; + int[] itemIndices; public RowViewHolder(ViewGroup row, int cellCount) { this.row = row; this.cells = new View[cellCount]; + this.itemIndices = new int[cellCount]; } public void measure() { @@ -1389,7 +1400,7 @@ public class ChooserActivity extends ResolverActivity { final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView); int height = ((RowViewHolder) (v.getTag())).measuredRowHeight; - offset += (int) (height * mChooserRowAdapter.getRowScale(pos) * chooserTargetRows); + offset += (int) (height * mChooserRowAdapter.getRowScale(pos)); if (vt >= 0) { mCachedViewType = vt; diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h index 3bfd64526ad6..86f288d0d711 100644 --- a/core/jni/android_view_PointerIcon.h +++ b/core/jni/android_view_PointerIcon.h @@ -31,6 +31,27 @@ enum { POINTER_ICON_STYLE_CUSTOM = -1, POINTER_ICON_STYLE_NULL = 0, POINTER_ICON_STYLE_ARROW = 1000, + POINTER_ICON_STYLE_CONTEXT_MENU = 1001, + POINTER_ICON_STYLE_HAND = 1002, + POINTER_ICON_STYLE_HELP = 1003, + POINTER_ICON_STYLE_WAIT = 1004, + POINTER_ICON_STYLE_CELL = 1006, + POINTER_ICON_STYLE_CROSSHAIR = 1007, + POINTER_ICON_STYLE_TEXT = 1008, + POINTER_ICON_STYLE_VERTICAL_TEXT = 1009, + POINTER_ICON_STYLE_ALIAS = 1010, + POINTER_ICON_STYLE_COPY = 1011, + POINTER_ICON_STYLE_NO_DROP = 1012, + POINTER_ICON_STYLE_ALL_SCROLL = 1013, + POINTER_ICON_STYLE_HORIZONTAL_DOUBLE_ARROW = 1014, + POINTER_ICON_STYLE_VERTICAL_DOUBLE_ARROW = 1015, + POINTER_ICON_STYLE_TOP_RIGHT_DOUBLE_ARROW = 1016, + POINTER_ICON_STYLE_TOP_LEFT_DOUBLE_ARROW = 1017, + POINTER_ICON_STYLE_ZOOM_IN = 1018, + POINTER_ICON_STYLE_ZOOM_OUT = 1019, + POINTER_ICON_STYLE_GRAB = 1020, + POINTER_ICON_STYLE_GRABBING = 1021, + POINTER_ICON_STYLE_SPOT_HOVER = 2000, POINTER_ICON_STYLE_SPOT_TOUCH = 2001, POINTER_ICON_STYLE_SPOT_ANCHOR = 2002, diff --git a/core/res/res/drawable-mdpi/pointer_alias.png b/core/res/res/drawable-mdpi/pointer_alias.png Binary files differnew file mode 100644 index 000000000000..8f61a39dd209 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_alias.png diff --git a/core/res/res/drawable-mdpi/pointer_all_scroll.png b/core/res/res/drawable-mdpi/pointer_all_scroll.png Binary files differnew file mode 100644 index 000000000000..a897ef4799a4 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_all_scroll.png diff --git a/core/res/res/drawable-mdpi/pointer_cell.png b/core/res/res/drawable-mdpi/pointer_cell.png Binary files differnew file mode 100644 index 000000000000..b5213890f0ac --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_cell.png diff --git a/core/res/res/drawable-mdpi/pointer_context_menu.png b/core/res/res/drawable-mdpi/pointer_context_menu.png Binary files differnew file mode 100644 index 000000000000..4e1ba4ef3fbe --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_context_menu.png diff --git a/core/res/res/drawable-mdpi/pointer_copy.png b/core/res/res/drawable-mdpi/pointer_copy.png Binary files differnew file mode 100644 index 000000000000..7d41036138c4 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_copy.png diff --git a/core/res/res/drawable-mdpi/pointer_crosshair.png b/core/res/res/drawable-mdpi/pointer_crosshair.png Binary files differnew file mode 100644 index 000000000000..8a06c773de7e --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_crosshair.png diff --git a/core/res/res/drawable-mdpi/pointer_grab.png b/core/res/res/drawable-mdpi/pointer_grab.png Binary files differnew file mode 100644 index 000000000000..0e0ea2eff072 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_grab.png diff --git a/core/res/res/drawable-mdpi/pointer_grabbing.png b/core/res/res/drawable-mdpi/pointer_grabbing.png Binary files differnew file mode 100644 index 000000000000..9deb64cb108d --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_grabbing.png diff --git a/core/res/res/drawable-mdpi/pointer_hand.png b/core/res/res/drawable-mdpi/pointer_hand.png Binary files differnew file mode 100644 index 000000000000..c614d9ebce7f --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_hand.png diff --git a/core/res/res/drawable-mdpi/pointer_help.png b/core/res/res/drawable-mdpi/pointer_help.png Binary files differnew file mode 100644 index 000000000000..d54b2b109bbd --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_help.png diff --git a/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png Binary files differnew file mode 100644 index 000000000000..a2951a9ef077 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_horizontal_double_arrow.png diff --git a/core/res/res/drawable-mdpi/pointer_nodrop.png b/core/res/res/drawable-mdpi/pointer_nodrop.png Binary files differnew file mode 100644 index 000000000000..ad13c663d328 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_nodrop.png diff --git a/core/res/res/drawable-mdpi/pointer_text.png b/core/res/res/drawable-mdpi/pointer_text.png Binary files differnew file mode 100644 index 000000000000..34d1698c6ff1 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_text.png diff --git a/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png Binary files differnew file mode 100644 index 000000000000..b0cd92ced9a9 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_top_left_diagonal_double_arrow.png diff --git a/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png Binary files differnew file mode 100644 index 000000000000..f8d3527fcd0a --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_top_right_diagonal_double_arrow.png diff --git a/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png Binary files differnew file mode 100644 index 000000000000..48c93798714e --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_vertical_double_arrow.png diff --git a/core/res/res/drawable-mdpi/pointer_vertical_text.png b/core/res/res/drawable-mdpi/pointer_vertical_text.png Binary files differnew file mode 100644 index 000000000000..9fcbcba80867 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_vertical_text.png diff --git a/core/res/res/drawable-mdpi/pointer_zoom_in.png b/core/res/res/drawable-mdpi/pointer_zoom_in.png Binary files differnew file mode 100644 index 000000000000..17c4e664f41c --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_zoom_in.png diff --git a/core/res/res/drawable-mdpi/pointer_zoom_out.png b/core/res/res/drawable-mdpi/pointer_zoom_out.png Binary files differnew file mode 100644 index 000000000000..742f705960bb --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_zoom_out.png diff --git a/core/res/res/drawable-xhdpi/pointer_alias.png b/core/res/res/drawable-xhdpi/pointer_alias.png Binary files differnew file mode 100644 index 000000000000..fe0fd25d1446 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_alias.png diff --git a/core/res/res/drawable-xhdpi/pointer_all_scroll.png b/core/res/res/drawable-xhdpi/pointer_all_scroll.png Binary files differnew file mode 100644 index 000000000000..e2374ece1067 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_all_scroll.png diff --git a/core/res/res/drawable-xhdpi/pointer_cell.png b/core/res/res/drawable-xhdpi/pointer_cell.png Binary files differnew file mode 100644 index 000000000000..4ca09e3229a2 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_cell.png diff --git a/core/res/res/drawable-xhdpi/pointer_context_menu.png b/core/res/res/drawable-xhdpi/pointer_context_menu.png Binary files differnew file mode 100644 index 000000000000..05a59f8c9690 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_context_menu.png diff --git a/core/res/res/drawable-xhdpi/pointer_copy.png b/core/res/res/drawable-xhdpi/pointer_copy.png Binary files differnew file mode 100644 index 000000000000..f28a3e9e923d --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_copy.png diff --git a/core/res/res/drawable-xhdpi/pointer_crosshair.png b/core/res/res/drawable-xhdpi/pointer_crosshair.png Binary files differnew file mode 100644 index 000000000000..86c649c1a1bc --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_crosshair.png diff --git a/core/res/res/drawable-xhdpi/pointer_grab.png b/core/res/res/drawable-xhdpi/pointer_grab.png Binary files differnew file mode 100644 index 000000000000..b5c28ba15980 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_grab.png diff --git a/core/res/res/drawable-xhdpi/pointer_grabbing.png b/core/res/res/drawable-xhdpi/pointer_grabbing.png Binary files differnew file mode 100644 index 000000000000..6aba589f9235 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_grabbing.png diff --git a/core/res/res/drawable-xhdpi/pointer_hand.png b/core/res/res/drawable-xhdpi/pointer_hand.png Binary files differnew file mode 100644 index 000000000000..486cb24f0965 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_hand.png diff --git a/core/res/res/drawable-xhdpi/pointer_help.png b/core/res/res/drawable-xhdpi/pointer_help.png Binary files differnew file mode 100644 index 000000000000..98a66322887a --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_help.png diff --git a/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png Binary files differnew file mode 100644 index 000000000000..299ae1198b7f --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_horizontal_double_arrow.png diff --git a/core/res/res/drawable-xhdpi/pointer_nodrop.png b/core/res/res/drawable-xhdpi/pointer_nodrop.png Binary files differnew file mode 100644 index 000000000000..c56bfbb89a72 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_nodrop.png diff --git a/core/res/res/drawable-xhdpi/pointer_text.png b/core/res/res/drawable-xhdpi/pointer_text.png Binary files differnew file mode 100644 index 000000000000..0cebeae84cc9 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_text.png diff --git a/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png Binary files differnew file mode 100644 index 000000000000..5454a8ba5ccb --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_top_left_diagonal_double_arrow.png diff --git a/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png Binary files differnew file mode 100644 index 000000000000..a4268e4c3be4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_top_right_diagonal_double_arrow.png diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png Binary files differnew file mode 100644 index 000000000000..95ca954d5d71 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_vertical_double_arrow.png diff --git a/core/res/res/drawable-xhdpi/pointer_vertical_text.png b/core/res/res/drawable-xhdpi/pointer_vertical_text.png Binary files differnew file mode 100644 index 000000000000..a07d091395a2 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_vertical_text.png diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_in.png b/core/res/res/drawable-xhdpi/pointer_zoom_in.png Binary files differnew file mode 100644 index 000000000000..9c29fcb528c4 --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_zoom_in.png diff --git a/core/res/res/drawable-xhdpi/pointer_zoom_out.png b/core/res/res/drawable-xhdpi/pointer_zoom_out.png Binary files differnew file mode 100644 index 000000000000..710552b66fbc --- /dev/null +++ b/core/res/res/drawable-xhdpi/pointer_zoom_out.png diff --git a/core/res/res/drawable/pointer_alias_icon.xml b/core/res/res/drawable/pointer_alias_icon.xml new file mode 100644 index 000000000000..8ba930165b48 --- /dev/null +++ b/core/res/res/drawable/pointer_alias_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_alias" + android:hotSpotX="8dp" + android:hotSpotY="6dp" /> diff --git a/core/res/res/drawable/pointer_all_scroll_icon.xml b/core/res/res/drawable/pointer_all_scroll_icon.xml new file mode 100644 index 000000000000..e9469488be3a --- /dev/null +++ b/core/res/res/drawable/pointer_all_scroll_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_all_scroll" + android:hotSpotX="11dp" + android:hotSpotY="11dp" /> diff --git a/core/res/res/drawable/pointer_cell_icon.xml b/core/res/res/drawable/pointer_cell_icon.xml new file mode 100644 index 000000000000..da1e320c1d71 --- /dev/null +++ b/core/res/res/drawable/pointer_cell_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_cell" + android:hotSpotX="11dp" + android:hotSpotY="11dp" /> diff --git a/core/res/res/drawable/pointer_context_menu_icon.xml b/core/res/res/drawable/pointer_context_menu_icon.xml new file mode 100644 index 000000000000..330b627b5d02 --- /dev/null +++ b/core/res/res/drawable/pointer_context_menu_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_context_menu" + android:hotSpotX="4dp" + android:hotSpotY="4dp" /> diff --git a/core/res/res/drawable/pointer_copy_icon.xml b/core/res/res/drawable/pointer_copy_icon.xml new file mode 100644 index 000000000000..e299db5b23b5 --- /dev/null +++ b/core/res/res/drawable/pointer_copy_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_copy" + android:hotSpotX="9dp" + android:hotSpotY="9dp" /> diff --git a/core/res/res/drawable/pointer_crosshair_icon.xml b/core/res/res/drawable/pointer_crosshair_icon.xml new file mode 100644 index 000000000000..3b96a8ad3ace --- /dev/null +++ b/core/res/res/drawable/pointer_crosshair_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_crosshair" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_grab_icon.xml b/core/res/res/drawable/pointer_grab_icon.xml new file mode 100644 index 000000000000..d437b3a7bf8a --- /dev/null +++ b/core/res/res/drawable/pointer_grab_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_grab" + android:hotSpotX="8dp" + android:hotSpotY="5dp" /> diff --git a/core/res/res/drawable/pointer_grabbing_icon.xml b/core/res/res/drawable/pointer_grabbing_icon.xml new file mode 100644 index 000000000000..38f4c3a064fa --- /dev/null +++ b/core/res/res/drawable/pointer_grabbing_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_grabbing" + android:hotSpotX="9dp" + android:hotSpotY="9dp" /> diff --git a/core/res/res/drawable/pointer_hand_icon.xml b/core/res/res/drawable/pointer_hand_icon.xml new file mode 100644 index 000000000000..3d90b88d5dca --- /dev/null +++ b/core/res/res/drawable/pointer_hand_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_hand" + android:hotSpotX="9dp" + android:hotSpotY="4dp" /> diff --git a/core/res/res/drawable/pointer_help_icon.xml b/core/res/res/drawable/pointer_help_icon.xml new file mode 100644 index 000000000000..49ae554249f8 --- /dev/null +++ b/core/res/res/drawable/pointer_help_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_help" + android:hotSpotX="4dp" + android:hotSpotY="4dp" /> diff --git a/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml b/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml new file mode 100644 index 000000000000..5a5ad9e62657 --- /dev/null +++ b/core/res/res/drawable/pointer_horizontal_double_arrow_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_horizontal_double_arrow" + android:hotSpotX="11dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_nodrop_icon.xml b/core/res/res/drawable/pointer_nodrop_icon.xml new file mode 100644 index 000000000000..955b40f2d567 --- /dev/null +++ b/core/res/res/drawable/pointer_nodrop_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_nodrop" + android:hotSpotX="9dp" + android:hotSpotY="9dp" /> diff --git a/core/res/res/drawable/pointer_text_icon.xml b/core/res/res/drawable/pointer_text_icon.xml new file mode 100644 index 000000000000..d948c8916963 --- /dev/null +++ b/core/res/res/drawable/pointer_text_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_text" + android:hotSpotX="12dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml new file mode 100644 index 000000000000..de5efe25dae2 --- /dev/null +++ b/core/res/res/drawable/pointer_top_left_diagonal_double_arrow_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_top_left_diagonal_double_arrow" + android:hotSpotX="11dp" + android:hotSpotY="11dp" /> diff --git a/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml new file mode 100644 index 000000000000..e87b52686426 --- /dev/null +++ b/core/res/res/drawable/pointer_top_right_diagonal_double_arrow_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_top_right_diagonal_double_arrow" + android:hotSpotX="12dp" + android:hotSpotY="11dp" /> diff --git a/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml b/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml new file mode 100644 index 000000000000..5759079a47fa --- /dev/null +++ b/core/res/res/drawable/pointer_vertical_double_arrow_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_vertical_double_arrow" + android:hotSpotX="11dp" + android:hotSpotY="12dp" /> diff --git a/core/res/res/drawable/pointer_vertical_text_icon.xml b/core/res/res/drawable/pointer_vertical_text_icon.xml new file mode 100644 index 000000000000..3b48dc89fb56 --- /dev/null +++ b/core/res/res/drawable/pointer_vertical_text_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_vertical_text" + android:hotSpotX="12dp" + android:hotSpotY="11dp" /> diff --git a/core/res/res/drawable/pointer_zoom_in_icon.xml b/core/res/res/drawable/pointer_zoom_in_icon.xml new file mode 100644 index 000000000000..e2dcb49c1600 --- /dev/null +++ b/core/res/res/drawable/pointer_zoom_in_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_zoom_in" + android:hotSpotX="10dp" + android:hotSpotY="10dp" /> diff --git a/core/res/res/drawable/pointer_zoom_out_icon.xml b/core/res/res/drawable/pointer_zoom_out_icon.xml new file mode 100644 index 000000000000..b805df3d18c1 --- /dev/null +++ b/core/res/res/drawable/pointer_zoom_out_icon.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android" + android:bitmap="@drawable/pointer_zoom_out" + android:hotSpotX="10dp" + android:hotSpotY="10dp" /> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index a6eb68b73baa..b818fe0a3e1a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -7605,6 +7605,44 @@ i <attr name="pointerIconSpotTouch" format="reference" /> <!-- Reference to a pointer icon drawable with STYLE_SPOT_ANCHOR --> <attr name="pointerIconSpotAnchor" format="reference" /> + <!-- Reference to a pointer drawable with STYLE_CONTEXT_MENU --> + <attr name="pointerIconContextMenu" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_HAND --> + <attr name="pointerIconHand" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_HELP --> + <attr name="pointerIconHelp" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_CELL --> + <attr name="pointerIconCell" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_CROSSHAIR --> + <attr name="pointerIconCrosshair" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_TEXT --> + <attr name="pointerIconText" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_VERTICAL_TEXT --> + <attr name="pointerIconVerticalText" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_ALIAS --> + <attr name="pointerIconAlias" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_COPY --> + <attr name="pointerIconCopy" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_NODROP --> + <attr name="pointerIconNodrop" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_ALL_SCROLL --> + <attr name="pointerIconAllScroll" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_HORIZONTAL_DOUBLE_ARROW --> + <attr name="pointerIconHorizontalDoubleArrow" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_VERTICAL_DOUBLE_ARROW --> + <attr name="pointerIconVerticalDoubleArrow" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW --> + <attr name="pointerIconTopRightDiagonalDoubleArrow" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW --> + <attr name="pointerIconTopLeftDiagonalDoubleArrow" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_ZOOM_IN --> + <attr name="pointerIconZoomIn" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_ZOOM_OUT --> + <attr name="pointerIconZoomOut" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_GRAB --> + <attr name="pointerIconGrab" format="reference"/> + <!-- Reference to a pointer drawable with STYLE_GRABBING --> + <attr name="pointerIconGrabbing" format="reference"/> </declare-styleable> <declare-styleable name="PointerIcon"> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 11c4cc01ee82..b831df827a8c 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1336,6 +1336,33 @@ please see styles_device_defaults.xml. <item name="pointerIconSpotHover">@drawable/pointer_spot_hover_icon</item> <item name="pointerIconSpotTouch">@drawable/pointer_spot_touch_icon</item> <item name="pointerIconSpotAnchor">@drawable/pointer_spot_anchor_icon</item> + <item name="pointerIconHand">@drawable/pointer_hand_icon</item> + <item name="pointerIconContextMenu">@drawable/pointer_context_menu_icon</item> + <item name="pointerIconHelp">@drawable/pointer_help_icon</item> + <item name="pointerIconCell">@drawable/pointer_cell_icon</item> + <item name="pointerIconCrosshair">@drawable/pointer_crosshair_icon</item> + <item name="pointerIconText">@drawable/pointer_text_icon</item> + <item name="pointerIconVerticalText">@drawable/pointer_vertical_text_icon</item> + <item name="pointerIconAlias">@drawable/pointer_alias_icon</item> + <item name="pointerIconCopy">@drawable/pointer_copy_icon</item> + <item name="pointerIconAllScroll">@drawable/pointer_all_scroll_icon</item> + <item name="pointerIconNodrop">@drawable/pointer_nodrop_icon</item> + <item name="pointerIconHorizontalDoubleArrow"> + @drawable/pointer_horizontal_double_arrow_icon + </item> + <item name="pointerIconVerticalDoubleArrow"> + @drawable/pointer_vertical_double_arrow_icon + </item> + <item name="pointerIconTopRightDiagonalDoubleArrow"> + @drawable/pointer_top_right_diagonal_double_arrow_icon + </item> + <item name="pointerIconTopLeftDiagonalDoubleArrow"> + @drawable/pointer_top_left_diagonal_double_arrow_icon + </item> + <item name="pointerIconZoomIn">@drawable/pointer_zoom_in_icon</item> + <item name="pointerIconZoomOut">@drawable/pointer_zoom_out_icon</item> + <item name="pointerIconGrab">@drawable/pointer_grab_icon</item> + <item name="pointerIconGrabbing">@drawable/pointer_grabbing_icon</item> </style> <!-- Wifi dialog styles --> diff --git a/docs/html/sdk/installing/index.jd b/docs/html/sdk/installing/index.jd index dc258db963c1..0375a2f37a99 100644 --- a/docs/html/sdk/installing/index.jd +++ b/docs/html/sdk/installing/index.jd @@ -42,9 +42,9 @@ install</a> instructions.</p> JDK 6 or higher (the JRE alone is not sufficient)—JDK 7 is required when developing for Android 5.0 and higher. To check if you have JDK installed (and which version), open a terminal and type <code>javac -version</code>. -If the JDK is not available or the version is lower than 6, -<a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html" class="external-link" ->go download JDK</a>.</p> +If the JDK is not available or the version is lower than version 6, download the +<a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html" class="external-link" +>Java SE Development Kit 7</a>.</p> <div class="procedure-box"> diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 8011b5945ee2..6f548bcabedc 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -2,6 +2,8 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +HWUI_NEW_OPS := false + hwui_src_files := \ font/CacheTexture.cpp \ font/Font.cpp \ @@ -85,6 +87,17 @@ hwui_cflags := \ -Wall -Wno-unused-parameter -Wunreachable-code \ -ffast-math -O3 -Werror + +ifeq (true, $(HWUI_NEW_OPS)) + hwui_src_files += \ + BakedOpRenderer.cpp \ + OpReorderer.cpp \ + RecordingCanvas.cpp + + hwui_cflags += -DHWUI_NEW_OPS + +endif + ifndef HWUI_COMPILE_SYMBOLS hwui_cflags += -fvisibility=hidden endif @@ -172,6 +185,13 @@ LOCAL_SRC_FILES += \ unit_tests/LinearAllocatorTests.cpp \ unit_tests/StringUtilsTests.cpp +ifeq (true, $(HWUI_NEW_OPS)) + LOCAL_SRC_FILES += \ + unit_tests/BakedOpStateTests.cpp \ + unit_tests/RecordingCanvasTests.cpp \ + unit_tests/OpReordererTests.cpp +endif + include $(BUILD_NATIVE_TEST) # ------------------------ diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp new file mode 100644 index 000000000000..4d9f9b479343 --- /dev/null +++ b/libs/hwui/BakedOpRenderer.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015 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 "BakedOpRenderer.h" + +#include "Caches.h" +#include "Glop.h" +#include "GlopBuilder.h" +#include "renderstate/RenderState.h" +#include "utils/GLUtils.h" + +namespace android { +namespace uirenderer { + +Texture* BakedOpRenderer::Info::getTexture(const SkBitmap* bitmap) { + Texture* texture = renderState.assetAtlas().getEntryTexture(bitmap); + if (!texture) { + return caches.textureCache.get(bitmap); + } + return texture; +} + +void BakedOpRenderer::Info::renderGlop(const BakedOpState& state, const Glop& glop) { + bool useScissor = state.computedState.clipSideFlags != OpClipSideFlags::None; + renderState.scissor().setEnabled(useScissor); + if (useScissor) { + const Rect& clip = state.computedState.clipRect; + renderState.scissor().set(clip.left, viewportHeight - clip.bottom, + clip.getWidth(), clip.getHeight()); + } + renderState.render(glop, orthoMatrix); + didDraw = true; +} + +void BakedOpRenderer::startFrame(Info& info) { + info.renderState.setViewport(info.viewportWidth, info.viewportHeight); + info.renderState.blend().syncEnabled(); + Caches::getInstance().clearGarbage(); + + if (!info.opaque) { + // TODO: partial invalidate! + info.renderState.scissor().setEnabled(false); + glClear(GL_COLOR_BUFFER_BIT); + info.didDraw = true; + } +} +void BakedOpRenderer::endFrame(Info& info) { + info.caches.pathCache.trim(); + info.caches.tessellationCache.trim(); + +#if DEBUG_OPENGL + GLUtils::dumpGLErrors(); +#endif + +#if DEBUG_MEMORY_USAGE + info.caches.dumpMemoryUsage(); +#else + if (Properties::debugLevel & kDebugMemory) { + info.caches.dumpMemoryUsage(); + } +#endif +} + +void BakedOpRenderer::onRenderNodeOp(Info*, const RenderNodeOp&, const BakedOpState&) { + LOG_ALWAYS_FATAL("unsupported operation"); +} + +void BakedOpRenderer::onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) { + info->caches.textureState().activateTexture(0); // TODO: should this be automatic, and/or elsewhere? + Texture* texture = info->getTexture(op.bitmap); + if (!texture) return; + const AutoTexture autoCleanup(texture); + + const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType) + ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; + Glop glop; + GlopBuilder(info->renderState, info->caches, &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUnitQuad(texture->uvMapper) + .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height)) + .build(); + info->renderGlop(state, glop); +} + +void BakedOpRenderer::onRectOp(Info* info, const RectOp& op, const BakedOpState& state) { + Glop glop; + GlopBuilder(info->renderState, info->caches, &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshUnitQuad() + .setFillPaint(*op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRect(op.unmappedBounds) + .build(); + info->renderGlop(state, glop); +} + +void BakedOpRenderer::onSimpleRectsOp(Info* info, const SimpleRectsOp& op, const BakedOpState& state) { + Glop glop; + GlopBuilder(info->renderState, info->caches, &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4) + .setFillPaint(*op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewOffsetRect(0, 0, op.unmappedBounds) + .build(); + info->renderGlop(state, glop); +} + + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h new file mode 100644 index 000000000000..b8b4426412e4 --- /dev/null +++ b/libs/hwui/BakedOpRenderer.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2015 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_BAKED_OP_RENDERER_H +#define ANDROID_HWUI_BAKED_OP_RENDERER_H + +#include "BakedOpState.h" +#include "Matrix.h" + +namespace android { +namespace uirenderer { + +class Caches; +struct Glop; +class RenderState; + +class BakedOpRenderer { +public: + class Info { + public: + Info(Caches& caches, RenderState& renderState, int viewportWidth, int viewportHeight, bool opaque) + : renderState(renderState) + , caches(caches) + , opaque(opaque) + , viewportWidth(viewportWidth) + , viewportHeight(viewportHeight) { + orthoMatrix.loadOrtho(viewportWidth, viewportHeight); + } + + Texture* getTexture(const SkBitmap* bitmap); + + void renderGlop(const BakedOpState& state, const Glop& glop); + RenderState& renderState; + Caches& caches; + + bool didDraw = false; + bool opaque; + + + // where should these live? layer state object? + int viewportWidth; + int viewportHeight; + Matrix4 orthoMatrix; + }; + + static void startFrame(Info& info); + static void endFrame(Info& info); + + /** + * Declare all "onBitmapOp(...)" style function for every op type. + * + * These functions will perform the actual rendering of the individual operations in OpenGL, + * given the transform/clip and other state built into the BakedOpState object passed in. + */ + #define BAKED_OP_RENDERER_METHOD(Type) static void on##Type(Info* info, const Type& op, const BakedOpState& state); + MAP_OPS(BAKED_OP_RENDERER_METHOD); +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_BAKED_OP_RENDERER_H diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h new file mode 100644 index 000000000000..e2201ca06a4b --- /dev/null +++ b/libs/hwui/BakedOpState.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2015 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_BAKED_OP_STATE_H +#define ANDROID_HWUI_BAKED_OP_STATE_H + +#include "Matrix.h" +#include "RecordedOp.h" +#include "Rect.h" +#include "Snapshot.h" + +namespace android { +namespace uirenderer { + +namespace OpClipSideFlags { + enum { + None = 0x0, + Left = 0x1, + Top = 0x2, + Right = 0x4, + Bottom = 0x8, + Full = 0xF, + // ConservativeFull = 0x1F needed? + }; +} + +/** + * Holds the resolved clip, transform, and bounds of a recordedOp, when replayed with a snapshot + */ +class ResolvedRenderState { +public: + // TODO: remove the mapRects/matrix multiply when snapshot & recorded transforms are translates + ResolvedRenderState(const Snapshot& snapshot, const RecordedOp& recordedOp) { + /* TODO: benchmark a fast path for translate-only matrices, such as: + if (CC_LIKELY(snapshot.transform->getType() == Matrix4::kTypeTranslate + && recordedOp.localMatrix.getType() == Matrix4::kTypeTranslate)) { + float translateX = snapshot.transform->getTranslateX() + recordedOp.localMatrix.getTranslateX(); + float translateY = snapshot.transform->getTranslateY() + recordedOp.localMatrix.getTranslateY(); + transform.loadTranslate(translateX, translateY, 0); + + // resolvedClipRect = intersect(parentMatrix * localClip, parentClip) + clipRect = recordedOp.localClipRect; + clipRect.translate(translateX, translateY); + clipRect.doIntersect(snapshot.getClipRect()); + clipRect.snapToPixelBoundaries(); + + // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect) + clippedBounds = recordedOp.unmappedBounds; + clippedBounds.translate(translateX, translateY); + } ... */ + + // resolvedMatrix = parentMatrix * localMatrix + transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix); + + // resolvedClipRect = intersect(parentMatrix * localClip, parentClip) + clipRect = recordedOp.localClipRect; + snapshot.transform->mapRect(clipRect); + clipRect.doIntersect(snapshot.getClipRect()); + clipRect.snapToPixelBoundaries(); + + // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect) + clippedBounds = recordedOp.unmappedBounds; + transform.mapRect(clippedBounds); + + if (clipRect.left > clippedBounds.left) clipSideFlags |= OpClipSideFlags::Left; + if (clipRect.top > clippedBounds.top) clipSideFlags |= OpClipSideFlags::Top; + if (clipRect.right < clippedBounds.right) clipSideFlags |= OpClipSideFlags::Right; + if (clipRect.bottom < clippedBounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom; + clippedBounds.doIntersect(clipRect); + + /** + * TODO: once we support complex clips, we may want to reject to avoid that work where + * possible. Should we: + * 1 - quickreject based on clippedBounds, quick early (duplicating logic in resolvedOp) + * 2 - merge stuff into tryConstruct factory method, so it can handle quickRejection + * and early return null in one place. + */ + } + Matrix4 transform; + Rect clipRect; + int clipSideFlags = 0; + Rect clippedBounds; +}; + +/** + * Self-contained op wrapper, containing all resolved state required to draw the op. + * + * Stashed pointers within all point to longer lived objects, with no ownership implied. + */ +class BakedOpState { +public: + static BakedOpState* tryConstruct(LinearAllocator& allocator, + const Snapshot& snapshot, const RecordedOp& recordedOp) { + BakedOpState* bakedOp = new (allocator) BakedOpState( + snapshot, recordedOp); + if (bakedOp->computedState.clippedBounds.isEmpty()) { + // bounds are empty, so op is rejected + allocator.rewindIfLastAlloc(bakedOp); + return nullptr; + } + return bakedOp; + } + + static void* operator new(size_t size, LinearAllocator& allocator) { + return allocator.alloc(size); + } + + // computed state: + const ResolvedRenderState computedState; + + // simple state (straight pointer/value storage): + const float alpha; + const RoundRectClipState* roundRectClipState; + const ProjectionPathMask* projectionPathMask; + const RecordedOp* op; + +private: + BakedOpState(const Snapshot& snapshot, const RecordedOp& recordedOp) + : computedState(snapshot, recordedOp) + , alpha(snapshot.alpha) + , roundRectClipState(snapshot.roundRectClipState) + , projectionPathMask(snapshot.projectionPathMask) + , op(&recordedOp) {} +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_BAKED_OP_STATE_H diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp index 0c29a9e928a2..a1825c5bc4c1 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -528,7 +528,7 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { int insertBatchIndex = mBatches.size(); if (!mBatches.empty()) { if (state->mBounds.isEmpty()) { - // don't know the bounds for op, so add to last batch and start from scratch on next op + // don't know the bounds for op, so create new batch and start from scratch on next op DrawBatch* b = new DrawBatch(deferInfo); b->add(op, state, deferInfo.opaqueOverBounds); mBatches.push_back(b); diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h index 7873fbdd342a..2d5979f2f1a7 100644 --- a/libs/hwui/DeferredDisplayList.h +++ b/libs/hwui/DeferredDisplayList.h @@ -49,7 +49,7 @@ typedef const void* mergeid_t; class DeferredDisplayState { public: - /** static void* operator new(size_t size); PURPOSELY OMITTED **/ + static void* operator new(size_t size) = delete; static void* operator new(size_t size, LinearAllocator& allocator) { return allocator.alloc(size); } @@ -61,7 +61,6 @@ public: bool mClipValid; Rect mClip; int mClipSideFlags; // specifies which sides of the bounds are clipped, unclipped if cleared - bool mClipped; mat4 mMatrix; float mAlpha; const RoundRectClipState* mRoundRectClipState; diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index ee51da2fb116..2337299da145 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -21,7 +21,13 @@ #include "Debug.h" #include "DisplayList.h" +#include "RenderNode.h" + +#if HWUI_NEW_OPS +#include "RecordedOp.h" +#else #include "DisplayListOp.h" +#endif namespace android { namespace uirenderer { @@ -61,8 +67,13 @@ void DisplayListData::cleanupResources() { regions.clear(); } +#if HWUI_NEW_OPS +size_t DisplayListData::addChild(RenderNodeOp* op) { + mReferenceHolders.push_back(op->renderNode); +#else size_t DisplayListData::addChild(DrawRenderNodeOp* op) { - mReferenceHolders.push_back(op->renderNode()); + mReferenceHolders.push_back(op->renderNode); +#endif size_t index = mChildren.size(); mChildren.push_back(op); return index; diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 0bdb8169ea06..8ba9ac357414 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -55,11 +55,12 @@ class OpenGLRenderer; class Rect; class Layer; -class ClipRectOp; -class SaveLayerOp; -class SaveOp; -class RestoreToCountOp; +#if HWUI_NEW_OPS +struct RecordedOp; +struct RenderNodeOp; +#else class DrawRenderNodeOp; +#endif /** * Holds data used in the playback a tree of DisplayLists. @@ -107,6 +108,7 @@ struct ReplayStateStruct : public PlaybackStateStruct { */ class DisplayListData { friend class DisplayListCanvas; + friend class RecordingCanvas; public: struct Chunk { // range of included ops in DLD::displayListOps @@ -139,11 +141,21 @@ public: Vector<Functor*> functors; const std::vector<Chunk>& getChunks() const { - return chunks; + return chunks; } +#if HWUI_NEW_OPS + const std::vector<RecordedOp*>& getOps() const { + return ops; + } +#endif +#if HWUI_NEW_OPS + size_t addChild(RenderNodeOp* childOp); + const std::vector<RenderNodeOp*>& children() { return mChildren; } +#else size_t addChild(DrawRenderNodeOp* childOp); const std::vector<DrawRenderNodeOp*>& children() { return mChildren; } +#endif void ref(VirtualLightRefBase* prop) { mReferenceHolders.push_back(prop); @@ -157,10 +169,18 @@ public: } private: +#if HWUI_NEW_OPS + std::vector<RecordedOp*> ops; +#endif + std::vector< sp<VirtualLightRefBase> > mReferenceHolders; +#if HWUI_NEW_OPS + std::vector<RenderNodeOp*> mChildren; +#else // list of children display lists for quick, non-drawing traversal std::vector<DrawRenderNodeOp*> mChildren; +#endif std::vector<Chunk> chunks; diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp index 77bde863af51..8d0ab0322b5b 100644 --- a/libs/hwui/DisplayListCanvas.cpp +++ b/libs/hwui/DisplayListCanvas.cpp @@ -558,16 +558,18 @@ size_t DisplayListCanvas::addDrawOp(DrawOp* op) { size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) { int opIndex = addDrawOp(op); +#if !HWUI_NEW_OPS int childIndex = mDisplayListData->addChild(op); // update the chunk's child indices DisplayListData::Chunk& chunk = mDisplayListData->chunks.back(); chunk.endChildIndex = childIndex + 1; - if (op->renderNode()->stagingProperties().isProjectionReceiver()) { + if (op->renderNode->stagingProperties().isProjectionReceiver()) { // use staging property, since recording on UI thread mDisplayListData->projectionReceiveIndex = opIndex; } +#endif return opIndex; } diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index ddfc533f9d77..1f6282d68688 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -1397,25 +1397,26 @@ private: class DrawRenderNodeOp : public DrawBoundedOp { friend class RenderNode; // grant RenderNode access to info of child friend class DisplayListData; // grant DisplayListData access to info of child + friend class DisplayListCanvas; public: DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent, bool clipIsSimple) : DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr) - , mRenderNode(renderNode) + , renderNode(renderNode) , mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple()) , mTransformFromParent(transformFromParent) , mSkipInOrderDraw(false) {} virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level, bool useQuickReject) override { - if (mRenderNode->isRenderable() && !mSkipInOrderDraw) { - mRenderNode->defer(deferStruct, level + 1); + if (renderNode->isRenderable() && !mSkipInOrderDraw) { + renderNode->defer(deferStruct, level + 1); } } virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level, bool useQuickReject) override { - if (mRenderNode->isRenderable() && !mSkipInOrderDraw) { - mRenderNode->replay(replayStruct, level + 1); + if (renderNode->isRenderable() && !mSkipInOrderDraw) { + renderNode->replay(replayStruct, level + 1); } } @@ -1424,18 +1425,16 @@ public: } virtual void output(int level, uint32_t logFlags) const override { - OP_LOG("Draw RenderNode %p %s", mRenderNode, mRenderNode->getName()); - if (mRenderNode && (logFlags & kOpLogFlag_Recurse)) { - mRenderNode->output(level + 1); + OP_LOG("Draw RenderNode %p %s", renderNode, renderNode->getName()); + if (renderNode && (logFlags & kOpLogFlag_Recurse)) { + renderNode->output(level + 1); } } virtual const char* name() override { return "DrawRenderNode"; } - RenderNode* renderNode() { return mRenderNode; } - private: - RenderNode* mRenderNode; + RenderNode* renderNode; /** * This RenderNode was drawn into a DisplayList with the canvas in a state that will likely diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp index fa166ae5ca5a..d2da8513ff56 100644 --- a/libs/hwui/GlopBuilder.cpp +++ b/libs/hwui/GlopBuilder.cpp @@ -461,12 +461,12 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) { // Transform //////////////////////////////////////////////////////////////////////////////// -void GlopBuilder::setTransform(const Matrix4& canvas, - const int transformFlags) { +GlopBuilder& GlopBuilder::setTransform(const Matrix4& canvas, const int transformFlags) { TRIGGER_STAGE(kTransformStage); mOutGlop->transform.canvas = canvas; mOutGlop->transform.transformFlags = transformFlags; + return *this; } //////////////////////////////////////////////////////////////////////////////// @@ -632,5 +632,42 @@ void GlopBuilder::build() { mOutGlop->transform.meshTransform().mapRect(mOutGlop->bounds); } +void GlopBuilder::dump(const Glop& glop) { + ALOGD("Glop Mesh"); + const Glop::Mesh& mesh = glop.mesh; + ALOGD(" primitive mode: %d", mesh.primitiveMode); + ALOGD(" indices: buffer obj %x, indices %p", mesh.indices.bufferObject, mesh.indices.indices); + + const Glop::Mesh::Vertices& vertices = glop.mesh.vertices; + ALOGD(" vertices: buffer obj %x, flags %x, pos %p, tex %p, clr %p, stride %d", + vertices.bufferObject, vertices.attribFlags, + vertices.position, vertices.texCoord, vertices.color, vertices.stride); + ALOGD(" element count: %d", mesh.elementCount); + + ALOGD("Glop Fill"); + const Glop::Fill& fill = glop.fill; + ALOGD(" program %p", fill.program); + if (fill.texture.texture) { + ALOGD(" texture %p, target %d, filter %d, clamp %d", + fill.texture.texture, fill.texture.target, fill.texture.filter, fill.texture.clamp); + if (fill.texture.textureTransform) { + fill.texture.textureTransform->dump("texture transform"); + } + } + ALOGD_IF(fill.colorEnabled, " color (argb) %.2f %.2f %.2f %.2f", + fill.color.a, fill.color.r, fill.color.g, fill.color.b); + ALOGD_IF(fill.filterMode != ProgramDescription::ColorFilterMode::None, + " filterMode %d", (int)fill.filterMode); + ALOGD_IF(fill.skiaShaderData.skiaShaderType, " shader type %d", + fill.skiaShaderData.skiaShaderType); + + ALOGD("Glop transform"); + glop.transform.modelView.dump("model view"); + glop.transform.canvas.dump("canvas"); + + ALOGD("Glop blend %d %d", glop.blend.src, glop.blend.dst); + ALOGD("Glop bounds " RECT_STRING, RECT_ARGS(glop.bounds)); +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h index 8d05570dd206..6f5802eedefd 100644 --- a/libs/hwui/GlopBuilder.h +++ b/libs/hwui/GlopBuilder.h @@ -71,9 +71,9 @@ public: GlopBuilder& setFillTextureLayer(Layer& layer, float alpha); GlopBuilder& setTransform(const Snapshot& snapshot, const int transformFlags) { - setTransform(*snapshot.transform, transformFlags); - return *this; + return setTransform(*snapshot.transform, transformFlags); } + GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags); GlopBuilder& setModelViewMapUnitToRect(const Rect destination); GlopBuilder& setModelViewMapUnitToRectSnap(const Rect destination); @@ -98,11 +98,12 @@ public: GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState); void build(); + + static void dump(const Glop& glop); private: void setFill(int color, float alphaScale, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage, const SkShader* shader, const SkColorFilter* colorFilter); - void setTransform(const Matrix4& canvas, const int transformFlags); enum StageFlags { kInitialStage = 0, diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h index ed517ac26295..c017638db895 100644 --- a/libs/hwui/Matrix.h +++ b/libs/hwui/Matrix.h @@ -126,6 +126,9 @@ public: void loadMultiply(const Matrix4& u, const Matrix4& v); void loadOrtho(float left, float right, float bottom, float top, float near, float far); + void loadOrtho(int width, int height) { + loadOrtho(0, width, height, 0, -1, 1); + } uint8_t getType() const; diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp new file mode 100644 index 000000000000..1d8be2b47d43 --- /dev/null +++ b/libs/hwui/OpReorderer.cpp @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2015 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 "OpReorderer.h" + +#include "utils/PaintUtils.h" +#include "RenderNode.h" + +#include "SkCanvas.h" +#include "utils/Trace.h" + +namespace android { +namespace uirenderer { + +class BatchBase { + +public: + BatchBase(batchid_t batchId, BakedOpState* op, bool merging) + : mBatchId(batchId) + , mMerging(merging) { + mBounds = op->computedState.clippedBounds; + mOps.push_back(op); + } + + bool intersects(const Rect& rect) const { + if (!rect.intersects(mBounds)) return false; + + for (const BakedOpState* op : mOps) { + if (rect.intersects(op->computedState.clippedBounds)) { + return true; + } + } + return false; + } + + batchid_t getBatchId() const { return mBatchId; } + bool isMerging() const { return mMerging; } + + const std::vector<BakedOpState*>& getOps() const { return mOps; } + + void dump() const { + ALOGD(" Batch %p, merging %d, bounds " RECT_STRING, this, mMerging, RECT_ARGS(mBounds)); + } +protected: + batchid_t mBatchId; + Rect mBounds; + std::vector<BakedOpState*> mOps; + bool mMerging; +}; + +class OpBatch : public BatchBase { +public: + static void* operator new(size_t size, LinearAllocator& allocator) { + return allocator.alloc(size); + } + + OpBatch(batchid_t batchId, BakedOpState* op) + : BatchBase(batchId, op, false) { + } + + void batchOp(BakedOpState* op) { + mBounds.unionWith(op->computedState.clippedBounds); + mOps.push_back(op); + } +}; + +class MergingOpBatch : public BatchBase { +public: + static void* operator new(size_t size, LinearAllocator& allocator) { + return allocator.alloc(size); + } + + MergingOpBatch(batchid_t batchId, BakedOpState* op) + : BatchBase(batchId, op, true) { + } + + /* + * 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; + } + + static bool paintIsDefault(const SkPaint& paint) { + return paint.getAlpha() == 255 + && paint.getColorFilter() == nullptr + && paint.getShader() == nullptr; + } + + static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) { + return a.getAlpha() == b.getAlpha() + && a.getColorFilter() == b.getColorFilter() + && a.getShader() == b.getShader(); + } + + /* + * 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 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(BakedOpState* op) const { + bool isTextBatch = getBatchId() == OpBatchType::Text + || getBatchId() == OpBatchType::ColorText; + + // Overlapping other operations is only allowed for text without shadow. For other ops, + // multiDraw isn't guaranteed to overdraw correctly + if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) { + if (intersects(op->computedState.clippedBounds)) return false; + } + + const BakedOpState* lhs = op; + const BakedOpState* rhs = mOps[0]; + + if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false; + + // Identical round rect clip state means both ops will clip in the same way, or not at all. + // As the state objects are const, we can compare their pointers to determine mergeability + if (lhs->roundRectClipState != rhs->roundRectClipState) return false; + if (lhs->projectionPathMask != rhs->projectionPathMask) 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 = op->computedState.clipSideFlags; + if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) { + const Rect& opBounds = op->computedState.clippedBounds; + float boundsDelta = mBounds.left - opBounds.left; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false; + boundsDelta = mBounds.top - opBounds.top; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false; + + // right and bottom delta calculation reversed to account for direction + boundsDelta = opBounds.right - mBounds.right; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false; + boundsDelta = opBounds.bottom - mBounds.bottom; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false; + } + + const SkPaint* newPaint = op->op->paint; + const SkPaint* oldPaint = mOps[0]->op->paint; + + if (newPaint == oldPaint) { + // if paints are equal, then modifiers + paint attribs don't need to be compared + return true; + } else if (newPaint && !oldPaint) { + return paintIsDefault(*newPaint); + } else if (!newPaint && oldPaint) { + return paintIsDefault(*oldPaint); + } + return paintsAreEquivalent(*newPaint, *oldPaint); + } + + void mergeOp(BakedOpState* op) { + mBounds.unionWith(op->computedState.clippedBounds); + mOps.push_back(op); + + const int newClipSideFlags = op->computedState.clipSideFlags; + mClipSideFlags |= newClipSideFlags; + + const Rect& opClip = op->computedState.clipRect; + if (newClipSideFlags & OpClipSideFlags::Left) mClipRect.left = opClip.left; + if (newClipSideFlags & OpClipSideFlags::Top) mClipRect.top = opClip.top; + if (newClipSideFlags & OpClipSideFlags::Right) mClipRect.right = opClip.right; + if (newClipSideFlags & OpClipSideFlags::Bottom) mClipRect.bottom = opClip.bottom; + } + +private: + int mClipSideFlags = 0; + Rect mClipRect; +}; + +class NullClient: public CanvasStateClient { + void onViewportInitialized() override {} + void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} + GLuint getTargetFbo() const override { return 0; } +}; +static NullClient sNullClient; + +OpReorderer::OpReorderer() + : mCanvasState(sNullClient) { +} + +void OpReorderer::defer(int viewportWidth, int viewportHeight, + const std::vector< sp<RenderNode> >& nodes) { + mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, + 0, 0, viewportWidth, viewportHeight, Vector3()); + for (const sp<RenderNode>& node : nodes) { + if (node->nothingToDraw()) continue; + + // TODO: dedupe this code with onRenderNode() + mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + if (node->applyViewProperties(mCanvasState)) { + // not rejected do ops... + const DisplayListData& data = node->getDisplayListData(); + deferImpl(data.getChunks(), data.getOps()); + } + mCanvasState.restore(); + } +} + +void OpReorderer::defer(int viewportWidth, int viewportHeight, + const std::vector<DisplayListData::Chunk>& chunks, const std::vector<RecordedOp*>& ops) { + ATRACE_NAME("prepare drawing commands"); + mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, + 0, 0, viewportWidth, viewportHeight, Vector3()); + deferImpl(chunks, ops); +} + +/** + * Used to define a list of lambdas referencing private OpReorderer::onXXXXOp() methods. + * + * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. E.g. a + * BitmapOp op then would be dispatched to OpReorderer::onBitmapOp(const BitmapOp&) + */ +#define OP_RECIEVER(Type) \ + [](OpReorderer& reorderer, const RecordedOp& op) { reorderer.on##Type(static_cast<const Type&>(op)); }, +void OpReorderer::deferImpl(const std::vector<DisplayListData::Chunk>& chunks, + const std::vector<RecordedOp*>& ops) { + static std::function<void(OpReorderer& reorderer, const RecordedOp&)> receivers[] = { + MAP_OPS(OP_RECIEVER) + }; + for (const DisplayListData::Chunk& chunk : chunks) { + for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { + const RecordedOp* op = ops[opIndex]; + receivers[op->opId](*this, *op); + } + } +} + +void OpReorderer::replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers) { + ATRACE_NAME("flush drawing commands"); + for (const BatchBase* batch : mBatches) { + // TODO: different behavior based on batch->isMerging() + for (const BakedOpState* op : batch->getOps()) { + receivers[op->op->opId](arg, *op->op, *op); + } + } +} + +BakedOpState* OpReorderer::bakeOpState(const RecordedOp& recordedOp) { + return BakedOpState::tryConstruct(mAllocator, *mCanvasState.currentSnapshot(), recordedOp); +} + +void OpReorderer::onRenderNodeOp(const RenderNodeOp& op) { + if (op.renderNode->nothingToDraw()) { + return; + } + mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + + // apply state from RecordedOp + mCanvasState.concatMatrix(op.localMatrix); + mCanvasState.clipRect(op.localClipRect.left, op.localClipRect.top, + op.localClipRect.right, op.localClipRect.bottom, SkRegion::kIntersect_Op); + + // apply RenderProperties state + if (op.renderNode->applyViewProperties(mCanvasState)) { + // not rejected do ops... + const DisplayListData& data = op.renderNode->getDisplayListData(); + deferImpl(data.getChunks(), data.getOps()); + } + mCanvasState.restore(); +} + +static batchid_t tessellatedBatchId(const SkPaint& paint) { + return paint.getPathEffect() + ? OpBatchType::AlphaMaskTexture + : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices); +} + +void OpReorderer::onBitmapOp(const BitmapOp& op) { + BakedOpState* bakedStateOp = bakeOpState(op); + if (!bakedStateOp) return; // quick rejected + + mergeid_t mergeId = (mergeid_t) op.bitmap->getGenerationID(); + // TODO: AssetAtlas + + deferMergeableOp(bakedStateOp, OpBatchType::Bitmap, mergeId); +} + +void OpReorderer::onRectOp(const RectOp& op) { + BakedOpState* bakedStateOp = bakeOpState(op); + if (!bakedStateOp) return; // quick rejected + deferUnmergeableOp(bakedStateOp, tessellatedBatchId(*op.paint)); +} + +void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) { + BakedOpState* bakedStateOp = bakeOpState(op); + if (!bakedStateOp) return; // quick rejected + deferUnmergeableOp(bakedStateOp, OpBatchType::Vertices); +} + +// 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 +void OpReorderer::locateInsertIndex(int batchId, const Rect& clippedBounds, + BatchBase** targetBatch, size_t* insertBatchIndex) const { + for (size_t i = mBatches.size() - 1; i >= mEarliestBatchIndex; i--) { + BatchBase* overBatch = mBatches[i]; + + if (overBatch == *targetBatch) break; + + // TODO: also consider shader shared between batch types + if (batchId == overBatch->getBatchId()) { + *insertBatchIndex = i + 1; + if (!*targetBatch) break; // found insert position, quit + } + + if (overBatch->intersects(clippedBounds)) { + // 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 = nullptr; + break; + } + } +} + +void OpReorderer::deferUnmergeableOp(BakedOpState* op, batchid_t batchId) { + OpBatch* targetBatch = mBatchLookup[batchId]; + + size_t insertBatchIndex = mBatches.size(); + if (targetBatch) { + locateInsertIndex(batchId, op->computedState.clippedBounds, + (BatchBase**)(&targetBatch), &insertBatchIndex); + } + + if (targetBatch) { + targetBatch->batchOp(op); + } else { + // new non-merging batch + targetBatch = new (mAllocator) OpBatch(batchId, op); + mBatchLookup[batchId] = targetBatch; + mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); + } +} + +// insertion point of a new batch, will hopefully be immediately after similar batch +// (generally, should be similar shader) +void OpReorderer::deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId) { + MergingOpBatch* targetBatch = nullptr; + + // Try to merge with any existing batch with same mergeId + auto getResult = mMergingBatches[batchId].find(mergeId); + if (getResult != mMergingBatches[batchId].end()) { + targetBatch = getResult->second; + if (!targetBatch->canMergeWith(op)) { + targetBatch = nullptr; + } + } + + size_t insertBatchIndex = mBatches.size(); + locateInsertIndex(batchId, op->computedState.clippedBounds, + (BatchBase**)(&targetBatch), &insertBatchIndex); + + if (targetBatch) { + targetBatch->mergeOp(op); + } else { + // new merging batch + targetBatch = new (mAllocator) MergingOpBatch(batchId, op); + mMergingBatches[batchId].insert(std::make_pair(mergeId, targetBatch)); + + mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); + } +} + +void OpReorderer::dump() { + for (const BatchBase* batch : mBatches) { + batch->dump(); + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/OpReorderer.h b/libs/hwui/OpReorderer.h new file mode 100644 index 000000000000..b99172cb673e --- /dev/null +++ b/libs/hwui/OpReorderer.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2015 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_OP_REORDERER_H +#define ANDROID_HWUI_OP_REORDERER_H + +#include "BakedOpState.h" +#include "CanvasState.h" +#include "DisplayList.h" +#include "RecordedOp.h" + +#include <vector> +#include <unordered_map> + +namespace android { +namespace uirenderer { + +class BakedOpState; +class BatchBase; +class MergingOpBatch; +class OpBatch; +class Rect; + +typedef int batchid_t; +typedef const void* mergeid_t; + +namespace OpBatchType { + enum { + None = 0, // Don't batch + Bitmap, + Patch, + AlphaVertices, + Vertices, + AlphaMaskTexture, + Text, + ColorText, + + Count // must be last + }; +} + +class OpReorderer { +public: + OpReorderer(); + + // TODO: not final, just presented this way for simplicity. Layers too? + void defer(int viewportWidth, int viewportHeight, const std::vector< sp<RenderNode> >& nodes); + + void defer(int viewportWidth, int viewportHeight, + const std::vector<DisplayListData::Chunk>& chunks, const std::vector<RecordedOp*>& ops); + typedef std::function<void(void*, const RecordedOp&, const BakedOpState&)> BakedOpReceiver; + + /** + * replayBakedOps() is templated based on what class will recieve ops being replayed. + * + * It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use + * state->op->opId to lookup a receiver that will be called when the op is replayed. + * + * For example a BitmapOp would resolve, via the lambda lookup, to calling: + * + * StaticReceiver::onBitmapOp(Arg* arg, const BitmapOp& op, const BakedOpState& state); + */ +#define BAKED_OP_RECEIVER(Type) \ + [](void* internalArg, const RecordedOp& op, const BakedOpState& state) { \ + StaticReceiver::on##Type(static_cast<Arg*>(internalArg), static_cast<const Type&>(op), state); \ + }, + template <typename StaticReceiver, typename Arg> + void replayBakedOps(Arg* arg) { + static BakedOpReceiver receivers[] = { + MAP_OPS(BAKED_OP_RECEIVER) + }; + StaticReceiver::startFrame(*arg); + replayBakedOpsImpl((void*)arg, receivers); + StaticReceiver::endFrame(*arg); + } +private: + BakedOpState* bakeOpState(const RecordedOp& recordedOp); + + void deferImpl(const std::vector<DisplayListData::Chunk>& chunks, + const std::vector<RecordedOp*>& ops); + + void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers); + + /** + * Declares all OpReorderer::onXXXXOp() methods for every RecordedOp type. + * + * These private methods are called from within deferImpl to defer each individual op + * type differently. + */ +#define INTERNAL_OP_HANDLER(Type) \ + void on##Type(const Type& op); + MAP_OPS(INTERNAL_OP_HANDLER) + + // iterate back toward target to see if anything drawn since should overlap the new op + // if no target, merging ops still iterate to find similar batch to insert after + void locateInsertIndex(int batchId, const Rect& clippedBounds, + BatchBase** targetBatch, size_t* insertBatchIndex) const; + + void deferUnmergeableOp(BakedOpState* op, batchid_t batchId); + + // insertion point of a new batch, will hopefully be immediately after similar batch + // (generally, should be similar shader) + void deferMergeableOp(BakedOpState* op, batchid_t batchId, mergeid_t mergeId); + + void dump(); + + std::vector<BatchBase*> mBatches; + + /** + * 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. + */ + std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatches[OpBatchType::Count]; + + // Maps batch ids to the most recent *non-merging* batch of that id + OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr }; + CanvasState mCanvasState; + + // contains ResolvedOps and Batches + LinearAllocator mAllocator; + + size_t mEarliestBatchIndex = 0; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_OP_REORDERER_H diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h new file mode 100644 index 000000000000..a69f0308ebc5 --- /dev/null +++ b/libs/hwui/RecordedOp.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2015 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_RECORDED_OP_H +#define ANDROID_HWUI_RECORDED_OP_H + +#include "utils/LinearAllocator.h" +#include "Rect.h" +#include "Matrix.h" + +#include "SkXfermode.h" + +class SkBitmap; +class SkPaint; + +namespace android { +namespace uirenderer { + +class RenderNode; +struct Vertex; + +/** + * The provided macro is executed for each op type in order, with the results separated by commas. + * + * This serves as the authoritative list of ops, used for generating ID enum, and ID based LUTs. + */ +#define MAP_OPS(OP_FN) \ + OP_FN(BitmapOp) \ + OP_FN(RectOp) \ + OP_FN(RenderNodeOp) \ + OP_FN(SimpleRectsOp) + +// Generate OpId enum +#define IDENTITY_FN(Type) Type, +namespace RecordedOpId { + enum { + MAP_OPS(IDENTITY_FN) + Count, + }; +} +static_assert(RecordedOpId::BitmapOp == 0, + "First index must be zero for LUTs to work"); + +#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect, const SkPaint* paint +#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const Rect& localClipRect +#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, paint) +#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClipRect, nullptr) + +struct RecordedOp { + /* ID from RecordedOpId - generally used for jumping into function tables */ + const int opId; + + /* bounds in *local* space, without accounting for DisplayList transformation */ + const Rect unmappedBounds; + + /* transform in recording space (vs DisplayList origin) */ + const Matrix4 localMatrix; + + /* clip in recording space */ + const Rect localClipRect; + + /* optional paint, stored in base object to simplify merging logic */ + const SkPaint* paint; +protected: + RecordedOp(unsigned int opId, BASE_PARAMS) + : opId(opId) + , unmappedBounds(unmappedBounds) + , localMatrix(localMatrix) + , localClipRect(localClipRect) + , paint(paint) {} +}; + +struct RenderNodeOp : RecordedOp { + RenderNodeOp(BASE_PARAMS_PAINTLESS, RenderNode* renderNode) + : SUPER_PAINTLESS(RenderNodeOp) + , renderNode(renderNode) {} + RenderNode * renderNode; // not const, since drawing modifies it (somehow...) +}; + +struct BitmapOp : RecordedOp { + BitmapOp(BASE_PARAMS, const SkBitmap* bitmap) + : SUPER(BitmapOp) + , bitmap(bitmap) {} + const SkBitmap* bitmap; + // TODO: asset atlas/texture id lookup? +}; + +struct RectOp : RecordedOp { + RectOp(BASE_PARAMS) + : SUPER(RectOp) {} +}; + +struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?) + SimpleRectsOp(BASE_PARAMS, Vertex* vertices, size_t vertexCount) + : SUPER(SimpleRectsOp) + , vertices(vertices) + , vertexCount(vertexCount) {} + Vertex* vertices; + const size_t vertexCount; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_RECORDED_OP_H diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp new file mode 100644 index 000000000000..c4debd60d888 --- /dev/null +++ b/libs/hwui/RecordingCanvas.cpp @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2015 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 "RecordingCanvas.h" + +#include "RecordedOp.h" +#include "RenderNode.h" + +namespace android { +namespace uirenderer { + +RecordingCanvas::RecordingCanvas(size_t width, size_t height) + : mState(*this) + , mResourceCache(ResourceCache::getInstance()) { + reset(width, height); +} + +RecordingCanvas::~RecordingCanvas() { + LOG_ALWAYS_FATAL_IF(mDisplayListData, + "Destroyed a RecordingCanvas during a record!"); +} + +void RecordingCanvas::reset(int width, int height) { + LOG_ALWAYS_FATAL_IF(mDisplayListData, + "prepareDirty called a second time during a recording!"); + mDisplayListData = new DisplayListData(); + + mState.initializeSaveStack(width, height, 0, 0, width, height, Vector3()); + + mDeferredBarrierType = kBarrier_InOrder; + mState.setDirtyClip(false); + mRestoreSaveCount = -1; +} + +DisplayListData* RecordingCanvas::finishRecording() { + mPaintMap.clear(); + mRegionMap.clear(); + mPathMap.clear(); + DisplayListData* data = mDisplayListData; + mDisplayListData = nullptr; + mSkiaCanvasProxy.reset(nullptr); + return data; +} + +SkCanvas* RecordingCanvas::asSkCanvas() { + LOG_ALWAYS_FATAL_IF(!mDisplayListData, + "attempting to get an SkCanvas when we are not recording!"); + if (!mSkiaCanvasProxy) { + mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this)); + } + + // SkCanvas instances default to identity transform, but should inherit + // the state of this Canvas; if this code was in the SkiaCanvasProxy + // constructor, we couldn't cache mSkiaCanvasProxy. + SkMatrix parentTransform; + getMatrix(&parentTransform); + mSkiaCanvasProxy.get()->setMatrix(parentTransform); + + return mSkiaCanvasProxy.get(); +} + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas state operations +// ---------------------------------------------------------------------------- +// Save (layer) +int RecordingCanvas::save(SkCanvas::SaveFlags flags) { + return mState.save((int) flags); +} + +void RecordingCanvas::RecordingCanvas::restore() { + if (mRestoreSaveCount < 0) { + restoreToCount(getSaveCount() - 1); + return; + } + + mRestoreSaveCount--; + mState.restore(); +} + +void RecordingCanvas::restoreToCount(int saveCount) { + mRestoreSaveCount = saveCount; + mState.restoreToCount(saveCount); +} + +int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, + SkCanvas::SaveFlags flags) { + LOG_ALWAYS_FATAL("TODO"); + return 0; +} + +// Matrix +void RecordingCanvas::rotate(float degrees) { + if (degrees == 0) return; + + mState.rotate(degrees); +} + +void RecordingCanvas::scale(float sx, float sy) { + if (sx == 1 && sy == 1) return; + + mState.scale(sx, sy); +} + +void RecordingCanvas::skew(float sx, float sy) { + mState.skew(sx, sy); +} + +void RecordingCanvas::translate(float dx, float dy) { + if (dx == 0 && dy == 0) return; + + mState.translate(dx, dy, 0); +} + +// Clip +bool RecordingCanvas::getClipBounds(SkRect* outRect) const { + Rect bounds = mState.getLocalClipBounds(); + *outRect = SkRect::MakeLTRB(bounds.left, bounds.top, bounds.right, bounds.bottom); + return !(outRect->isEmpty()); +} +bool RecordingCanvas::quickRejectRect(float left, float top, float right, float bottom) const { + return mState.quickRejectConservative(left, top, right, bottom); +} +bool RecordingCanvas::quickRejectPath(const SkPath& path) const { + SkRect bounds = path.getBounds(); + return mState.quickRejectConservative(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom); +} +bool RecordingCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) { + return mState.clipRect(left, top, right, bottom, op); +} +bool RecordingCanvas::clipPath(const SkPath* path, SkRegion::Op op) { + return mState.clipPath(path, op); +} +bool RecordingCanvas::clipRegion(const SkRegion* region, SkRegion::Op op) { + return mState.clipRegion(region, op); +} + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas draw operations +// ---------------------------------------------------------------------------- +void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) { + SkPaint paint; + paint.setColor(color); + paint.setXfermodeMode(mode); + drawPaint(paint); +} + +void RecordingCanvas::drawPaint(const SkPaint& paint) { + // TODO: more efficient recording? + Matrix4 identity; + identity.loadIdentity(); + + addOp(new (alloc()) RectOp( + mState.getRenderTargetClipBounds(), + identity, + mState.getRenderTargetClipBounds(), + refPaint(&paint))); +} + +// Geometry +void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) { + LOG_ALWAYS_FATAL("TODO!"); +} +void RecordingCanvas::drawLines(const float* points, int count, const SkPaint& paint) { + LOG_ALWAYS_FATAL("TODO!"); +} +void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) { + addOp(new (alloc()) RectOp( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + mState.getRenderTargetClipBounds(), + refPaint(&paint))); +} + +void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) { + if (rects == nullptr) return; + + Vertex* rectData = (Vertex*) mDisplayListData->allocator.alloc(vertexCount * sizeof(Vertex)); + Vertex* vertex = rectData; + + float left = FLT_MAX; + float top = FLT_MAX; + float right = FLT_MIN; + float bottom = FLT_MIN; + for (int index = 0; index < vertexCount; index += 4) { + float l = rects[index + 0]; + float t = rects[index + 1]; + float r = rects[index + 2]; + float b = rects[index + 3]; + + Vertex::set(vertex++, l, t); + Vertex::set(vertex++, r, t); + Vertex::set(vertex++, l, b); + Vertex::set(vertex++, r, b); + + left = std::min(left, l); + top = std::min(top, t); + right = std::max(right, r); + bottom = std::max(bottom, b); + } + addOp(new (alloc()) SimpleRectsOp( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + mState.getRenderTargetClipBounds(), + refPaint(paint), rectData, vertexCount)); +} + +void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { + if (paint.getStyle() == SkPaint::kFill_Style + && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) { + int count = 0; + Vector<float> rects; + SkRegion::Iterator it(region); + while (!it.done()) { + const SkIRect& r = it.rect(); + rects.push(r.fLeft); + rects.push(r.fTop); + rects.push(r.fRight); + rects.push(r.fBottom); + count += 4; + it.next(); + } + drawSimpleRects(rects.array(), count, &paint); + } else { + SkRegion::Iterator it(region); + while (!it.done()) { + const SkIRect& r = it.rect(); + drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint); + it.next(); + } + } +} +void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom, + float rx, float ry, const SkPaint& paint) { + LOG_ALWAYS_FATAL("TODO!"); +} +void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) { + LOG_ALWAYS_FATAL("TODO!"); +} +void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) { + LOG_ALWAYS_FATAL("TODO!"); +} +void RecordingCanvas::drawArc(float left, float top, float right, float bottom, + float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) { + LOG_ALWAYS_FATAL("TODO!"); +} +void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { + LOG_ALWAYS_FATAL("TODO!"); +} + +// Bitmap-based +void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) { + save(SkCanvas::kMatrix_SaveFlag); + translate(left, top); + drawBitmap(&bitmap, paint); + restore(); +} + +void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, + const SkPaint* paint) { + if (matrix.isIdentity()) { + drawBitmap(&bitmap, paint); + } else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) + && MathUtils::isPositive(matrix.getScaleX()) + && MathUtils::isPositive(matrix.getScaleY())) { + // SkMatrix::isScaleTranslate() not available in L + SkRect src; + SkRect dst; + bitmap.getBounds(&src); + matrix.mapRect(&dst, src); + drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, + dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint); + } else { + save(SkCanvas::kMatrix_SaveFlag); + concat(matrix); + drawBitmap(&bitmap, paint); + restore(); + } +} +void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop, + float srcRight, float srcBottom, float dstLeft, float dstTop, + float dstRight, float dstBottom, const SkPaint* paint) { + if (srcLeft == 0 && srcTop == 0 + && srcRight == bitmap.width() + && srcBottom == bitmap.height() + && (srcBottom - srcTop == dstBottom - dstTop) + && (srcRight - srcLeft == dstRight - dstLeft)) { + // transform simple rect to rect drawing case into position bitmap ops, since they merge + save(SkCanvas::kMatrix_SaveFlag); + translate(dstLeft, dstTop); + drawBitmap(&bitmap, paint); + restore(); + } else { + LOG_ALWAYS_FATAL("TODO!"); + } +} +void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, + const float* vertices, const int* colors, const SkPaint* paint) { + LOG_ALWAYS_FATAL("TODO!"); +} +void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk, + float dstLeft, float dstTop, float dstRight, float dstBottom, + const SkPaint* paint) { + LOG_ALWAYS_FATAL("TODO!"); +} + +// Text +void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int count, + const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, + float boundsRight, float boundsBottom, float totalAdvance) { + LOG_ALWAYS_FATAL("TODO!"); +} +void RecordingCanvas::drawPosText(const uint16_t* text, const float* positions, int count, + int posCount, const SkPaint& paint) { + LOG_ALWAYS_FATAL("TODO!"); +} +void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, + float hOffset, float vOffset, const SkPaint& paint) { + LOG_ALWAYS_FATAL("TODO!"); +} + +void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { + addOp(new (alloc()) BitmapOp( + Rect(0, 0, bitmap->width(), bitmap->height()), + *(mState.currentSnapshot()->transform), + mState.getRenderTargetClipBounds(), + refPaint(paint), refBitmap(*bitmap))); +} +void RecordingCanvas::drawRenderNode(RenderNode* renderNode) { + RenderNodeOp* op = new (alloc()) RenderNodeOp( + Rect(0, 0, renderNode->getWidth(), renderNode->getHeight()), // are these safe? they're theoretically dynamic + *(mState.currentSnapshot()->transform), + mState.getRenderTargetClipBounds(), + renderNode); + int opIndex = addOp(op); + int childIndex = mDisplayListData->addChild(op); + + // update the chunk's child indices + DisplayListData::Chunk& chunk = mDisplayListData->chunks.back(); + chunk.endChildIndex = childIndex + 1; + + if (renderNode->stagingProperties().isProjectionReceiver()) { + // use staging property, since recording on UI thread + mDisplayListData->projectionReceiveIndex = opIndex; + } +} + +size_t RecordingCanvas::addOp(RecordedOp* op) { + // TODO: validate if "addDrawOp" quickrejection logic is useful before adding + int insertIndex = mDisplayListData->ops.size(); + mDisplayListData->ops.push_back(op); + if (mDeferredBarrierType != kBarrier_None) { + // op is first in new chunk + mDisplayListData->chunks.emplace_back(); + DisplayListData::Chunk& newChunk = mDisplayListData->chunks.back(); + newChunk.beginOpIndex = insertIndex; + newChunk.endOpIndex = insertIndex + 1; + newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder); + + int nextChildIndex = mDisplayListData->children().size(); + newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex; + mDeferredBarrierType = kBarrier_None; + } else { + // standard case - append to existing chunk + mDisplayListData->chunks.back().endOpIndex = insertIndex + 1; + } + return insertIndex; +} + +void RecordingCanvas::refBitmapsInShader(const SkShader* shader) { + if (!shader) return; + + // If this paint has an SkShader that has an SkBitmap add + // it to the bitmap pile + SkBitmap bitmap; + SkShader::TileMode xy[2]; + if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) { + refBitmap(bitmap); + return; + } + SkShader::ComposeRec rec; + if (shader->asACompose(&rec)) { + refBitmapsInShader(rec.fShaderA); + refBitmapsInShader(rec.fShaderB); + return; + } +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h new file mode 100644 index 000000000000..ed299e56ce7c --- /dev/null +++ b/libs/hwui/RecordingCanvas.h @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2015 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_RECORDING_CANVAS_H +#define ANDROID_HWUI_RECORDING_CANVAS_H + +#include "Canvas.h" +#include "CanvasState.h" +#include "DisplayList.h" +#include "utils/LinearAllocator.h" +#include "utils/NinePatch.h" +#include "ResourceCache.h" +#include "SkiaCanvasProxy.h" +#include "Snapshot.h" + +#include "SkDrawFilter.h" +#include <vector> + +namespace android { +namespace uirenderer { + +class OpReceiver; +struct RecordedOp; + +class RecordingCanvas: public Canvas, public CanvasStateClient { +public: + RecordingCanvas(size_t width, size_t height); + virtual ~RecordingCanvas(); + + void reset(int width, int height); + DisplayListData* finishRecording(); + +// ---------------------------------------------------------------------------- +// MISC HWUI OPERATIONS - TODO: CATEGORIZE +// ---------------------------------------------------------------------------- + void insertReorderBarrier(bool enableReorder) {} + void drawRenderNode(RenderNode* renderNode); + +// ---------------------------------------------------------------------------- +// CanvasStateClient interface +// ---------------------------------------------------------------------------- + virtual void onViewportInitialized() override {} + virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override {} + virtual GLuint getTargetFbo() const override { return -1; } + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas interface +// ---------------------------------------------------------------------------- + virtual SkCanvas* asSkCanvas() override; + + virtual void setBitmap(const SkBitmap& bitmap) override { + LOG_ALWAYS_FATAL("RecordingCanvas is not backed by a bitmap."); + } + + virtual bool isOpaque() override { return false; } + virtual int width() override { return mState.getWidth(); } + virtual int height() override { return mState.getHeight(); } + + virtual void setHighContrastText(bool highContrastText) override { + mHighContrastText = highContrastText; + } + virtual bool isHighContrastText() override { return mHighContrastText; } + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas state operations +// ---------------------------------------------------------------------------- + // Save (layer) + virtual int getSaveCount() const override { return mState.getSaveCount(); } + virtual int save(SkCanvas::SaveFlags flags) override; + virtual void restore() override; + virtual void restoreToCount(int saveCount) override; + + virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, + SkCanvas::SaveFlags flags) override; + virtual int saveLayerAlpha(float left, float top, float right, float bottom, + int alpha, SkCanvas::SaveFlags flags) override { + SkPaint paint; + paint.setAlpha(alpha); + return saveLayer(left, top, right, bottom, &paint, flags); + } + + // Matrix + virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); } + virtual void setMatrix(const SkMatrix& matrix) override { mState.setMatrix(matrix); } + + virtual void concat(const SkMatrix& matrix) override { mState.concatMatrix(matrix); } + virtual void rotate(float degrees) override; + virtual void scale(float sx, float sy) override; + virtual void skew(float sx, float sy) override; + virtual void translate(float dx, float dy) override; + + // Clip + virtual bool getClipBounds(SkRect* outRect) const override; + virtual bool quickRejectRect(float left, float top, float right, float bottom) const override; + virtual bool quickRejectPath(const SkPath& path) const override; + + virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op) override; + virtual bool clipPath(const SkPath* path, SkRegion::Op op) override; + virtual bool clipRegion(const SkRegion* region, SkRegion::Op op) override; + + // Misc + virtual SkDrawFilter* getDrawFilter() override { return mDrawFilter.get(); } + virtual void setDrawFilter(SkDrawFilter* filter) override { + mDrawFilter.reset(SkSafeRef(filter)); + } + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas draw operations +// ---------------------------------------------------------------------------- + virtual void drawColor(int color, SkXfermode::Mode mode) override; + virtual void drawPaint(const SkPaint& paint) override; + + // Geometry + virtual void drawPoint(float x, float y, const SkPaint& paint) override { + float points[2] = { x, y }; + drawPoints(points, 2, paint); + } + virtual void drawPoints(const float* points, int count, const SkPaint& paint) override; + virtual void drawLine(float startX, float startY, float stopX, float stopY, + const SkPaint& paint) override { + float points[4] = { startX, startY, stopX, stopY }; + drawLines(points, 4, paint); + } + virtual void drawLines(const float* points, int count, const SkPaint& paint) override; + virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override; + virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override; + virtual void drawRoundRect(float left, float top, float right, float bottom, + float rx, float ry, const SkPaint& paint) override; + virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override; + virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint) override; + virtual void drawArc(float left, float top, float right, float bottom, + float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) override; + virtual void drawPath(const SkPath& path, const SkPaint& paint) override; + virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount, + const float* verts, const float* tex, const int* colors, + const uint16_t* indices, int indexCount, const SkPaint& paint) override + { /* RecordingCanvas does not support drawVertices(); ignore */ } + + // Bitmap-based + virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override; + virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, + const SkPaint* paint) override; + virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop, + float srcRight, float srcBottom, float dstLeft, float dstTop, + float dstRight, float dstBottom, const SkPaint* paint) override; + virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, + const float* vertices, const int* colors, const SkPaint* paint) override; + virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk, + float dstLeft, float dstTop, float dstRight, float dstBottom, + const SkPaint* paint) override; + + // Text + virtual void drawText(const uint16_t* glyphs, const float* positions, int count, + const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, + float boundsRight, float boundsBottom, float totalAdvance) override; + virtual void drawPosText(const uint16_t* text, const float* positions, int count, + int posCount, const SkPaint& paint) override; + virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, + float hOffset, float vOffset, const SkPaint& paint) override; + virtual bool drawTextAbsolutePos() const override { return false; } + +private: + enum DeferredBarrierType { + kBarrier_None, + kBarrier_InOrder, + kBarrier_OutOfOrder, + }; + + void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint); + void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint); + + + size_t addOp(RecordedOp* op); +// ---------------------------------------------------------------------------- +// lazy object copy +// ---------------------------------------------------------------------------- + LinearAllocator& alloc() { return mDisplayListData->allocator; } + + void refBitmapsInShader(const SkShader* shader); + + template<class T> + inline const T* refBuffer(const T* srcBuffer, int32_t count) { + if (!srcBuffer) return nullptr; + + T* dstBuffer = (T*) mDisplayListData->allocator.alloc(count * sizeof(T)); + memcpy(dstBuffer, srcBuffer, count * sizeof(T)); + return dstBuffer; + } + + inline char* refText(const char* text, size_t byteLength) { + return (char*) refBuffer<uint8_t>((uint8_t*)text, byteLength); + } + + inline const SkPath* refPath(const SkPath* path) { + if (!path) return nullptr; + + // The points/verbs within the path are refcounted so this copy operation + // is inexpensive and maintains the generationID of the original path. + const SkPath* cachedPath = new SkPath(*path); + mDisplayListData->pathResources.push_back(cachedPath); + return cachedPath; + } + + inline const SkPaint* refPaint(const SkPaint* paint) { + if (!paint) return nullptr; + + // If there is a draw filter apply it here and store the modified paint + // so that we don't need to modify the paint every time we access it. + SkTLazy<SkPaint> filteredPaint; + if (mDrawFilter.get()) { + filteredPaint.set(*paint); + mDrawFilter->filter(filteredPaint.get(), SkDrawFilter::kPaint_Type); + paint = filteredPaint.get(); + } + + // compute the hash key for the paint and check the cache. + const uint32_t key = paint->getHash(); + const SkPaint* cachedPaint = mPaintMap.valueFor(key); + // In the unlikely event that 2 unique paints have the same hash we do a + // object equality check to ensure we don't erroneously dedup them. + if (cachedPaint == nullptr || *cachedPaint != *paint) { + cachedPaint = new SkPaint(*paint); + std::unique_ptr<const SkPaint> copy(cachedPaint); + mDisplayListData->paints.push_back(std::move(copy)); + + // replaceValueFor() performs an add if the entry doesn't exist + mPaintMap.replaceValueFor(key, cachedPaint); + refBitmapsInShader(cachedPaint->getShader()); + } + + return cachedPaint; + } + + inline const SkRegion* refRegion(const SkRegion* region) { + if (!region) { + return region; + } + + const SkRegion* cachedRegion = mRegionMap.valueFor(region); + // TODO: Add generation ID to SkRegion + if (cachedRegion == nullptr) { + std::unique_ptr<const SkRegion> copy(new SkRegion(*region)); + cachedRegion = copy.get(); + mDisplayListData->regions.push_back(std::move(copy)); + + // replaceValueFor() performs an add if the entry doesn't exist + mRegionMap.replaceValueFor(region, cachedRegion); + } + + return cachedRegion; + } + + inline const SkBitmap* refBitmap(const SkBitmap& bitmap) { + // Note that this assumes the bitmap is immutable. There are cases this won't handle + // correctly, such as creating the bitmap from scratch, drawing with it, changing its + // contents, and drawing again. The only fix would be to always copy it the first time, + // which doesn't seem worth the extra cycles for this unlikely case. + SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap); + alloc().autoDestroy(localBitmap); + mDisplayListData->bitmapResources.push_back(localBitmap); + return localBitmap; + } + + inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) { + mDisplayListData->patchResources.push_back(patch); + mResourceCache.incrementRefcount(patch); + return patch; + } + + DefaultKeyedVector<uint32_t, const SkPaint*> mPaintMap; + DefaultKeyedVector<const SkPath*, const SkPath*> mPathMap; + DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap; + + CanvasState mState; + std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy; + ResourceCache& mResourceCache; + DeferredBarrierType mDeferredBarrierType = kBarrier_None; + DisplayListData* mDisplayListData = nullptr; + bool mHighContrastText = false; + SkAutoTUnref<SkDrawFilter> mDrawFilter; + int mRestoreSaveCount = -1; +}; // class RecordingCanvas + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_RECORDING_CANVAS_H diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index bf1b4d0b0d0e..d122a55ef702 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -25,6 +25,9 @@ #include "DamageAccumulator.h" #include "Debug.h" +#if HWUI_NEW_OPS +#include "RecordedOp.h" +#endif #include "DisplayListOp.h" #include "LayerRenderer.h" #include "OpenGLRenderer.h" @@ -47,7 +50,7 @@ void RenderNode::debugDumpLayers(const char* prefix) { } if (mDisplayListData) { for (size_t i = 0; i < mDisplayListData->children().size(); i++) { - mDisplayListData->children()[i]->mRenderNode->debugDumpLayers(prefix); + mDisplayListData->children()[i]->renderNode->debugDumpLayers(prefix); } } } @@ -172,7 +175,7 @@ void RenderNode::copyTo(proto::RenderNode *pnode) { pnode->clear_children(); if (mDisplayListData) { for (auto&& child : mDisplayListData->children()) { - child->mRenderNode->copyTo(pnode->add_children()); + child->renderNode->copyTo(pnode->add_children()); } } } @@ -332,6 +335,10 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) { info.damageAccumulator->popTransform(); } +void RenderNode::syncProperties() { + mProperties = mStagingProperties; +} + void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { // Push the animators first so that setupStartValueIfNecessary() is called // before properties() is trampled by stagingProperties(), as they are @@ -343,7 +350,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { mDirtyPropertyFields = 0; damageSelf(info); info.damageAccumulator->popTransform(); - mProperties = mStagingProperties; + syncProperties(); applyLayerPropertiesToLayer(info); // We could try to be clever and only re-damage if the matrix changed. // However, we don't need to worry about that. The cost of over-damaging @@ -364,35 +371,39 @@ void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) { mLayer->setBlend(props.needsBlending()); } +void RenderNode::syncDisplayList() { + // Make sure we inc first so that we don't fluctuate between 0 and 1, + // which would thrash the layer cache + if (mStagingDisplayListData) { + for (auto&& child : mStagingDisplayListData->children()) { + child->renderNode->incParentRefCount(); + } + } + deleteDisplayListData(); + mDisplayListData = mStagingDisplayListData; + mStagingDisplayListData = nullptr; + if (mDisplayListData) { + for (size_t i = 0; i < mDisplayListData->functors.size(); i++) { + (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr); + } + } +} + void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) { if (mNeedsDisplayListDataSync) { mNeedsDisplayListDataSync = false; - // Make sure we inc first so that we don't fluctuate between 0 and 1, - // which would thrash the layer cache - if (mStagingDisplayListData) { - for (size_t i = 0; i < mStagingDisplayListData->children().size(); i++) { - mStagingDisplayListData->children()[i]->mRenderNode->incParentRefCount(); - } - } // Damage with the old display list first then the new one to catch any // changes in isRenderable or, in the future, bounds damageSelf(info); - deleteDisplayListData(); - mDisplayListData = mStagingDisplayListData; - mStagingDisplayListData = nullptr; - if (mDisplayListData) { - for (size_t i = 0; i < mDisplayListData->functors.size(); i++) { - (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr); - } - } + syncDisplayList(); damageSelf(info); } } void RenderNode::deleteDisplayListData() { if (mDisplayListData) { - for (size_t i = 0; i < mDisplayListData->children().size(); i++) { - mDisplayListData->children()[i]->mRenderNode->decParentRefCount(); + for (auto&& child : mDisplayListData->children()) { + child->renderNode->decParentRefCount(); } } delete mDisplayListData; @@ -407,13 +418,17 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL info.prepareTextures = cache.prefetchAndMarkInUse( info.canvasContext, subtree->bitmapResources[i]); } - for (size_t i = 0; i < subtree->children().size(); i++) { - DrawRenderNodeOp* op = subtree->children()[i]; - RenderNode* childNode = op->mRenderNode; + for (auto&& op : subtree->children()) { + RenderNode* childNode = op->renderNode; +#if HWUI_NEW_OPS + info.damageAccumulator->pushTransform(&op->localMatrix); + bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip; +#else info.damageAccumulator->pushTransform(&op->mTransformFromParent); bool childFunctorsNeedLayer = functorsNeedLayer // Recorded with non-rect clip, or canvas-rotated by parent || op->mRecordedWithPotentialStencilClip; +#endif childNode->prepareTreeImpl(info, childFunctorsNeedLayer); info.damageAccumulator->popTransform(); } @@ -426,8 +441,8 @@ void RenderNode::destroyHardwareResources() { mLayer = nullptr; } if (mDisplayListData) { - for (size_t i = 0; i < mDisplayListData->children().size(); i++) { - mDisplayListData->children()[i]->mRenderNode->destroyHardwareResources(); + for (auto&& child : mDisplayListData->children()) { + child->renderNode->destroyHardwareResources(); } if (mNeedsDisplayListDataSync) { // Next prepare tree we are going to push a new display list, so we can @@ -449,6 +464,34 @@ void RenderNode::decParentRefCount() { } } +bool RenderNode::applyViewProperties(CanvasState& canvasState) const { + const Outline& outline = properties().getOutline(); + if (properties().getAlpha() <= 0 + || (outline.getShouldClip() && outline.isEmpty()) + || properties().getScaleX() == 0 + || properties().getScaleY() == 0) { + return false; // rejected + } + + if (properties().getLeft() != 0 || properties().getTop() != 0) { + canvasState.translate(properties().getLeft(), properties().getTop()); + } + if (properties().getStaticMatrix()) { + canvasState.concatMatrix(*properties().getStaticMatrix()); + } else if (properties().getAnimationMatrix()) { + canvasState.concatMatrix(*properties().getAnimationMatrix()); + } + if (properties().hasTransformMatrix()) { + if (properties().isTransformTranslateOnly()) { + canvasState.translate(properties().getTranslationX(), properties().getTranslationY()); + } else { + canvasState.concatMatrix(*properties().getTransformMatrix()); + } + } + return !canvasState.quickRejectConservative( + 0, 0, properties().getWidth(), properties().getHeight()); +} + /* * For property operations, we pass a savecount of 0, since the operations aren't part of the * displaylist, and thus don't have to compensate for the record-time/playback-time discrepancy in @@ -580,6 +623,7 @@ void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform) * which are flagged to not draw in the standard draw loop. */ void RenderNode::computeOrdering() { +#if !HWUI_NEW_OPS ATRACE_CALL(); mProjectedNodes.clear(); @@ -588,14 +632,16 @@ void RenderNode::computeOrdering() { if (mDisplayListData == nullptr) return; for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) { DrawRenderNodeOp* childOp = mDisplayListData->children()[i]; - childOp->mRenderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity()); + childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity()); } +#endif } void RenderNode::computeOrderingImpl( DrawRenderNodeOp* opState, std::vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface, const mat4* transformFromProjectionSurface) { +#if !HWUI_NEW_OPS mProjectedNodes.clear(); if (mDisplayListData == nullptr || mDisplayListData->isEmpty()) return; @@ -619,7 +665,7 @@ void RenderNode::computeOrderingImpl( bool haveAppliedPropertiesToProjection = false; for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) { DrawRenderNodeOp* childOp = mDisplayListData->children()[i]; - RenderNode* child = childOp->mRenderNode; + RenderNode* child = childOp->renderNode; std::vector<DrawRenderNodeOp*>* projectionChildren = nullptr; const mat4* projectionTransform = nullptr; @@ -642,6 +688,7 @@ void RenderNode::computeOrderingImpl( child->computeOrderingImpl(childOp, projectionChildren, projectionTransform); } } +#endif } class DeferOperationHandler { @@ -701,11 +748,12 @@ void RenderNode::replay(ReplayStateStruct& replayStruct, const int level) { void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk, std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) { +#if !HWUI_NEW_OPS if (chunk.beginChildIndex == chunk.endChildIndex) return; for (unsigned int i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) { DrawRenderNodeOp* childOp = mDisplayListData->children()[i]; - RenderNode* child = childOp->mRenderNode; + RenderNode* child = childOp->renderNode; float childZ = child->properties().getZ(); if (!MathUtils::isZero(childZ) && chunk.reorderChildren) { @@ -719,6 +767,7 @@ void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk, // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order) std::stable_sort(zTranslatedNodes.begin(), zTranslatedNodes.end()); +#endif } template <class T> @@ -824,7 +873,7 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode, while (shadowIndex < endIndex || drawIndex < endIndex) { if (shadowIndex < endIndex) { DrawRenderNodeOp* casterOp = zTranslatedNodes[shadowIndex].value; - RenderNode* caster = casterOp->mRenderNode; + RenderNode* caster = casterOp->renderNode; const float casterZ = zTranslatedNodes[shadowIndex].key; // attempt to render the shadow if the caster about to be drawn is its caster, // OR if its caster's Z value is similar to the previous potential caster @@ -869,7 +918,7 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& const DisplayListOp* op = (mDisplayListData->displayListOps[mDisplayListData->projectionReceiveIndex]); const DrawRenderNodeOp* backgroundOp = reinterpret_cast<const DrawRenderNodeOp*>(op); - const RenderProperties& backgroundProps = backgroundOp->mRenderNode->properties(); + const RenderProperties& backgroundProps = backgroundOp->renderNode->properties(); renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY()); // If the projection reciever has an outline, we mask projected content to it diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 88fc60856feb..ff673ba32343 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -29,8 +29,8 @@ #include "AnimatorManager.h" #include "Debug.h" -#include "Matrix.h" #include "DisplayList.h" +#include "Matrix.h" #include "RenderProperties.h" #include <vector> @@ -43,6 +43,7 @@ class SkRegion; namespace android { namespace uirenderer { +class CanvasState; class DisplayListOp; class DisplayListCanvas; class OpenGLRenderer; @@ -74,6 +75,7 @@ class RenderNode; * attached. */ class RenderNode : public VirtualLightRefBase { +friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties public: enum DirtyPropertyMask { GENERIC = 1 << 1, @@ -176,8 +178,25 @@ public: AnimatorManager& animators() { return mAnimatorManager; } + // Returns false if the properties dictate the subtree contained in this RenderNode won't render + bool applyViewProperties(CanvasState& canvasState) const; + void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const; + bool nothingToDraw() const { + const Outline& outline = properties().getOutline(); + return mDisplayListData == nullptr + || properties().getAlpha() <= 0 + || (outline.getShouldClip() && outline.isEmpty()) + || properties().getScaleX() == 0 + || properties().getScaleY() == 0; + } + + // Only call if RenderNode has DisplayListData... + const DisplayListData& getDisplayListData() const { + return *mDisplayListData; + } + private: typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair; @@ -235,6 +254,10 @@ private: const char* mText; }; + + void syncProperties(); + void syncDisplayList(); + void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer); void pushStagingPropertiesChanges(TreeInfo& info); void pushStagingDisplayListChanges(TreeInfo& info); diff --git a/libs/hwui/microbench/DisplayListCanvasBench.cpp b/libs/hwui/microbench/DisplayListCanvasBench.cpp index 7b6be7a17a37..fd42adb13da2 100644 --- a/libs/hwui/microbench/DisplayListCanvasBench.cpp +++ b/libs/hwui/microbench/DisplayListCanvasBench.cpp @@ -18,12 +18,22 @@ #include <utils/Singleton.h> #include "DisplayList.h" +#if HWUI_NEW_OPS +#include "RecordingCanvas.h" +#else #include "DisplayListCanvas.h" +#endif #include "microbench/MicroBench.h" using namespace android; using namespace android::uirenderer; +#if HWUI_NEW_OPS +typedef RecordingCanvas TestCanvas; +#else +typedef DisplayListCanvas TestCanvas; +#endif + BENCHMARK_NO_ARG(BM_DisplayListData_alloc); void BM_DisplayListData_alloc::Run(int iters) { StartBenchmarkTiming(); @@ -48,7 +58,7 @@ void BM_DisplayListData_alloc_theoretical::Run(int iters) { BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_empty); void BM_DisplayListCanvas_record_empty::Run(int iters) { - DisplayListCanvas canvas(100, 100); + TestCanvas canvas(100, 100); canvas.finishRecording(); StartBenchmarkTiming(); @@ -62,7 +72,7 @@ void BM_DisplayListCanvas_record_empty::Run(int iters) { BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_saverestore); void BM_DisplayListCanvas_record_saverestore::Run(int iters) { - DisplayListCanvas canvas(100, 100); + TestCanvas canvas(100, 100); canvas.finishRecording(); StartBenchmarkTiming(); @@ -80,7 +90,7 @@ void BM_DisplayListCanvas_record_saverestore::Run(int iters) { BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_translate); void BM_DisplayListCanvas_record_translate::Run(int iters) { - DisplayListCanvas canvas(100, 100); + TestCanvas canvas(100, 100); canvas.finishRecording(); StartBenchmarkTiming(); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 38f6e539693e..e1d8abdd7034 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -28,6 +28,11 @@ #include "renderstate/Stencil.h" #include "protos/hwui.pb.h" +#if HWUI_NEW_OPS +#include "BakedOpRenderer.h" +#include "OpReorderer.h" +#endif + #include <cutils/properties.h> #include <google/protobuf/io/zero_copy_stream_impl.h> #include <private/hwui/DrawGlInfo.h> @@ -121,9 +126,11 @@ void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) { bool CanvasContext::initialize(ANativeWindow* window) { setSurface(window); +#if !HWUI_NEW_OPS if (mCanvas) return false; mCanvas = new OpenGLRenderer(mRenderThread.renderState()); mCanvas->initProperties(); +#endif return true; } @@ -162,11 +169,13 @@ void CanvasContext::makeCurrent() { } void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) { +#if !HWUI_NEW_OPS bool success = layerUpdater->apply(); LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!"); if (layerUpdater->backingLayer()->deferredUpdateScheduled) { mCanvas->pushLayerUpdate(layerUpdater->backingLayer()); } +#endif } static bool wasSkipped(FrameInfo* info) { @@ -239,8 +248,10 @@ void CanvasContext::notifyFramePending() { } void CanvasContext::draw() { +#if !HWUI_NEW_OPS LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE, "drawRenderNode called on a context with no canvas or surface!"); +#endif SkRect dirty; mDamageAccumulator.finish(&dirty); @@ -254,6 +265,8 @@ void CanvasContext::draw() { mCurrentFrameInfo->markIssueDrawCommandsStart(); Frame frame = mEglManager.beginFrame(mEglSurface); + +#if !HWUI_NEW_OPS if (frame.width() != mCanvas->getViewportWidth() || frame.height() != mCanvas->getViewportHeight()) { // can't rely on prior content of window if viewport size changes @@ -417,6 +430,16 @@ void CanvasContext::draw() { // Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point mCurrentFrameInfo->markSwapBuffers(); +#else + OpReorderer reorderer; + reorderer.defer(frame.width(), frame.height(), mRenderNodes); + BakedOpRenderer::Info info(Caches::getInstance(), mRenderThread.renderState(), + frame.width(), frame.height(), mOpaque); + reorderer.replayBakedOps<BakedOpRenderer>(&info); + + bool drew = info.didDraw; + SkRect screenDirty = SkRect::MakeWH(frame.width(), frame.height()); +#endif if (drew) { if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) { diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp index a59261c14fc5..891af9171518 100644 --- a/libs/hwui/tests/TreeContentAnimation.cpp +++ b/libs/hwui/tests/TreeContentAnimation.cpp @@ -20,6 +20,7 @@ #include <AnimationContext.h> #include <DisplayListCanvas.h> +#include <RecordingCanvas.h> #include <RenderNode.h> #include <renderthread/RenderProxy.h> #include <renderthread/RenderTask.h> @@ -39,6 +40,13 @@ using namespace android::uirenderer; using namespace android::uirenderer::renderthread; using namespace android::uirenderer::test; +#if HWUI_NEW_OPS +typedef RecordingCanvas TestCanvas; +#else +typedef DisplayListCanvas TestCanvas; +#endif + + class ContextFactory : public IContextFactory { public: virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { @@ -46,13 +54,13 @@ public: } }; -static DisplayListCanvas* startRecording(RenderNode* node) { - DisplayListCanvas* renderer = new DisplayListCanvas( +static TestCanvas* startRecording(RenderNode* node) { + TestCanvas* renderer = new TestCanvas( node->stagingProperties().getWidth(), node->stagingProperties().getHeight()); return renderer; } -static void endRecording(DisplayListCanvas* renderer, RenderNode* node) { +static void endRecording(TestCanvas* renderer, RenderNode* node) { node->setStagingDisplayList(renderer->finishRecording()); delete renderer; } @@ -67,7 +75,7 @@ public: frameCount = fc; } } - virtual void createContent(int width, int height, DisplayListCanvas* renderer) = 0; + virtual void createContent(int width, int height, TestCanvas* renderer) = 0; virtual void doFrame(int frameNr) = 0; template <class T> @@ -102,7 +110,7 @@ public: android::uirenderer::Rect DUMMY; - DisplayListCanvas* renderer = startRecording(rootNode); + TestCanvas* renderer = startRecording(rootNode); animation.createContent(width, height, renderer); endRecording(renderer, rootNode); @@ -132,7 +140,7 @@ public: class ShadowGridAnimation : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* renderer) override { renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); renderer->insertReorderBarrier(true); @@ -163,7 +171,7 @@ private: node->mutateStagingProperties().mutableOutline().setShouldClip(true); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - DisplayListCanvas* renderer = startRecording(node.get()); + TestCanvas* renderer = startRecording(node.get()); renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); endRecording(renderer, node.get()); return node; @@ -179,7 +187,7 @@ static Benchmark _ShadowGrid(BenchmarkInfo{ class ShadowGrid2Animation : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* renderer) override { renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); renderer->insertReorderBarrier(true); @@ -210,7 +218,7 @@ private: node->mutateStagingProperties().mutableOutline().setShouldClip(true); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - DisplayListCanvas* renderer = startRecording(node.get()); + TestCanvas* renderer = startRecording(node.get()); renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); endRecording(renderer, node.get()); return node; @@ -226,7 +234,7 @@ static Benchmark _ShadowGrid2(BenchmarkInfo{ class RectGridAnimation : public TreeContentAnimation { public: sp<RenderNode> card; - void createContent(int width, int height, DisplayListCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* renderer) override { renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); renderer->insertReorderBarrier(true); @@ -247,7 +255,7 @@ private: node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - DisplayListCanvas* renderer = startRecording(node.get()); + TestCanvas* renderer = startRecording(node.get()); renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); SkRegion region; @@ -275,7 +283,7 @@ static Benchmark _RectGrid(BenchmarkInfo{ class OvalAnimation : public TreeContentAnimation { public: sp<RenderNode> card; - void createContent(int width, int height, DisplayListCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* renderer) override { renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); renderer->insertReorderBarrier(true); @@ -297,7 +305,7 @@ private: node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - DisplayListCanvas* renderer = startRecording(node.get()); + TestCanvas* renderer = startRecording(node.get()); SkPaint paint; paint.setAntiAlias(true); @@ -317,7 +325,7 @@ static Benchmark _Oval(BenchmarkInfo{ class PartialDamageTest : public TreeContentAnimation { public: std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { + void createContent(int width, int height, TestCanvas* renderer) override { static SkColor COLORS[] = { 0xFFF44336, 0xFF9C27B0, @@ -342,7 +350,7 @@ public: cards[0]->mutateStagingProperties().setTranslationY(curFrame); cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - DisplayListCanvas* renderer = startRecording(cards[0].get()); + TestCanvas* renderer = startRecording(cards[0].get()); renderer->drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0), SkXfermode::kSrcOver_Mode); endRecording(renderer, cards[0].get()); @@ -370,7 +378,7 @@ private: node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - DisplayListCanvas* renderer = startRecording(node.get()); + TestCanvas* renderer = startRecording(node.get()); renderer->drawColor(color, SkXfermode::kSrcOver_Mode); endRecording(renderer, node.get()); return node; @@ -383,3 +391,43 @@ static Benchmark _PartialDamage(BenchmarkInfo{ "EGL_KHR_partial_update is supported by the device & are enabled in hwui.", TreeContentAnimation::run<PartialDamageTest> }); + + +class SimpleRectGridAnimation : public TreeContentAnimation { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas* renderer) override { + SkPaint paint; + paint.setColor(0xFF00FFFF); + renderer->drawRect(0, 0, width, height, paint); + + card = createCard(40, 40, 200, 200); + renderer->drawRenderNode(card.get()); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + sp<RenderNode> node = new RenderNode(); + node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); + node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + TestCanvas* renderer = startRecording(node.get()); + SkPaint paint; + paint.setColor(0xFFFF00FF); + renderer->drawRect(0, 0, width, height, paint); + + endRecording(renderer, node.get()); + return node; + } +}; +static Benchmark _SimpleRectGrid(BenchmarkInfo{ + "simplerectgrid", + "A simple collection of rects. " + "Low CPU/GPU load.", + TreeContentAnimation::run<SimpleRectGridAnimation> +}); diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp new file mode 100644 index 000000000000..82aebeabe038 --- /dev/null +++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include <BakedOpState.h> +#include <RecordedOp.h> +#include <unit_tests/TestUtils.h> + +namespace android { +namespace uirenderer { + +TEST(ResolvedRenderState, resolution) { + Matrix4 identity; + identity.loadIdentity(); + + Matrix4 translate10x20; + translate10x20.loadTranslate(10, 20, 0); + + SkPaint paint; + RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, Rect(0, 0, 100, 200), &paint); + { + // recorded with transform, no parent transform + auto parentSnapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200)); + ResolvedRenderState state(*parentSnapshot, recordedOp); + EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20); + EXPECT_EQ(state.clipRect, Rect(0, 0, 100, 200)); + EXPECT_EQ(state.clippedBounds, Rect(40, 60, 100, 200)); // translated and also clipped + } + { + // recorded with transform and parent transform + auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(0, 0, 100, 200)); + ResolvedRenderState state(*parentSnapshot, recordedOp); + + Matrix4 expectedTranslate; + expectedTranslate.loadTranslate(20, 40, 0); + EXPECT_MATRIX_APPROX_EQ(state.transform, expectedTranslate); + + // intersection of parent & transformed child clip + EXPECT_EQ(state.clipRect, Rect(10, 20, 100, 200)); + + // translated and also clipped + EXPECT_EQ(state.clippedBounds, Rect(50, 80, 100, 200)); + } +} + +TEST(BakedOpState, constructAndReject) { + LinearAllocator allocator; + + Matrix4 identity; + identity.loadIdentity(); + + Matrix4 translate100x0; + translate100x0.loadTranslate(100, 0, 0); + + SkPaint paint; + { + RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, Rect(0, 0, 100, 200), &paint); + auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200)); + BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, rejectOp); + + EXPECT_EQ(bakedOp, nullptr); // rejected by clip, so not constructed + EXPECT_LE(allocator.usedSize(), 8u); // no significant allocation space used for rejected op + } + { + RectOp successOp(Rect(30, 40, 100, 200), identity, Rect(0, 0, 100, 200), &paint); + auto snapshot = TestUtils::makeSnapshot(identity, Rect(0, 0, 100, 200)); + BakedOpState* bakedOp = BakedOpState::tryConstruct(allocator, *snapshot, successOp); + + EXPECT_NE(bakedOp, nullptr); // NOT rejected by clip, so will be constructed + EXPECT_GT(allocator.usedSize(), 64u); // relatively large alloc for non-rejected op + } +} + +#define UNSUPPORTED_OP(Info, Type) \ + static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); } + +class Info { +public: + int index = 0; +}; + +} +} diff --git a/libs/hwui/unit_tests/ClipAreaTests.cpp b/libs/hwui/unit_tests/ClipAreaTests.cpp index 0c5e5e715dea..d6192df08bc3 100644 --- a/libs/hwui/unit_tests/ClipAreaTests.cpp +++ b/libs/hwui/unit_tests/ClipAreaTests.cpp @@ -101,10 +101,9 @@ TEST(ClipArea, paths) { EXPECT_FALSE(area.isEmpty()); EXPECT_FALSE(area.isSimple()); EXPECT_FALSE(area.isRectangleList()); + Rect clipRect(area.getClipRect()); - clipRect.dump("clipRect"); Rect expected(0, 0, r * 2, r * 2); - expected.dump("expected"); EXPECT_EQ(expected, clipRect); SkRegion clipRegion(area.getClipRegion()); auto skRect(clipRegion.getBounds()); diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp new file mode 100644 index 000000000000..fcaea1e36a69 --- /dev/null +++ b/libs/hwui/unit_tests/OpReordererTests.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include <BakedOpState.h> +#include <OpReorderer.h> +#include <RecordedOp.h> +#include <RecordingCanvas.h> +#include <unit_tests/TestUtils.h> + +#include <unordered_map> + +namespace android { +namespace uirenderer { + +#define UNSUPPORTED_OP(Info, Type) \ + static void on##Type(Info*, const Type&, const BakedOpState&) { FAIL(); } + +class Info { +public: + int index = 0; +}; + +class SimpleReceiver { +public: + static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) { + EXPECT_EQ(1, info->index++); + } + static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) { + EXPECT_EQ(0, info->index++); + } + UNSUPPORTED_OP(Info, RenderNodeOp) + UNSUPPORTED_OP(Info, SimpleRectsOp) + static void startFrame(Info& info) {} + static void endFrame(Info& info) {} +}; +TEST(OpReorderer, simple) { + auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + SkBitmap bitmap; + bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25)); + + canvas.drawRect(0, 0, 100, 200, SkPaint()); + canvas.drawBitmap(bitmap, 10, 10, nullptr); + }); + + OpReorderer reorderer; + reorderer.defer(200, 200, dld->getChunks(), dld->getOps()); + + Info info; + reorderer.replayBakedOps<SimpleReceiver>(&info); +} + + +static int SIMPLE_BATCHING_LOOPS = 5; +class SimpleBatchingReceiver { +public: + static void onBitmapOp(Info* info, const BitmapOp& op, const BakedOpState& state) { + EXPECT_TRUE(info->index++ >= SIMPLE_BATCHING_LOOPS); + } + static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) { + EXPECT_TRUE(info->index++ < SIMPLE_BATCHING_LOOPS); + } + UNSUPPORTED_OP(Info, RenderNodeOp) + UNSUPPORTED_OP(Info, SimpleRectsOp) + static void startFrame(Info& info) {} + static void endFrame(Info& info) {} +}; +TEST(OpReorderer, simpleBatching) { + auto dld = TestUtils::createDLD<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + SkBitmap bitmap; + bitmap.setInfo(SkImageInfo::MakeUnknown(10, 10)); + + // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects. + // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group. + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + for (int i = 0; i < SIMPLE_BATCHING_LOOPS; i++) { + canvas.translate(0, 10); + canvas.drawRect(0, 0, 10, 10, SkPaint()); + canvas.drawBitmap(bitmap, 5, 0, nullptr); + } + canvas.restore(); + }); + + OpReorderer reorderer; + reorderer.defer(200, 200, dld->getChunks(), dld->getOps()); + + Info info; + reorderer.replayBakedOps<SimpleBatchingReceiver>(&info); + EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, info.index); // 2 x loops ops, because no merging (TODO: force no merging) +} + +class RenderNodeReceiver { +public: + UNSUPPORTED_OP(Info, BitmapOp) + static void onRectOp(Info* info, const RectOp& op, const BakedOpState& state) { + switch(info->index++) { + case 0: + EXPECT_EQ(Rect(0, 0, 200, 200), state.computedState.clippedBounds); + EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); + break; + case 1: + EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds); + EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); + break; + default: + FAIL(); + } + } + UNSUPPORTED_OP(Info, RenderNodeOp) + UNSUPPORTED_OP(Info, SimpleRectsOp) + static void startFrame(Info& info) {} + static void endFrame(Info& info) {} +}; +TEST(OpReorderer, renderNode) { + sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + + RenderNode* childPtr = child.get(); + sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorDKGRAY); + canvas.drawRect(0, 0, 200, 200, paint); + + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.translate(40, 40); + canvas.drawRenderNode(childPtr); + canvas.restore(); + }); + + TestUtils::syncNodePropertiesAndDisplayList(child); + TestUtils::syncNodePropertiesAndDisplayList(parent); + + std::vector< sp<RenderNode> > nodes; + nodes.push_back(parent.get()); + + OpReorderer reorderer; + reorderer.defer(200, 200, nodes); + + Info info; + reorderer.replayBakedOps<RenderNodeReceiver>(&info); +} + +} +} diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp new file mode 100644 index 000000000000..c8138338a20e --- /dev/null +++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include <RecordedOp.h> +#include <RecordingCanvas.h> +#include <unit_tests/TestUtils.h> + +namespace android { +namespace uirenderer { + +static void playbackOps(const std::vector<DisplayListData::Chunk>& chunks, + const std::vector<RecordedOp*>& ops, std::function<void(const RecordedOp&)> opReciever) { + for (const DisplayListData::Chunk& chunk : chunks) { + for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { + opReciever(*ops[opIndex]); + } + } +} + +TEST(RecordingCanvas, emptyPlayback) { + auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.restore(); + }); + playbackOps(dld->getChunks(), dld->getOps(), [](const RecordedOp& op) { FAIL(); }); +} + +TEST(RecordingCanvas, testSimpleRectRecord) { + auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + canvas.drawRect(10, 20, 90, 180, SkPaint()); + }); + + int count = 0; + playbackOps(dld->getChunks(), dld->getOps(), [&count](const RecordedOp& op) { + count++; + ASSERT_EQ(RecordedOpId::RectOp, op.opId); + ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect); + ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds); + }); + ASSERT_EQ(1, count); // only one observed +} + +TEST(RecordingCanvas, backgroundAndImage) { + auto dld = TestUtils::createDLD<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + SkBitmap bitmap; + bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25)); + SkPaint paint; + paint.setColor(SK_ColorBLUE); + + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + { + // a background! + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.drawRect(0, 0, 100, 200, paint); + canvas.restore(); + } + { + // an image! + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.translate(25, 25); + canvas.scale(2, 2); + canvas.drawBitmap(bitmap, 0, 0, nullptr); + canvas.restore(); + } + canvas.restore(); + }); + + int count = 0; + playbackOps(dld->getChunks(), dld->getOps(), [&count](const RecordedOp& op) { + if (count == 0) { + ASSERT_EQ(RecordedOpId::RectOp, op.opId); + ASSERT_NE(nullptr, op.paint); + EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); + EXPECT_EQ(Rect(0, 0, 100, 200), op.unmappedBounds); + EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect); + + Matrix4 expectedMatrix; + expectedMatrix.loadIdentity(); + EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); + } else { + ASSERT_EQ(RecordedOpId::BitmapOp, op.opId); + EXPECT_EQ(nullptr, op.paint); + EXPECT_EQ(Rect(0, 0, 25, 25), op.unmappedBounds); + EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect); + + Matrix4 expectedMatrix; + expectedMatrix.loadTranslate(25, 25, 0); + expectedMatrix.scale(2, 2, 1); + EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); + } + count++; + }); + ASSERT_EQ(2, count); // two draws observed +} + +} +} diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/unit_tests/TestUtils.h new file mode 100644 index 000000000000..257dd2806d38 --- /dev/null +++ b/libs/hwui/unit_tests/TestUtils.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 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 TEST_UTILS_H +#define TEST_UTILS_H + +#include <Matrix.h> +#include <Snapshot.h> +#include <RenderNode.h> + +#include <memory> + +namespace android { +namespace uirenderer { + +#define EXPECT_MATRIX_APPROX_EQ(a, b) \ + EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b)) + +class TestUtils { +public: + static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) { + for (int i = 0; i < 16; i++) { + if (!MathUtils::areEqual(a[i], b[i])) { + return false; + } + } + return true; + } + + static std::unique_ptr<Snapshot> makeSnapshot(const Matrix4& transform, const Rect& clip) { + std::unique_ptr<Snapshot> snapshot(new Snapshot()); + snapshot->clip(clip.left, clip.top, clip.right, clip.bottom, SkRegion::kReplace_Op); + *(snapshot->transform) = transform; + return snapshot; + } + + template<class CanvasType> + static std::unique_ptr<DisplayListData> createDLD(int width, int height, + std::function<void(CanvasType& canvas)> canvasCallback) { + CanvasType canvas(width, height); + canvasCallback(canvas); + return std::unique_ptr<DisplayListData>(canvas.finishRecording()); + } + + template<class CanvasType> + static sp<RenderNode> createNode(int left, int top, int right, int bottom, + std::function<void(CanvasType& canvas)> canvasCallback) { + sp<RenderNode> node = new RenderNode(); + node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom); + node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + CanvasType canvas( + node->stagingProperties().getWidth(), node->stagingProperties().getHeight()); + canvasCallback(canvas); + node->setStagingDisplayList(canvas.finishRecording()); + return node; + } + + static void syncNodePropertiesAndDisplayList(sp<RenderNode>& node) { + node->syncProperties(); + node->syncDisplayList(); + } +}; // class TestUtils + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* TEST_UTILS_H */ diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h index d00236ed955a..db537130e12e 100644 --- a/libs/hwui/utils/PaintUtils.h +++ b/libs/hwui/utils/PaintUtils.h @@ -20,6 +20,7 @@ #include <SkColorFilter.h> #include <SkDrawLooper.h> +#include <SkShader.h> #include <SkXfermode.h> namespace android { diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 11527378f586..0f86bc614b2b 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -78,6 +78,7 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mLocked.pointerAlpha = 0.0f; // pointer is initially faded mLocked.pointerSprite = mSpriteController->createSprite(); mLocked.pointerIconChanged = false; + mLocked.requestedPointerShape = 0; mLocked.buttonState = 0; @@ -231,6 +232,10 @@ void PointerController::unfade(Transition transition) { void PointerController::setPresentation(Presentation presentation) { AutoMutex _l(mLock); + if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) { + mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources); + } + if (mLocked.presentation != presentation) { mLocked.presentation = presentation; mLocked.presentationChanged = true; @@ -391,6 +396,15 @@ void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_ updatePointerLocked(); } +void PointerController::updatePointerShape(int iconId) { + AutoMutex _l(mLock); + if (mLocked.requestedPointerShape != iconId) { + mLocked.requestedPointerShape = iconId; + mLocked.presentationChanged = true; + updatePointerLocked(); + } +} + void PointerController::setPointerIcon(const SpriteIcon& icon) { AutoMutex _l(mLock); @@ -497,8 +511,22 @@ void PointerController::updatePointerLocked() { } if (mLocked.pointerIconChanged || mLocked.presentationChanged) { - mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER - ? mLocked.pointerIcon : mResources.spotAnchor); + if (mLocked.presentation == PRESENTATION_POINTER) { + if (mLocked.requestedPointerShape == 0) { + mLocked.pointerSprite->setIcon(mLocked.pointerIcon); + } else { + std::map<int, SpriteIcon>::const_iterator iter = + mLocked.additionalMouseResources.find(mLocked.requestedPointerShape); + if (iter != mLocked.additionalMouseResources.end()) { + mLocked.pointerSprite->setIcon(iter->second); + } else { + ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerShape); + mLocked.pointerSprite->setIcon(mLocked.pointerIcon); + } + } + } else { + mLocked.pointerSprite->setIcon(mResources.spotAnchor); + } mLocked.pointerIconChanged = false; mLocked.presentationChanged = false; } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index b9e4ce7e7ed0..308ff1242064 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -19,6 +19,8 @@ #include "SpriteController.h" +#include <map> + #include <ui/DisplayInfo.h> #include <input/Input.h> #include <inputflinger/PointerControllerInterface.h> @@ -40,7 +42,6 @@ struct PointerResources { SpriteIcon spotAnchor; }; - /* * Pointer controller policy interface. * @@ -57,6 +58,7 @@ protected: public: virtual void loadPointerResources(PointerResources* outResources) = 0; + virtual void loadAdditionalMouseResources(std::map<int, SpriteIcon>* outResources) = 0; }; @@ -93,6 +95,7 @@ public: const uint32_t* spotIdToIndex, BitSet32 spotIdBits); virtual void clearSpots(); + void updatePointerShape(int iconId); void setDisplayViewport(int32_t width, int32_t height, int32_t orientation); void setPointerIcon(const SpriteIcon& icon); void setInactivityTimeout(InactivityTimeout inactivityTimeout); @@ -155,6 +158,10 @@ private: SpriteIcon pointerIcon; bool pointerIconChanged; + std::map<int, SpriteIcon> additionalMouseResources; + + int32_t requestedPointerShape; + int32_t buttonState; Vector<Spot*> spots; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index e562682551e4..8e98f106e05d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Animatable; @@ -325,6 +326,7 @@ public abstract class QSTile<TState extends State> implements Listenable { public interface Host { void startActivityDismissingKeyguard(Intent intent); + void startActivityDismissingKeyguard(PendingIntent intent); void warn(String message, Throwable t); void collapsePanels(); Looper getLooper(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java index 3d0dc7b5c1a2..c7f2284c07a6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java @@ -99,7 +99,7 @@ public class IntentTile extends QSTile<QSTile.State> { try { if (pi != null) { if (pi.isActivity()) { - getHost().startActivityDismissingKeyguard(pi.getIntent()); + getHost().startActivityDismissingKeyguard(pi); } else { pi.send(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index aaed735990db..893866925474 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -1617,6 +1617,59 @@ public abstract class BaseStatusBar extends SystemUI implements return null; } + public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { + if (!isDeviceProvisioned()) return; + + final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); + final boolean afterKeyguardGone = intent.isActivity() + && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), + mCurrentUserId); + dismissKeyguardThenExecute(new OnDismissAction() { + public boolean onDismiss() { + new Thread() { + @Override + public void run() { + try { + if (keyguardShowing && !afterKeyguardGone) { + ActivityManagerNative.getDefault() + .keyguardWaitingForActivityDrawn(); + } + + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManagerNative.getDefault().resumeAppSwitches(); + } catch (RemoteException e) { + } + + try { + intent.send(); + } catch (PendingIntent.CanceledException e) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending intent failed: " + e); + + // TODO: Dismiss Keyguard. + } + if (intent.isActivity()) { + mAssistManager.hideAssist(); + overrideActivityPendingAppTransition(keyguardShowing + && !afterKeyguardGone); + } + } + }.start(); + + // close the shade if it was open + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, + true /* force */, true /* delayed */); + visibilityChanged(false); + + return true; + } + }, afterKeyguardGone); + } + private final class NotificationClicker implements View.OnClickListener { public void onClick(final View v) { if (!(v instanceof ExpandableNotificationRow)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java index 9ef320bc3fd9..8f689c6c00b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import android.app.PendingIntent; import android.content.Intent; /** @@ -24,6 +25,7 @@ import android.content.Intent; * Keyguard. */ public interface ActivityStarter { + void startPendingIntentDismissingKeyguard(PendingIntent intent); void startActivity(Intent intent, boolean dismissShade); void startActivity(Intent intent, boolean dismissShade, Callback callback); void preventNextAnimation(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 1c1a1d76fd4a..cfde7913613f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -3261,6 +3261,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return !isDeviceProvisioned() || (mDisabled1 & StatusBarManager.DISABLE_SEARCH) != 0; } + public void postStartActivityDismissingKeyguard(final PendingIntent intent) { + mHandler.post(new Runnable() { + @Override + public void run() { + startPendingIntentDismissingKeyguard(intent); + } + }); + } + public void postStartActivityDismissingKeyguard(final Intent intent, int delay) { mHandler.postDelayed(new Runnable() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 6906a52138c7..f8ddc7332c1f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -121,6 +122,11 @@ public class QSTileHost implements QSTile.Host, Tunable { } @Override + public void startActivityDismissingKeyguard(PendingIntent intent) { + mStatusBar.postStartActivityDismissingKeyguard(intent); + } + + @Override public void warn(String message, Throwable t) { // already logged } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java index 65d84e287159..6cda561bd08f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java @@ -554,8 +554,8 @@ public class StatusBarHeaderView extends BaseStatusBarHeader implements View.OnC startBatteryActivity(); } else if (v == mAlarmStatus && mNextAlarm != null) { PendingIntent showIntent = mNextAlarm.getShowIntent(); - if (showIntent != null && showIntent.isActivity()) { - mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */); + if (showIntent != null) { + mActivityStarter.startPendingIntentDismissingKeyguard(showIntent); } } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 5a136729291a..5b40375007c1 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -206,6 +206,7 @@ public class InputManagerService extends IInputManager.Stub private static native void nativeReloadDeviceAliases(long ptr); private static native String nativeDump(long ptr); private static native void nativeMonitor(long ptr); + private static native void nativeSetPointerIconShape(long ptr, int iconId); // Input event injection constants defined in InputDispatcher.h. private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0; @@ -1407,6 +1408,12 @@ public class InputManagerService extends IInputManager.Stub } } + // Binder call + @Override + public void setPointerIconShape(int iconId) { + nativeSetPointerIconShape(mPtr, iconId); + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 1d4f047ff605..8cb0a1310ec5 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -199,6 +199,7 @@ public: void setShowTouches(bool enabled); void setInteractive(bool interactive); void reloadCalibration(); + void setPointerIconShape(int32_t iconId); /* --- InputReaderPolicyInterface implementation --- */ @@ -237,6 +238,7 @@ public: /* --- PointerControllerPolicyInterface implementation --- */ virtual void loadPointerResources(PointerResources* outResources); + virtual void loadAdditionalMouseResources(std::map<int, SpriteIcon>* outResources); private: sp<InputManager> mInputManager; @@ -779,6 +781,15 @@ void NativeInputManager::reloadCalibration() { InputReaderConfiguration::CHANGE_TOUCH_AFFINE_TRANSFORMATION); } +void NativeInputManager::setPointerIconShape(int32_t iconId) { + AutoMutex _l(mLock); + sp<PointerController> controller = mLocked.pointerController.promote(); + if (controller != NULL) { + // Use 0 (the default icon) for ARROW. + controller->updatePointerShape((iconId == POINTER_ICON_STYLE_ARROW) ? 0 : iconId); + } +} + TouchAffineTransformation NativeInputManager::getTouchAffineTransformation( JNIEnv *env, jfloatArray matrixArr) { ScopedFloatArrayRO matrix(env, matrixArr); @@ -1029,6 +1040,15 @@ void NativeInputManager::loadPointerResources(PointerResources* outResources) { &outResources->spotAnchor); } +void NativeInputManager::loadAdditionalMouseResources(std::map<int, SpriteIcon>* outResources) { + JNIEnv* env = jniEnv(); + + for (int iconId = POINTER_ICON_STYLE_CONTEXT_MENU; iconId <= POINTER_ICON_STYLE_GRABBING; + ++iconId) { + loadSystemIconAsSprite(env, mContextObj, iconId, &((*outResources)[iconId])); + } +} + // ---------------------------------------------------------------------------- @@ -1367,6 +1387,11 @@ static void nativeMonitor(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { im->getInputManager()->getDispatcher()->monitor(); } +static void nativeSetPointerIconShape(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint iconId) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + im->setPointerIconShape(iconId); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -1425,6 +1450,8 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*) nativeDump }, { "nativeMonitor", "(J)V", (void*) nativeMonitor }, + { "nativeSetPointerIconShape", "(JI)V", + (void*) nativeSetPointerIconShape }, }; #define FIND_CLASS(var, className) \ diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java index 3ca0c84543d3..31d859f654ba 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java @@ -87,6 +87,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // This is an indirect indication of the microphone being open in some other application. private boolean mServiceDisabled = false; private boolean mStarted = false; + private boolean mRecognitionAborted = false; private PowerSaveModeListener mPowerSaveModeListener; SoundTriggerHelper(Context context) { @@ -386,8 +387,9 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { private void onRecognitionAbortLocked() { Slog.w(TAG, "Recognition aborted"); - // No-op - // This is handled via service state changes instead. + // If abort has been called, the hardware has already stopped recognition, so we shouldn't + // call it again when we process the state change. + mRecognitionAborted = true; } private void onRecognitionFailureLocked() { @@ -490,8 +492,13 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } return status; } else { - // Stop recognition. - int status = mModule.stopRecognition(mCurrentSoundModelHandle); + // Stop recognition (only if we haven't been aborted). + int status = STATUS_OK; + if (!mRecognitionAborted) { + status = mModule.stopRecognition(mCurrentSoundModelHandle); + } else { + mRecognitionAborted = false; + } if (status != SoundTrigger.STATUS_OK) { Slog.w(TAG, "stopRecognition call failed with " + status); if (notify) { |