Commit 64786c6c by olly Committed by Oliver Woodman

Refactor GlViewGroup to ViewRenderer

GlViewGroup doesn't work properly as an actual ViewGroup. For example,
it doesn't support addition of child views after instantiation. This
change turns the class into a renderer, which is also more consistent
with other classes in the package.

PiperOrigin-RevId: 275322295
parent 0c0a67bb
......@@ -21,8 +21,10 @@ import android.graphics.SurfaceTexture;
import android.opengl.Matrix;
import android.os.Bundle;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import androidx.annotation.BinderThread;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
......@@ -30,9 +32,9 @@ import androidx.annotation.UiThread;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.spherical.GlViewGroup;
import com.google.android.exoplayer2.ui.spherical.PointerRenderer;
import com.google.android.exoplayer2.ui.spherical.SceneRenderer;
import com.google.android.exoplayer2.ui.spherical.ViewRenderer;
import com.google.android.exoplayer2.util.Assertions;
import com.google.vr.ndk.base.DaydreamApi;
import com.google.vr.sdk.base.AndroidCompat;
......@@ -71,18 +73,18 @@ public abstract class GvrPlayerActivity extends GvrActivity {
// If a custom theme isn't specified, the Context's theme is used. For VR Activities, this is
// the old Android default theme rather than a modern theme. Override this with a custom theme.
Context theme = new ContextThemeWrapper(this, R.style.ExoVrTheme);
GlViewGroup glViewGroup = new GlViewGroup(theme, R.layout.exo_vr_ui);
View viewGroup = LayoutInflater.from(theme).inflate(R.layout.exo_vr_ui, /* root= */ null);
playerControlView = Assertions.checkNotNull(glViewGroup.findViewById(R.id.controller));
ViewRenderer viewRenderer = new ViewRenderer(/* context= */ this, gvrView, viewGroup);
playerControlView = Assertions.checkNotNull(viewGroup.findViewById(R.id.controller));
playerControlView.setShowVrButton(true);
playerControlView.setVrButtonListener(v -> exit());
sceneRenderer = new SceneRenderer();
PointerRenderer pointerRenderer = new PointerRenderer();
Renderer renderer = new Renderer(sceneRenderer, pointerRenderer, glViewGroup);
Renderer renderer = new Renderer(sceneRenderer, pointerRenderer, viewRenderer);
// Attach glViewGroup to gvrView in order to properly handle UI events.
gvrView.addView(glViewGroup);
// Standard GvrView configuration
gvrView.setEGLConfigChooser(
8, 8, 8, 8, // RGBA bits.
......@@ -104,7 +106,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
new ControllerManager(/* context= */ this, new ControllerManagerEventListener());
Controller controller = controllerManager.getController();
ControllerEventListener controllerEventListener =
new ControllerEventListener(controller, pointerRenderer, glViewGroup);
new ControllerEventListener(controller, pointerRenderer, viewRenderer);
controller.setEventListener(controllerEventListener);
}
......@@ -231,14 +233,14 @@ public abstract class GvrPlayerActivity extends GvrActivity {
private final SceneRenderer sceneRenderer;
private final PointerRenderer pointerRenderer;
private final GlViewGroup glViewGroup;
private final ViewRenderer viewRenderer;
private final float[] viewProjectionMatrix;
public Renderer(
SceneRenderer sceneRenderer, PointerRenderer pointerRenderer, GlViewGroup glViewGroup) {
SceneRenderer sceneRenderer, PointerRenderer pointerRenderer, ViewRenderer viewRenderer) {
this.sceneRenderer = sceneRenderer;
this.pointerRenderer = pointerRenderer;
this.glViewGroup = glViewGroup;
this.viewRenderer = viewRenderer;
viewProjectionMatrix = new float[16];
}
......@@ -250,8 +252,8 @@ public abstract class GvrPlayerActivity extends GvrActivity {
Matrix.multiplyMM(
viewProjectionMatrix, 0, eye.getPerspective(Z_NEAR, Z_FAR), 0, eye.getEyeView(), 0);
sceneRenderer.drawFrame(viewProjectionMatrix, eye.getType() == Eye.Type.RIGHT);
if (glViewGroup.isVisible()) {
glViewGroup.getRenderer().draw(viewProjectionMatrix);
if (viewRenderer.isVisible()) {
viewRenderer.draw(viewProjectionMatrix);
pointerRenderer.draw(viewProjectionMatrix);
}
}
......@@ -262,7 +264,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override
public void onSurfaceCreated(EGLConfig config) {
onSurfaceTextureAvailable(sceneRenderer.init());
glViewGroup.getRenderer().init();
viewRenderer.init();
pointerRenderer.init();
}
......@@ -271,7 +273,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override
public void onRendererShutdown() {
glViewGroup.getRenderer().shutdown();
viewRenderer.shutdown();
pointerRenderer.shutdown();
sceneRenderer.shutdown();
}
......@@ -281,16 +283,16 @@ public abstract class GvrPlayerActivity extends GvrActivity {
private final Controller controller;
private final PointerRenderer pointerRenderer;
private final GlViewGroup glViewGroup;
private final ViewRenderer viewRenderer;
private final float[] controllerOrientationMatrix;
private boolean clickButtonDown;
private boolean appButtonDown;
public ControllerEventListener(
Controller controller, PointerRenderer pointerRenderer, GlViewGroup glViewGroup) {
Controller controller, PointerRenderer pointerRenderer, ViewRenderer viewRenderer) {
this.controller = controller;
this.pointerRenderer = pointerRenderer;
this.glViewGroup = glViewGroup;
this.viewRenderer = viewRenderer;
controllerOrientationMatrix = new float[16];
}
......@@ -319,7 +321,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
}
private void dispatchClick(int action, float yaw, float pitch) {
boolean clickedOnView = glViewGroup.simulateClick(action, yaw, pitch);
boolean clickedOnView = viewRenderer.simulateClick(action, yaw, pitch);
if (action == MotionEvent.ACTION_DOWN && !clickedOnView) {
togglePlayerControlVisibility();
}
......
......@@ -21,66 +21,60 @@ import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.google.android.exoplayer2.util.Assertions;
/** This View uses standard Android APIs to render its child Views to a texture. */
public final class GlViewGroup extends FrameLayout {
/** Renders a {@link View} on a quad and supports simulated clicks on the view. */
public final class ViewRenderer {
private final CanvasRenderer canvasRenderer;
private final View view;
private final InternalFrameLayout frameLayout;
/**
* @param context The Context the view is running in, through which it can access the current
* theme, resources, etc.
* @param layoutId ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>)
* @param context A context.
* @param parentView The parent view.
* @param view The view to render.
*/
public GlViewGroup(Context context, int layoutId) {
super(context);
public ViewRenderer(Context context, ViewGroup parentView, View view) {
this.canvasRenderer = new CanvasRenderer();
this.view = view;
// Wrap the view in an internal view that redirects rendering.
frameLayout = new InternalFrameLayout(context, view, canvasRenderer);
canvasRenderer.setSize(frameLayout.getMeasuredWidth(), frameLayout.getMeasuredHeight());
// The internal view must be added to the parent to ensure proper delivery of UI events.
parentView.addView(frameLayout);
}
/** Finishes constructing this object on the GL Thread. */
public void init() {
canvasRenderer.init();
}
LayoutInflater.from(context).inflate(layoutId, this);
/**
* Renders the view as a quad.
*
* @param viewProjectionMatrix Array of floats containing the quad's 4x4 perspective matrix in the
* {@link android.opengl.Matrix} format.
*/
public void draw(float[] viewProjectionMatrix) {
canvasRenderer.draw(viewProjectionMatrix);
}
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
Assertions.checkState(width > 0 && height > 0);
canvasRenderer.setSize(width, height);
setLayoutParams(new FrameLayout.LayoutParams(width, height));
/** Frees GL resources. */
public void shutdown() {
canvasRenderer.shutdown();
}
/** Returns whether the view is currently visible. */
@UiThread
public boolean isVisible() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
if (getChildAt(i).getVisibility() == VISIBLE) {
return true;
}
}
return false;
}
@Override
public void dispatchDraw(Canvas notUsed) {
Canvas glCanvas = canvasRenderer.lockCanvas();
if (glCanvas == null) {
// This happens if Android tries to draw this View before GL initialization completes. We need
// to retry until the draw call happens after GL invalidation.
postInvalidate();
return;
}
// Clear the canvas first.
glCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// Have Android render the child views.
super.dispatchDraw(glCanvas);
// Commit the changes.
canvasRenderer.unlockCanvasAndPost(glCanvas);
return view.getVisibility() == View.VISIBLE;
}
/**
......@@ -97,18 +91,47 @@ public final class GlViewGroup extends FrameLayout {
if (!isVisible()) {
return false;
}
PointF point = canvasRenderer.translateClick(yaw, pitch);
@Nullable PointF point = canvasRenderer.translateClick(yaw, pitch);
if (point == null) {
return false;
}
long now = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(now, now, action, point.x, point.y, /* metaState= */ 1);
dispatchTouchEvent(event);
frameLayout.dispatchTouchEvent(event);
return true;
}
@AnyThread
public CanvasRenderer getRenderer() {
return canvasRenderer;
private static final class InternalFrameLayout extends FrameLayout {
private final CanvasRenderer canvasRenderer;
public InternalFrameLayout(Context context, View wrappedView, CanvasRenderer canvasRenderer) {
super(context);
this.canvasRenderer = canvasRenderer;
addView(wrappedView);
measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
Assertions.checkState(width > 0 && height > 0);
setLayoutParams(new FrameLayout.LayoutParams(width, height));
}
@Override
public void dispatchDraw(Canvas notUsed) {
@Nullable Canvas glCanvas = canvasRenderer.lockCanvas();
if (glCanvas == null) {
// This happens if Android tries to draw this View before GL initialization completes. We
// need to retry until the draw call happens after GL invalidation.
postInvalidate();
return;
}
// Clear the canvas first.
glCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// Have Android render the child views.
super.dispatchDraw(glCanvas);
// Commit the changes.
canvasRenderer.unlockCanvasAndPost(glCanvas);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment