Add GlRect helper class

Adds a class that represents an image rectangle
in OpenGL coordinate convention.

android.graphics.Rect puts (0, 0) as the top-left corner:
it enforces `Rect.top <= Rect.bottom` and this matches
`android.graphics.Bitmap` coordinates: docs https://developer.android.com/reference/android/graphics/Rect

This is different from OpenGL coordinates where (0, 0) is
at the bottom-left corner. I.e. GlRect.bottom <= GlRect.top: docs https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glReadPixels.xhtml

The reason for this change is to allow a public API GlRect
getScaledRegion() which selects a region of pixels of a GL texture
to be copied to CPU memory.

PiperOrigin-RevId: 669231826
This commit is contained in:
dancho 2024-08-30 01:11:10 -07:00 committed by Copybara-Service
parent 388d1f17b9
commit dc9854cc5b
6 changed files with 63 additions and 24 deletions

View File

@ -0,0 +1,48 @@
/*
* Copyright 2024 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
*
* https://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.
*/
package androidx.media3.common.util;
import static androidx.media3.common.util.Assertions.checkArgument;
/**
* Represents a rectangle by the coordinates of its 4 edges (left, bottom, right, top).
*
* <p>Note that the right and top coordinates are exclusive.
*
* <p>This class represents coordinates in the OpenGL coordinate convention: {@code left <= right}
* and {@code bottom <= top}.
*/
@UnstableApi
public final class GlRect {
public int left;
public int bottom;
public int right;
public int top;
/** Creates an instance from (0, 0) to the specified width and height. */
public GlRect(int width, int height) {
this(/* left= */ 0, /* bottom= */ 0, width, height);
}
/** Creates an instance. */
public GlRect(int left, int bottom, int right, int top) {
checkArgument(left <= right && bottom <= top);
this.left = left;
this.bottom = bottom;
this.right = right;
this.top = top;
}
}

View File

@ -23,7 +23,6 @@ import static androidx.media3.common.util.Assertions.checkState;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
@ -837,7 +836,7 @@ public final class GlUtil {
* @param drawFboId The framebuffer object to draw into.
* @param drawRect The rectangular region of {@code drawFboId} to draw into.
*/
public static void blitFrameBuffer(int readFboId, Rect readRect, int drawFboId, Rect drawRect)
public static void blitFrameBuffer(int readFboId, GlRect readRect, int drawFboId, GlRect drawRect)
throws GlException {
int[] boundFramebuffer = new int[1];
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, boundFramebuffer, /* offset= */ 0);
@ -848,13 +847,13 @@ public final class GlUtil {
checkGlError();
GLES30.glBlitFramebuffer(
readRect.left,
readRect.top,
readRect.right,
readRect.bottom,
readRect.right,
readRect.top,
drawRect.left,
drawRect.top,
drawRect.right,
drawRect.bottom,
drawRect.right,
drawRect.top,
GLES30.GL_COLOR_BUFFER_BIT,
GLES30.GL_LINEAR);
checkGlError();

View File

@ -26,7 +26,6 @@ import static com.google.common.truth.Truth.assertThat;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.AbsoluteSizeSpan;
@ -34,6 +33,7 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.TypefaceSpan;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.util.Consumer;
import androidx.media3.common.util.GlRect;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util;
import androidx.media3.test.utils.TextureBitmapReader;
@ -140,12 +140,8 @@ public class ByteBufferGlEffectTest {
}
@Override
public Rect getScaledRegion(long presentationTimeUs) {
return new Rect(
/* left= */ 0,
/* top= */ 0,
/* right= */ INPUT_FRAME_WIDTH,
/* bottom= */ INPUT_FRAME_HEIGHT);
public GlRect getScaledRegion(long presentationTimeUs) {
return new GlRect(INPUT_FRAME_WIDTH, INPUT_FRAME_HEIGHT);
}
@Override

View File

@ -19,11 +19,11 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
import android.graphics.Rect;
import androidx.media3.common.C;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.GlRect;
import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util;
@ -107,8 +107,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
textureInfo.fboId,
processor.getScaledRegion(presentationTimeUs),
effectInputTexture.fboId,
new Rect(
/* left= */ 0, /* top= */ 0, effectInputTexture.width, effectInputTexture.height));
new GlRect(effectInputTexture.width, effectInputTexture.height));
TexturePixelBuffer texturePixelBuffer = new TexturePixelBuffer(effectInputTexture);
unmappedPixelBuffers.add(texturePixelBuffer);

View File

@ -20,10 +20,10 @@ import static androidx.media3.common.util.Assertions.checkArgument;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.opengl.GLES20;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.GlRect;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi;
import com.google.common.util.concurrent.ListenableFuture;
@ -143,9 +143,7 @@ import java.util.concurrent.Future;
* @return The rectangular region of the input image that will be scaled to fill the effect
* input image.
*/
// TODO: b/b/361286064 - This method misuses android.graphics.Rect for OpenGL coordinates.
// Implement a custom GlUtils.Rect to correctly label lower left corner as (0, 0).
Rect getScaledRegion(long presentationTimeUs);
GlRect getScaledRegion(long presentationTimeUs);
/**
* Processing the image data in the {@code image}.

View File

@ -18,13 +18,13 @@ package androidx.media3.effect;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState;
import android.graphics.Rect;
import androidx.annotation.CallSuper;
import androidx.annotation.IntRange;
import androidx.media3.common.C;
import androidx.media3.common.GlObjectsProvider;
import androidx.media3.common.GlTextureInfo;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.GlRect;
import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.UnstableApi;
import com.google.common.util.concurrent.Futures;
@ -213,10 +213,9 @@ import java.util.concurrent.TimeUnit;
checkState(inputTexture.fboId != C.INDEX_UNSET);
GlUtil.blitFrameBuffer(
inputTexture.fboId,
new Rect(/* left= */ 0, /* top= */ 0, /* right= */ inputWidth, /* bottom= */ inputHeight),
new GlRect(inputWidth, inputHeight),
outputTexture.fboId,
new Rect(
/* left= */ 0, /* top= */ 0, /* right= */ inputWidth, /* bottom= */ inputHeight));
new GlRect(inputWidth, inputHeight));
Future<T> task =
concurrentEffect.queueInputFrame(glObjectsProvider, outputTexture, presentationTimeUs);