Support recording HW Bitmaps in Picture

Bug: 34881007
Test: bit CtsGraphicsTestCases:*
Test: bit CtsUiRenderingTestCases:.testclasses.HardwareBitmapTests

Change-Id: Ic751c356682ea3db17a1b031ec46106a1a2ab918
This commit is contained in:
Chris Craik
2017-05-04 15:09:33 -07:00
committed by John Reck
parent e53c1a1b6b
commit 716f38177e
4 changed files with 133 additions and 9 deletions

View File

@@ -526,10 +526,19 @@ public abstract class BaseCanvas {
return mAllowHwBitmapsInSwMode;
}
/**
* @hide
*/
protected void onHwBitmapInSwMode() {
if (!mAllowHwBitmapsInSwMode) {
throw new IllegalArgumentException(
"Software rendering doesn't support hardware bitmaps");
}
}
private void throwIfHwBitmapInSwMode(Bitmap bitmap) {
if (!mAllowHwBitmapsInSwMode && !isHardwareAccelerated()
&& bitmap.getConfig() == Bitmap.Config.HARDWARE) {
throw new IllegalStateException("Software rendering doesn't support hardware bitmaps");
if (!isHardwareAccelerated() && bitmap.getConfig() == Bitmap.Config.HARDWARE) {
onHwBitmapInSwMode();
}
}

View File

@@ -29,6 +29,10 @@ import android.os.StrictMode;
import android.os.Trace;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.DisplayListCanvas;
import android.view.RenderNode;
import android.view.ThreadedRenderer;
import libcore.util.NativeAllocationRegistry;
import java.io.OutputStream;
@@ -1170,6 +1174,82 @@ public final class Bitmap implements Parcelable {
return createBitmap(display, colors, 0, width, width, height, config);
}
/**
* Creates a Bitmap from the given {@link Picture} source of recorded drawing commands.
*
* Equivalent to calling {@link #createBitmap(Picture, int, int, Config)} with
* width and height the same as the Picture's width and height and a Config.HARDWARE
* config.
*
* @param source The recorded {@link Picture} of drawing commands that will be
* drawn into the returned Bitmap.
* @return An immutable bitmap with a HARDWARE config whose contents are created
* from the recorded drawing commands in the Picture source.
*/
public static @NonNull Bitmap createBitmap(@NonNull Picture source) {
return createBitmap(source, source.getWidth(), source.getHeight(), Config.HARDWARE);
}
/**
* Creates a Bitmap from the given {@link Picture} source of recorded drawing commands.
*
* The bitmap will be immutable with the given width and height. If the width and height
* are not the same as the Picture's width & height, the Picture will be scaled to
* fit the given width and height.
*
* @param source The recorded {@link Picture} of drawing commands that will be
* drawn into the returned Bitmap.
* @param width The width of the bitmap to create. The picture's width will be
* scaled to match if necessary.
* @param height The height of the bitmap to create. The picture's height will be
* scaled to match if necessary.
* @param config The {@link Config} of the created bitmap. If this is null then
* the bitmap will be {@link Config#HARDWARE}.
*
* @return An immutable bitmap with a HARDWARE config whose contents are created
* from the recorded drawing commands in the Picture source.
*/
public static @NonNull Bitmap createBitmap(@NonNull Picture source, int width, int height,
@NonNull Config config) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("width & height must be > 0");
}
if (config == null) {
throw new IllegalArgumentException("Config must not be null");
}
if (source.requiresHardwareAcceleration() && config != Config.HARDWARE) {
StrictMode.noteSlowCall("GPU readback");
}
if (config == Config.HARDWARE || source.requiresHardwareAcceleration()) {
final RenderNode node = RenderNode.create("BitmapTemporary", null);
node.setLeftTopRightBottom(0, 0, width, height);
node.setClipToBounds(false);
final DisplayListCanvas canvas = node.start(width, height);
if (source.getWidth() != width || source.getHeight() != height) {
canvas.scale(width / (float) source.getWidth(),
height / (float) source.getHeight());
}
canvas.drawPicture(source);
node.end(canvas);
Bitmap bitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
if (config != Config.HARDWARE) {
bitmap = bitmap.copy(config, false);
}
return bitmap;
} else {
Bitmap bitmap = Bitmap.createBitmap(width, height, config);
Canvas canvas = new Canvas(bitmap);
if (source.getWidth() != width || source.getHeight() != height) {
canvas.scale(width / (float) source.getWidth(),
height / (float) source.getHeight());
}
canvas.drawPicture(source);
canvas.setBitmap(null);
bitmap.makeImmutable();
return bitmap;
}
}
/**
* Returns an optional array of private data, used by the UI system for
* some bitmaps. Not intended to be called by applications.
@@ -1259,6 +1339,12 @@ public final class Bitmap implements Parcelable {
return mIsMutable;
}
/** @hide */
public final void makeImmutable() {
// todo mIsMutable = false;
// todo nMakeImmutable();
}
/**
* <p>Indicates whether pixels stored in this bitmaps are stored pre-multiplied.
* When a pixel is pre-multiplied, the RGB components have been multiplied by

View File

@@ -31,8 +31,9 @@ import java.io.OutputStream;
* be replayed on a hardware accelerated canvas.</p>
*/
public class Picture {
private Canvas mRecordingCanvas;
private PictureCanvas mRecordingCanvas;
private long mNativePicture;
private boolean mRequiresHwAcceleration;
private static final int WORKING_STREAM_STORAGE = 16 * 1024;
@@ -78,8 +79,12 @@ public class Picture {
* into it.
*/
public Canvas beginRecording(int width, int height) {
if (mRecordingCanvas != null) {
throw new IllegalStateException("Picture already recording, must call #endRecording()");
}
long ni = nativeBeginRecording(mNativePicture, width, height);
mRecordingCanvas = new RecordingCanvas(this, ni);
mRecordingCanvas = new PictureCanvas(this, ni);
mRequiresHwAcceleration = false;
return mRecordingCanvas;
}
@@ -91,6 +96,7 @@ public class Picture {
*/
public void endRecording() {
if (mRecordingCanvas != null) {
mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap;
mRecordingCanvas = null;
nativeEndRecording(mNativePicture);
}
@@ -112,6 +118,18 @@ public class Picture {
return nativeGetHeight(mNativePicture);
}
/**
* Indicates whether or not this Picture contains recorded commands that only work when
* drawn to a hardware-accelerated canvas. If this returns true then this Picture can only
* be drawn to another Picture or to a Canvas where canvas.isHardwareAccelerated() is true.
*
* @return true if the Picture can only be drawn to a hardware-accelerated canvas,
* false otherwise.
*/
public boolean requiresHardwareAcceleration() {
return mRequiresHwAcceleration;
}
/**
* Draw this picture on the canvas.
* <p>
@@ -129,6 +147,9 @@ public class Picture {
if (mRecordingCanvas != null) {
endRecording();
}
if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) {
canvas.onHwBitmapInSwMode();
}
nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture);
}
@@ -164,8 +185,7 @@ public class Picture {
if (stream == null) {
throw new NullPointerException();
}
if (!nativeWriteToStream(mNativePicture, stream,
new byte[WORKING_STREAM_STORAGE])) {
if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) {
throw new RuntimeException();
}
}
@@ -182,10 +202,11 @@ public class Picture {
OutputStream stream, byte[] storage);
private static native void nativeDestructor(long nativePicture);
private static class RecordingCanvas extends Canvas {
private static class PictureCanvas extends Canvas {
private final Picture mPicture;
boolean mHoldsHwBitmap;
public RecordingCanvas(Picture pict, long nativeCanvas) {
public PictureCanvas(Picture pict, long nativeCanvas) {
super(nativeCanvas);
mPicture = pict;
}
@@ -202,5 +223,10 @@ public class Picture {
}
super.drawPicture(picture);
}
@Override
protected void onHwBitmapInSwMode() {
mHoldsHwBitmap = true;
}
}
}