diff --git a/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java b/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java index d3c8fca714..1360abf4e0 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/GlUtil.java @@ -571,20 +571,15 @@ public final class GlUtil { } /** - * Allocates a new texture, initialized with the {@link Bitmap bitmap} data. - * - *

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. diff --git a/libraries/effect/src/main/java/androidx/media3/effect/BitmapOverlay.java b/libraries/effect/src/main/java/androidx/media3/effect/BitmapOverlay.java index d171b18349..af7ef92799 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/BitmapOverlay.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/BitmapOverlay.java @@ -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) { + hasUpdatedBitmapReference = bitmap != lastBitmap; + if (shouldInvalidateCache()) { + lastBitmap = bitmap; try { - lastBitmap = bitmap; - if (lastTextureId != C.INDEX_UNSET) { - GlUtil.deleteTexture(lastTextureId); + 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); } diff --git a/libraries/effect/src/main/java/androidx/media3/effect/DrawableOverlay.java b/libraries/effect/src/main/java/androidx/media3/effect/DrawableOverlay.java index 3e04777b47..3bdfd29097 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/DrawableOverlay.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/DrawableOverlay.java @@ -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; - lastBitmap = - Bitmap.createBitmap( - lastDrawable.getIntrinsicWidth(), - lastDrawable.getIntrinsicHeight(), - Bitmap.Config.ARGB_8888); + 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); diff --git a/libraries/effect/src/main/java/androidx/media3/effect/TextOverlay.java b/libraries/effect/src/main/java/androidx/media3/effect/TextOverlay.java index 3b8d17793d..50730ac9ac 100644 --- a/libraries/effect/src/main/java/androidx/media3/effect/TextOverlay.java +++ b/libraries/effect/src/main/java/androidx/media3/effect/TextOverlay.java @@ -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)); - lastBitmap = - Bitmap.createBitmap( - staticLayout.getWidth(), staticLayout.getHeight(), Bitmap.Config.ARGB_8888); + 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);