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; ...@@ -21,8 +21,10 @@ import android.graphics.SurfaceTexture;
import android.opengl.Matrix; import android.opengl.Matrix;
import android.os.Bundle; import android.os.Bundle;
import android.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.Surface; import android.view.Surface;
import android.view.View;
import androidx.annotation.BinderThread; import androidx.annotation.BinderThread;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
...@@ -30,9 +32,9 @@ import androidx.annotation.UiThread; ...@@ -30,9 +32,9 @@ import androidx.annotation.UiThread;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ui.PlayerControlView; 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.PointerRenderer;
import com.google.android.exoplayer2.ui.spherical.SceneRenderer; 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.android.exoplayer2.util.Assertions;
import com.google.vr.ndk.base.DaydreamApi; import com.google.vr.ndk.base.DaydreamApi;
import com.google.vr.sdk.base.AndroidCompat; import com.google.vr.sdk.base.AndroidCompat;
...@@ -71,18 +73,18 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -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 // 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. // the old Android default theme rather than a modern theme. Override this with a custom theme.
Context theme = new ContextThemeWrapper(this, R.style.ExoVrTheme); 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.setShowVrButton(true);
playerControlView.setVrButtonListener(v -> exit()); playerControlView.setVrButtonListener(v -> exit());
sceneRenderer = new SceneRenderer(); sceneRenderer = new SceneRenderer();
PointerRenderer pointerRenderer = new PointerRenderer(); 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 // Standard GvrView configuration
gvrView.setEGLConfigChooser( gvrView.setEGLConfigChooser(
8, 8, 8, 8, // RGBA bits. 8, 8, 8, 8, // RGBA bits.
...@@ -104,7 +106,7 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -104,7 +106,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
new ControllerManager(/* context= */ this, new ControllerManagerEventListener()); new ControllerManager(/* context= */ this, new ControllerManagerEventListener());
Controller controller = controllerManager.getController(); Controller controller = controllerManager.getController();
ControllerEventListener controllerEventListener = ControllerEventListener controllerEventListener =
new ControllerEventListener(controller, pointerRenderer, glViewGroup); new ControllerEventListener(controller, pointerRenderer, viewRenderer);
controller.setEventListener(controllerEventListener); controller.setEventListener(controllerEventListener);
} }
...@@ -231,14 +233,14 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -231,14 +233,14 @@ public abstract class GvrPlayerActivity extends GvrActivity {
private final SceneRenderer sceneRenderer; private final SceneRenderer sceneRenderer;
private final PointerRenderer pointerRenderer; private final PointerRenderer pointerRenderer;
private final GlViewGroup glViewGroup; private final ViewRenderer viewRenderer;
private final float[] viewProjectionMatrix; private final float[] viewProjectionMatrix;
public Renderer( public Renderer(
SceneRenderer sceneRenderer, PointerRenderer pointerRenderer, GlViewGroup glViewGroup) { SceneRenderer sceneRenderer, PointerRenderer pointerRenderer, ViewRenderer viewRenderer) {
this.sceneRenderer = sceneRenderer; this.sceneRenderer = sceneRenderer;
this.pointerRenderer = pointerRenderer; this.pointerRenderer = pointerRenderer;
this.glViewGroup = glViewGroup; this.viewRenderer = viewRenderer;
viewProjectionMatrix = new float[16]; viewProjectionMatrix = new float[16];
} }
...@@ -250,8 +252,8 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -250,8 +252,8 @@ public abstract class GvrPlayerActivity extends GvrActivity {
Matrix.multiplyMM( Matrix.multiplyMM(
viewProjectionMatrix, 0, eye.getPerspective(Z_NEAR, Z_FAR), 0, eye.getEyeView(), 0); viewProjectionMatrix, 0, eye.getPerspective(Z_NEAR, Z_FAR), 0, eye.getEyeView(), 0);
sceneRenderer.drawFrame(viewProjectionMatrix, eye.getType() == Eye.Type.RIGHT); sceneRenderer.drawFrame(viewProjectionMatrix, eye.getType() == Eye.Type.RIGHT);
if (glViewGroup.isVisible()) { if (viewRenderer.isVisible()) {
glViewGroup.getRenderer().draw(viewProjectionMatrix); viewRenderer.draw(viewProjectionMatrix);
pointerRenderer.draw(viewProjectionMatrix); pointerRenderer.draw(viewProjectionMatrix);
} }
} }
...@@ -262,7 +264,7 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -262,7 +264,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override @Override
public void onSurfaceCreated(EGLConfig config) { public void onSurfaceCreated(EGLConfig config) {
onSurfaceTextureAvailable(sceneRenderer.init()); onSurfaceTextureAvailable(sceneRenderer.init());
glViewGroup.getRenderer().init(); viewRenderer.init();
pointerRenderer.init(); pointerRenderer.init();
} }
...@@ -271,7 +273,7 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -271,7 +273,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override @Override
public void onRendererShutdown() { public void onRendererShutdown() {
glViewGroup.getRenderer().shutdown(); viewRenderer.shutdown();
pointerRenderer.shutdown(); pointerRenderer.shutdown();
sceneRenderer.shutdown(); sceneRenderer.shutdown();
} }
...@@ -281,16 +283,16 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -281,16 +283,16 @@ public abstract class GvrPlayerActivity extends GvrActivity {
private final Controller controller; private final Controller controller;
private final PointerRenderer pointerRenderer; private final PointerRenderer pointerRenderer;
private final GlViewGroup glViewGroup; private final ViewRenderer viewRenderer;
private final float[] controllerOrientationMatrix; private final float[] controllerOrientationMatrix;
private boolean clickButtonDown; private boolean clickButtonDown;
private boolean appButtonDown; private boolean appButtonDown;
public ControllerEventListener( public ControllerEventListener(
Controller controller, PointerRenderer pointerRenderer, GlViewGroup glViewGroup) { Controller controller, PointerRenderer pointerRenderer, ViewRenderer viewRenderer) {
this.controller = controller; this.controller = controller;
this.pointerRenderer = pointerRenderer; this.pointerRenderer = pointerRenderer;
this.glViewGroup = glViewGroup; this.viewRenderer = viewRenderer;
controllerOrientationMatrix = new float[16]; controllerOrientationMatrix = new float[16];
} }
...@@ -319,7 +321,7 @@ public abstract class GvrPlayerActivity extends GvrActivity { ...@@ -319,7 +321,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
} }
private void dispatchClick(int action, float yaw, float pitch) { 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) { if (action == MotionEvent.ACTION_DOWN && !clickedOnView) {
togglePlayerControlVisibility(); togglePlayerControlVisibility();
} }
......
...@@ -21,66 +21,60 @@ import android.graphics.Color; ...@@ -21,66 +21,60 @@ import android.graphics.Color;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import androidx.annotation.AnyThread; import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
/** This View uses standard Android APIs to render its child Views to a texture. */ /** Renders a {@link View} on a quad and supports simulated clicks on the view. */
public final class GlViewGroup extends FrameLayout { public final class ViewRenderer {
private final CanvasRenderer canvasRenderer; 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 * @param context A context.
* theme, resources, etc. * @param parentView The parent view.
* @param layoutId ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>) * @param view The view to render.
*/ */
public GlViewGroup(Context context, int layoutId) { public ViewRenderer(Context context, ViewGroup parentView, View view) {
super(context);
this.canvasRenderer = new CanvasRenderer(); this.canvasRenderer = new CanvasRenderer();
this.view = view;
LayoutInflater.from(context).inflate(layoutId, this); // Wrap the view in an internal view that redirects rendering.
frameLayout = new InternalFrameLayout(context, view, canvasRenderer);
measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); canvasRenderer.setSize(frameLayout.getMeasuredWidth(), frameLayout.getMeasuredHeight());
int width = getMeasuredWidth(); // The internal view must be added to the parent to ensure proper delivery of UI events.
int height = getMeasuredHeight(); parentView.addView(frameLayout);
Assertions.checkState(width > 0 && height > 0);
canvasRenderer.setSize(width, height);
setLayoutParams(new FrameLayout.LayoutParams(width, height));
} }
/** Returns whether the view is currently visible. */ /** Finishes constructing this object on the GL Thread. */
@UiThread public void init() {
public boolean isVisible() { canvasRenderer.init();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
if (getChildAt(i).getVisibility() == VISIBLE) {
return true;
} }
}
return false; /**
* 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);
} }
@Override /** Frees GL resources. */
public void dispatchDraw(Canvas notUsed) { public void shutdown() {
Canvas glCanvas = canvasRenderer.lockCanvas(); canvasRenderer.shutdown();
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. /** Returns whether the view is currently visible. */
glCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); @UiThread
// Have Android render the child views. public boolean isVisible() {
super.dispatchDraw(glCanvas); return view.getVisibility() == View.VISIBLE;
// Commit the changes.
canvasRenderer.unlockCanvasAndPost(glCanvas);
} }
/** /**
...@@ -97,18 +91,47 @@ public final class GlViewGroup extends FrameLayout { ...@@ -97,18 +91,47 @@ public final class GlViewGroup extends FrameLayout {
if (!isVisible()) { if (!isVisible()) {
return false; return false;
} }
PointF point = canvasRenderer.translateClick(yaw, pitch); @Nullable PointF point = canvasRenderer.translateClick(yaw, pitch);
if (point == null) { if (point == null) {
return false; return false;
} }
long now = SystemClock.uptimeMillis(); long now = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(now, now, action, point.x, point.y, /* metaState= */ 1); MotionEvent event = MotionEvent.obtain(now, now, action, point.x, point.y, /* metaState= */ 1);
dispatchTouchEvent(event); frameLayout.dispatchTouchEvent(event);
return true; return true;
} }
@AnyThread private static final class InternalFrameLayout extends FrameLayout {
public CanvasRenderer getRenderer() {
return canvasRenderer; 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