Effect: Avoid allocating bitmaps and textures in Overlays.
In `TextOverlay` and `DrawableOverlay`, treat `Bitmap` as a buffer, where we allocate it rarely and reuse it as long as possible before making a new one. In `BitmapOverlay`, avoid allocating GL textures too often as well. Strongly reduces allocations and memory usage growth (saving ~100-150 MB on 4k60fps at high end), at the cost of more code complexity and low-end using 70MB more, on 1/1 comparisons. PiperOrigin-RevId: 585990602
This commit is contained in:
parent
eb01c3f440
commit
e5ef31b277
@ -571,20 +571,15 @@ public final class GlUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates a new texture, initialized with the {@link Bitmap bitmap} data.
|
||||
*
|
||||
* <p>The created texture will have the same size as the specified {@link Bitmap}.
|
||||
* Allocates a new texture, initialized with the {@link Bitmap bitmap} data and size.
|
||||
*
|
||||
* @param bitmap The {@link Bitmap} for which the texture is created.
|
||||
* @return The texture identifier for the newly-allocated texture.
|
||||
* @throws GlException If the texture allocation fails.
|
||||
*/
|
||||
public static int createTexture(Bitmap bitmap) throws GlException {
|
||||
assertValidTextureSize(bitmap.getWidth(), bitmap.getHeight());
|
||||
int texId = generateTexture();
|
||||
bindTexture(GLES20.GL_TEXTURE_2D, texId);
|
||||
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, bitmap, /* border= */ 0);
|
||||
checkGlError();
|
||||
setTexture(texId, bitmap);
|
||||
return texId;
|
||||
}
|
||||
|
||||
@ -642,14 +637,22 @@ public final class GlUtil {
|
||||
return texId;
|
||||
}
|
||||
|
||||
/** Returns a new GL texture identifier. */
|
||||
private static int generateTexture() throws GlException {
|
||||
/** Returns a new, unbound GL texture identifier. */
|
||||
public static int generateTexture() throws GlException {
|
||||
int[] texId = new int[1];
|
||||
GLES20.glGenTextures(/* n= */ 1, texId, /* offset= */ 0);
|
||||
checkGlError();
|
||||
return texId[0];
|
||||
}
|
||||
|
||||
/** Sets the {@code texId} to contain the {@link Bitmap bitmap} data and size. */
|
||||
public static void setTexture(int texId, Bitmap bitmap) throws GlException {
|
||||
assertValidTextureSize(bitmap.getWidth(), bitmap.getHeight());
|
||||
bindTexture(GLES20.GL_TEXTURE_2D, texId);
|
||||
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, bitmap, /* border= */ 0);
|
||||
checkGlError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the texture of the given type with default configuration of GL_LINEAR filtering and
|
||||
* GL_CLAMP_TO_EDGE wrapping.
|
||||
|
@ -44,6 +44,7 @@ public abstract class BitmapOverlay extends TextureOverlay {
|
||||
private final float[] flipVerticallyMatrix;
|
||||
|
||||
private int lastTextureId;
|
||||
private boolean hasUpdatedBitmapReference;
|
||||
private @Nullable Bitmap lastBitmap;
|
||||
|
||||
/* package */ BitmapOverlay() {
|
||||
@ -74,16 +75,27 @@ public abstract class BitmapOverlay extends TextureOverlay {
|
||||
return new Size(checkNotNull(lastBitmap).getWidth(), checkNotNull(lastBitmap).getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the cached bitmap overlay should be updated using the latest {@linkplain
|
||||
* #getBitmap bitmap}.
|
||||
*/
|
||||
protected boolean shouldInvalidateCache() {
|
||||
// Bitmap#sameAs() is documented as a slow method. Therefore, only use a reference comparison by
|
||||
// default, instead of the deeper comparison done in sameAs.
|
||||
return hasUpdatedBitmapReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTextureId(long presentationTimeUs) throws VideoFrameProcessingException {
|
||||
Bitmap bitmap = getBitmap(presentationTimeUs);
|
||||
if (bitmap != lastBitmap) {
|
||||
try {
|
||||
hasUpdatedBitmapReference = bitmap != lastBitmap;
|
||||
if (shouldInvalidateCache()) {
|
||||
lastBitmap = bitmap;
|
||||
if (lastTextureId != C.INDEX_UNSET) {
|
||||
GlUtil.deleteTexture(lastTextureId);
|
||||
try {
|
||||
if (lastTextureId == C.INDEX_UNSET) {
|
||||
lastTextureId = GlUtil.generateTexture();
|
||||
}
|
||||
lastTextureId = GlUtil.createTexture(checkNotNull(lastBitmap));
|
||||
GlUtil.setTexture(lastTextureId, bitmap);
|
||||
} catch (GlUtil.GlException e) {
|
||||
throw new VideoFrameProcessingException(e);
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@ -31,6 +33,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
*/
|
||||
@UnstableApi
|
||||
public abstract class DrawableOverlay extends BitmapOverlay {
|
||||
private boolean hasUpdatedDrawable;
|
||||
private @MonotonicNonNull Bitmap lastBitmap;
|
||||
private @MonotonicNonNull Drawable lastDrawable;
|
||||
|
||||
@ -44,19 +47,34 @@ public abstract class DrawableOverlay extends BitmapOverlay {
|
||||
*/
|
||||
public abstract Drawable getDrawable(long presentationTimeUs);
|
||||
|
||||
/**
|
||||
* Returns whether the cached drawable overlay should be updated using the latest {@linkplain
|
||||
* #getDrawable drawable}
|
||||
*/
|
||||
@Override
|
||||
protected final boolean shouldInvalidateCache() {
|
||||
return hasUpdatedDrawable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getBitmap(long presentationTimeUs) {
|
||||
Drawable overlayDrawable = getDrawable(presentationTimeUs);
|
||||
// TODO(b/227625365): Drawable doesn't implement the equals method, so investigate other methods
|
||||
// of detecting the need to redraw the bitmap.
|
||||
if (!overlayDrawable.equals(lastDrawable)) {
|
||||
hasUpdatedDrawable = !overlayDrawable.equals(lastDrawable);
|
||||
if (shouldInvalidateCache()) {
|
||||
lastDrawable = overlayDrawable;
|
||||
if (lastBitmap == null
|
||||
|| lastBitmap.getWidth() != lastDrawable.getIntrinsicWidth()
|
||||
|| lastBitmap.getHeight() != lastDrawable.getIntrinsicHeight()) {
|
||||
lastBitmap =
|
||||
Bitmap.createBitmap(
|
||||
lastDrawable.getIntrinsicWidth(),
|
||||
lastDrawable.getIntrinsicHeight(),
|
||||
Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
Canvas canvas = new Canvas(lastBitmap);
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
lastDrawable.draw(canvas);
|
||||
}
|
||||
return checkNotNull(lastBitmap);
|
||||
|
@ -22,6 +22,8 @@ import static java.lang.Math.ceil;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.text.Layout;
|
||||
import android.text.SpannableString;
|
||||
import android.text.StaticLayout;
|
||||
@ -78,6 +80,8 @@ public abstract class TextOverlay extends BitmapOverlay {
|
||||
|
||||
public static final int TEXT_SIZE_PIXELS = 100;
|
||||
|
||||
private boolean hasUpdatedText;
|
||||
|
||||
private @MonotonicNonNull Bitmap lastBitmap;
|
||||
private @MonotonicNonNull SpannableString lastText;
|
||||
|
||||
@ -88,19 +92,34 @@ public abstract class TextOverlay extends BitmapOverlay {
|
||||
*/
|
||||
public abstract SpannableString getText(long presentationTimeUs);
|
||||
|
||||
/**
|
||||
* Returns whether the cached text overlay should be updated using the latest {@linkplain #getText
|
||||
* text}
|
||||
*/
|
||||
@Override
|
||||
protected final boolean shouldInvalidateCache() {
|
||||
return hasUpdatedText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap getBitmap(long presentationTimeUs) {
|
||||
SpannableString overlayText = getText(presentationTimeUs);
|
||||
if (!overlayText.equals(lastText)) {
|
||||
hasUpdatedText = !overlayText.equals(lastText);
|
||||
if (shouldInvalidateCache()) {
|
||||
lastText = overlayText;
|
||||
TextPaint textPaint = new TextPaint();
|
||||
textPaint.setTextSize(TEXT_SIZE_PIXELS);
|
||||
StaticLayout staticLayout =
|
||||
createStaticLayout(overlayText, textPaint, getSpannedTextWidth(overlayText, textPaint));
|
||||
if (lastBitmap == null
|
||||
|| lastBitmap.getWidth() != staticLayout.getWidth()
|
||||
|| lastBitmap.getHeight() != staticLayout.getHeight()) {
|
||||
lastBitmap =
|
||||
Bitmap.createBitmap(
|
||||
staticLayout.getWidth(), staticLayout.getHeight(), Bitmap.Config.ARGB_8888);
|
||||
}
|
||||
Canvas canvas = new Canvas(checkNotNull(lastBitmap));
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||
staticLayout.draw(canvas);
|
||||
}
|
||||
return checkNotNull(lastBitmap);
|
||||
|
Loading…
x
Reference in New Issue
Block a user