From f27b479f957df19b44913f81509aaef7af0d19ce Mon Sep 17 00:00:00 2001 From: ziadb Date: Thu, 26 Jul 2018 15:31:01 -0400 Subject: [PATCH] SkAR Java: refactored project structure 1) Changed file paths 2) Added helpers that were missing previously Bug: skia: Change-Id: Idb4194fcef815a23277a17670acf46255a47451d Reviewed-on: https://skia-review.googlesource.com/143680 Reviewed-by: Mike Reed --- .../helloskar/app}/ARSurfaceView.java | 2 +- .../helloskar/app}/HelloCanvasAR.java | 2 +- .../helloskar/app}/SkARFingerPainting.java | 2 +- .../helpers/CameraPermissionHelper.java | 65 ++++++ .../helpers/DisplayRotationHelper.java | 112 +++++++++++ .../helloskar/helpers/FullScreenHelper.java | 49 +++++ .../helloskar/helpers/SnackbarHelper.java | 113 +++++++++++ .../examples/helloskar/helpers/TapHelper.java | 116 +++++++++++ .../rendering/BackgroundRenderer.java | 190 ++++++++++++++++++ .../helloskar/rendering}/DrawManager.java | 2 +- .../helloskar/rendering/ShaderUtil.java | 99 +++++++++ 11 files changed, 748 insertions(+), 4 deletions(-) rename platform_tools/android/apps/skar_java/src/main/java/com/google/{ar/core/examples/java/helloskar => skar/examples/helloskar/app}/ARSurfaceView.java (95%) rename platform_tools/android/apps/skar_java/src/main/java/com/google/{ar/core/examples/java/helloskar => skar/examples/helloskar/app}/HelloCanvasAR.java (99%) rename platform_tools/android/apps/skar_java/src/main/java/com/google/skar/{ => examples/helloskar/app}/SkARFingerPainting.java (98%) create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/CameraPermissionHelper.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/DisplayRotationHelper.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/FullScreenHelper.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/SnackbarHelper.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/TapHelper.java create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/BackgroundRenderer.java rename platform_tools/android/apps/skar_java/src/main/java/com/google/{ar/core/examples/java/helloskar => skar/examples/helloskar/rendering}/DrawManager.java (99%) create mode 100644 platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/ShaderUtil.java diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/ARSurfaceView.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/ARSurfaceView.java similarity index 95% rename from platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/ARSurfaceView.java rename to platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/ARSurfaceView.java index eaa336cc9e..03185c2cd3 100644 --- a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/ARSurfaceView.java +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/ARSurfaceView.java @@ -1,4 +1,4 @@ -package com.google.ar.core.examples.java.helloskar; +package com.google.skar.examples.helloskar.app; import android.content.Context; import android.graphics.Color; diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloCanvasAR.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/HelloCanvasAR.java similarity index 99% rename from platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloCanvasAR.java rename to platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/HelloCanvasAR.java index cc4ad93b99..718545e72b 100644 --- a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/HelloCanvasAR.java +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/HelloCanvasAR.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.ar.core.examples.java.helloskar; +package com.google.skar.examples.helloskar.app; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARFingerPainting.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/SkARFingerPainting.java similarity index 98% rename from platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARFingerPainting.java rename to platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/SkARFingerPainting.java index d63295857d..eac2a6bb33 100644 --- a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/SkARFingerPainting.java +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/app/SkARFingerPainting.java @@ -1,4 +1,4 @@ -package com.google.skar; +package com.google.skar.examples.helloskar.app; import android.graphics.Color; import android.graphics.Path; diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/CameraPermissionHelper.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/CameraPermissionHelper.java new file mode 100644 index 0000000000..f8e9c33af3 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/CameraPermissionHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * 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 + * + * http://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 com.google.skar.examples.helloskar.helpers; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.provider.Settings; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; + +/** + * Helper to ask camera permission. + */ +public final class CameraPermissionHelper { + private static final int CAMERA_PERMISSION_CODE = 0; + private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA; + + /** + * Check to see we have the necessary permissions for this app. + */ + public static boolean hasCameraPermission(Activity activity) { + return ContextCompat.checkSelfPermission(activity, CAMERA_PERMISSION) + == PackageManager.PERMISSION_GRANTED; + } + + /** + * Check to see we have the necessary permissions for this app, and ask for them if we don't. + */ + public static void requestCameraPermission(Activity activity) { + ActivityCompat.requestPermissions( + activity, new String[]{CAMERA_PERMISSION}, CAMERA_PERMISSION_CODE); + } + + /** + * Check to see if we need to show the rationale for this permission. + */ + public static boolean shouldShowRequestPermissionRationale(Activity activity) { + return ActivityCompat.shouldShowRequestPermissionRationale(activity, CAMERA_PERMISSION); + } + + /** + * Launch Application Setting to grant permission. + */ + public static void launchPermissionSettings(Activity activity) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", activity.getPackageName(), null)); + activity.startActivity(intent); + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/DisplayRotationHelper.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/DisplayRotationHelper.java new file mode 100644 index 0000000000..7e4ce81c29 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/DisplayRotationHelper.java @@ -0,0 +1,112 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * 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 + * + * http://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 com.google.skar.examples.helloskar.helpers; + +import android.app.Activity; +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.view.Display; +import android.view.WindowManager; + +import com.google.ar.core.Session; + +/** + * Helper to track the display rotations. In particular, the 180 degree rotations are not notified + * by the onSurfaceChanged() callback, and thus they require listening to the android display + * events. + */ +public final class DisplayRotationHelper implements DisplayListener { + private boolean viewportChanged; + private int viewportWidth; + private int viewportHeight; + private final Context context; + private final Display display; + + /** + * Constructs the DisplayRotationHelper but does not register the listener yet. + * + * @param context the Android {@link Context}. + */ + public DisplayRotationHelper(Context context) { + this.context = context; + display = context.getSystemService(WindowManager.class).getDefaultDisplay(); + } + + /** + * Registers the display listener. Should be called from {@link Activity#onResume()}. + */ + public void onResume() { + context.getSystemService(DisplayManager.class).registerDisplayListener(this, null); + } + + /** + * Unregisters the display listener. Should be called from {@link Activity#onPause()}. + */ + public void onPause() { + context.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + } + + /** + * Records a change in surface dimensions. This will be later used by {@link + * #updateSessionIfNeeded(Session)}. Should be called from {@link + * android.opengl.GLSurfaceView.Renderer + * #onSurfaceChanged(javax.microedition.khronos.opengles.GL10, int, int)}. + * + * @param width the updated width of the surface. + * @param height the updated height of the surface. + */ + public void onSurfaceChanged(int width, int height) { + viewportWidth = width; + viewportHeight = height; + viewportChanged = true; + } + + /** + * Updates the session display geometry if a change was posted either by {@link + * #onSurfaceChanged(int, int)} call or by {@link #onDisplayChanged(int)} system callback. This + * function should be called explicitly before each call to {@link Session#update()}. This + * function will also clear the 'pending update' (viewportChanged) flag. + * + * @param session the {@link Session} object to update if display geometry changed. + */ + public void updateSessionIfNeeded(Session session) { + if (viewportChanged) { + int displayRotation = display.getRotation(); + session.setDisplayGeometry(displayRotation, viewportWidth, viewportHeight); + viewportChanged = false; + } + } + + /** + * Returns the current rotation state of android display. Same as {@link Display#getRotation()}. + */ + public int getRotation() { + return display.getRotation(); + } + + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + viewportChanged = true; + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/FullScreenHelper.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/FullScreenHelper.java new file mode 100644 index 0000000000..3738b963dc --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/FullScreenHelper.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * 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 + * + * http://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 com.google.skar.examples.helloskar.helpers; + +import android.app.Activity; +import android.view.View; +import android.view.WindowManager; + +/** + * Helper to set up the Android full screen mode. + */ +public final class FullScreenHelper { + /** + * Sets the Android fullscreen flags. Expected to be called from {@link + * Activity#onWindowFocusChanged(boolean hasFocus)}. + * + * @param activity the Activity on which the full screen mode will be set. + * @param hasFocus the hasFocus flag passed from the {@link Activity#onWindowFocusChanged(boolean + * hasFocus)} callback. + */ + public static void setFullScreenOnWindowFocusChanged(Activity activity, boolean hasFocus) { + if (hasFocus) { + // https://developer.android.com/training/system-ui/immersive.html#sticky + activity + .getWindow() + .getDecorView() + .setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/SnackbarHelper.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/SnackbarHelper.java new file mode 100644 index 0000000000..02393499e9 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/SnackbarHelper.java @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * 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 + * + * http://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 com.google.skar.examples.helloskar.helpers; + +import android.app.Activity; +import android.support.design.widget.BaseTransientBottomBar; +import android.support.design.widget.Snackbar; +import android.view.View; + +/** + * Helper to manage the sample snackbar. Hides the Android boilerplate code, and exposes simpler + * methods. + */ +public final class SnackbarHelper { + private static final int BACKGROUND_COLOR = 0xbf323232; + private Snackbar messageSnackbar; + + private enum DismissBehavior {HIDE, SHOW, FINISH} + + ; + + public boolean isShowing() { + return messageSnackbar != null; + } + + /** + * Shows a snackbar with a given message. + */ + public void showMessage(Activity activity, String message) { + show(activity, message, DismissBehavior.HIDE); + } + + /** + * Shows a snackbar with a given message, and a dismiss button. + */ + public void showMessageWithDismiss(Activity activity, String message) { + show(activity, message, DismissBehavior.SHOW); + } + + /** + * Shows a snackbar with a given error message. When dismissed, will finish the activity. Useful + * for notifying errors, where no further interaction with the activity is possible. + */ + public void showError(Activity activity, String errorMessage) { + show(activity, errorMessage, DismissBehavior.FINISH); + } + + /** + * Hides the currently showing snackbar, if there is one. Safe to call from any thread. Safe to + * call even if snackbar is not shown. + */ + public void hide(Activity activity) { + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + if (messageSnackbar != null) { + messageSnackbar.dismiss(); + } + messageSnackbar = null; + } + }); + } + + private void show( + final Activity activity, final String message, final DismissBehavior dismissBehavior) { + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + messageSnackbar = + Snackbar.make( + activity.findViewById(android.R.id.content), + message, + Snackbar.LENGTH_INDEFINITE); + messageSnackbar.getView().setBackgroundColor(BACKGROUND_COLOR); + if (dismissBehavior != DismissBehavior.HIDE) { + messageSnackbar.setAction( + "Dismiss", + new View.OnClickListener() { + @Override + public void onClick(View v) { + messageSnackbar.dismiss(); + } + }); + if (dismissBehavior == DismissBehavior.FINISH) { + messageSnackbar.addCallback( + new BaseTransientBottomBar.BaseCallback() { + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + super.onDismissed(transientBottomBar, event); + activity.finish(); + } + }); + } + } + messageSnackbar.show(); + } + }); + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/TapHelper.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/TapHelper.java new file mode 100644 index 0000000000..bcbfe2aa98 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/helpers/TapHelper.java @@ -0,0 +1,116 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * 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 + * + * http://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 com.google.skar.examples.helloskar.helpers; + +import android.content.Context; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +/** + * Helper to detect taps using Android GestureDetector, and pass the taps between UI thread and + * render thread. + */ +public final class TapHelper implements OnTouchListener { + private final GestureDetector gestureDetector; + private final BlockingQueue queuedSingleTaps = new ArrayBlockingQueue<>(16); + private final BlockingQueue queuedFingerHold = new ArrayBlockingQueue<>(16); + private boolean isScrolling = false; + private boolean previousScroll = true; + + public static class ScrollEvent { + public MotionEvent e; + public boolean isStartOfScroll; + + public ScrollEvent(MotionEvent e, boolean isStartOfScroll) { + this.e = e; + this.isStartOfScroll = isStartOfScroll; + } + } + + /** + * Creates the tap helper. + * + * @param context the application's context. + */ + public TapHelper(Context context) { + gestureDetector = + new GestureDetector( + context, + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapUp(MotionEvent e) { + // Queue tap if there is space. Tap is lost if queue is full. + queuedSingleTaps.offer(e); + return true; + } + + @Override + public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + // Queue motion events when scrolling + if (e2.getPointerCount() == 1 && e1.getPointerCount() == 1) { + previousScroll = isScrolling; + isScrolling = true; + queuedFingerHold.offer(new ScrollEvent(e2, startedScrolling())); + return true; + } + return false; + } + + + @Override + public boolean onDown(MotionEvent e) { + return true; + } + }); + } + + /** + * Polls for a tap. + * + * @return if a tap was queued, a MotionEvent for the tap. Otherwise null if no taps are queued. + */ + public MotionEvent poll() { + return queuedSingleTaps.poll(); + } + + public ScrollEvent holdPoll() { return queuedFingerHold.poll(); } + + public boolean startedScrolling() { + return isScrolling && !previousScroll; + } + + @Override + public boolean onTouch(View view, MotionEvent motionEvent) { + boolean val = gestureDetector.onTouchEvent(motionEvent); + + // If finger is up + is scrolling: don't scroll anymore, and empty Touch Hold queue + if (motionEvent.getAction() == MotionEvent.ACTION_UP && isScrolling) { + previousScroll = true; + isScrolling = false; + queuedFingerHold.clear(); + } + return val; + } +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/BackgroundRenderer.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/BackgroundRenderer.java new file mode 100644 index 0000000000..b6b5a859b8 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/BackgroundRenderer.java @@ -0,0 +1,190 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * 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 + * + * http://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 com.google.skar.examples.helloskar.rendering; + +import android.content.Context; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; + +import com.google.ar.core.Frame; +import com.google.ar.core.Session; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +/** + * This class renders the AR background from camera feed. It creates and hosts the texture given to + * ARCore to be filled with the camera image. + */ +public class BackgroundRenderer { + private static final String TAG = BackgroundRenderer.class.getSimpleName(); + + // Shader names. + private static final String VERTEX_SHADER_NAME = "shaders/screenquad.vert"; + private static final String FRAGMENT_SHADER_NAME = "shaders/screenquad.frag"; + + private static final int COORDS_PER_VERTEX = 3; + private static final int TEXCOORDS_PER_VERTEX = 2; + private static final int FLOAT_SIZE = 4; + + private FloatBuffer quadVertices; + private FloatBuffer quadTexCoord; + private FloatBuffer quadTexCoordTransformed; + + private int quadProgram; + + private int quadPositionParam; + private int quadTexCoordParam; + private int textureId = -1; + + public BackgroundRenderer() { + } + + public int getTextureId() { + return textureId; + } + + /** + * Allocates and initializes OpenGL resources needed by the background renderer. Must be called on + * the OpenGL thread, typically in {@link GLSurfaceView.Renderer#onSurfaceCreated(GL10, + * EGLConfig)}. + * + * @param context Needed to access shader source. + */ + public void createOnGlThread(Context context) throws IOException { + // Generate the background texture. + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + textureId = textures[0]; + int textureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; + GLES20.glBindTexture(textureTarget, textureId); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); + + int numVertices = 4; + if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) { + throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer."); + } + + ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE); + bbVertices.order(ByteOrder.nativeOrder()); + quadVertices = bbVertices.asFloatBuffer(); + quadVertices.put(QUAD_COORDS); + quadVertices.position(0); + + ByteBuffer bbTexCoords = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoords.order(ByteOrder.nativeOrder()); + quadTexCoord = bbTexCoords.asFloatBuffer(); + quadTexCoord.put(QUAD_TEXCOORDS); + quadTexCoord.position(0); + + ByteBuffer bbTexCoordsTransformed = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoordsTransformed.order(ByteOrder.nativeOrder()); + quadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer(); + + int vertexShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_NAME); + int fragmentShader = + ShaderUtil.loadGLShader(TAG, context, GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_NAME); + + quadProgram = GLES20.glCreateProgram(); + GLES20.glAttachShader(quadProgram, vertexShader); + GLES20.glAttachShader(quadProgram, fragmentShader); + GLES20.glLinkProgram(quadProgram); + GLES20.glUseProgram(quadProgram); + + ShaderUtil.checkGLError(TAG, "Program creation"); + + quadPositionParam = GLES20.glGetAttribLocation(quadProgram, "a_Position"); + quadTexCoordParam = GLES20.glGetAttribLocation(quadProgram, "a_TexCoord"); + + ShaderUtil.checkGLError(TAG, "Program parameters"); + } + + /** + * Draws the AR background image. The image will be drawn such that virtual content rendered with + * the matrices provided by {@link com.google.ar.core.Camera#getViewMatrix(float[], int)} and + * {@link com.google.ar.core.Camera#getProjectionMatrix(float[], int, float, float)} will + * accurately follow static physical objects. This must be called before drawing virtual + * content. + * + * @param frame The last {@code Frame} returned by {@link Session#update()}. + */ + public void draw(Frame frame) { + // If display rotation changed (also includes view size change), we need to re-query the uv + // coordinates for the screen rect, as they may have changed as well. + if (frame.hasDisplayGeometryChanged()) { + frame.transformDisplayUvCoords(quadTexCoord, quadTexCoordTransformed); + } + + // No need to test or write depth, the screen quad has arbitrary depth, and is expected + // to be drawn first. + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + GLES20.glDepthMask(false); + + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); + + GLES20.glUseProgram(quadProgram); + + // Set the vertex positions. + GLES20.glVertexAttribPointer( + quadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadVertices); + + // Set the texture coordinates. + GLES20.glVertexAttribPointer( + quadTexCoordParam, + TEXCOORDS_PER_VERTEX, + GLES20.GL_FLOAT, + false, + 0, + quadTexCoordTransformed); + + // Enable vertex arrays + GLES20.glEnableVertexAttribArray(quadPositionParam); + GLES20.glEnableVertexAttribArray(quadTexCoordParam); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + // Disable vertex arrays + GLES20.glDisableVertexAttribArray(quadPositionParam); + GLES20.glDisableVertexAttribArray(quadTexCoordParam); + + // Restore the depth state for further drawing. + GLES20.glDepthMask(true); + GLES20.glEnable(GLES20.GL_DEPTH_TEST); + + ShaderUtil.checkGLError(TAG, "Draw"); + } + + private static final float[] QUAD_COORDS = + new float[]{ + -1.0f, -1.0f, 0.0f, -1.0f, +1.0f, 0.0f, +1.0f, -1.0f, 0.0f, +1.0f, +1.0f, 0.0f, + }; + + private static final float[] QUAD_TEXCOORDS = + new float[]{ + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + }; +} diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/DrawManager.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java similarity index 99% rename from platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/DrawManager.java rename to platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java index 724ab17c02..2ef85b049e 100644 --- a/platform_tools/android/apps/skar_java/src/main/java/com/google/ar/core/examples/java/helloskar/DrawManager.java +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java @@ -1,4 +1,4 @@ -package com.google.ar.core.examples.java.helloskar; +package com.google.skar.examples.helloskar.rendering; import android.content.Context; import android.graphics.Bitmap; diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/ShaderUtil.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/ShaderUtil.java new file mode 100644 index 0000000000..ce33a45966 --- /dev/null +++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/ShaderUtil.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * 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 + * + * http://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 com.google.skar.examples.helloskar.rendering; + +import android.content.Context; +import android.opengl.GLES20; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * Shader helper functions. + */ +public class ShaderUtil { + /** + * Converts a raw text file, saved as a resource, into an OpenGL ES shader. + * + * @param type The type of shader we will be creating. + * @param filename The filename of the asset file about to be turned into a shader. + * @return The shader object handler. + */ + public static int loadGLShader(String tag, Context context, int type, String filename) + throws IOException { + String code = readRawTextFileFromAssets(context, filename); + int shader = GLES20.glCreateShader(type); + GLES20.glShaderSource(shader, code); + GLES20.glCompileShader(shader); + + // Get the compilation status. + final int[] compileStatus = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); + + // If the compilation failed, delete the shader. + if (compileStatus[0] == 0) { + Log.e(tag, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } + + if (shader == 0) { + throw new RuntimeException("Error creating shader."); + } + + return shader; + } + + /** + * Checks if we've had an error inside of OpenGL ES, and if so what that error is. + * + * @param label Label to report in case of error. + * @throws RuntimeException If an OpenGL error is detected. + */ + public static void checkGLError(String tag, String label) { + int lastError = GLES20.GL_NO_ERROR; + // Drain the queue of all errors. + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + Log.e(tag, label + ": glError " + error); + lastError = error; + } + if (lastError != GLES20.GL_NO_ERROR) { + throw new RuntimeException(label + ": glError " + lastError); + } + } + + /** + * Converts a raw text file into a string. + * + * @param filename The filename of the asset file about to be turned into a shader. + * @return The context of the text file, or null in case of error. + */ + private static String readRawTextFileFromAssets(Context context, String filename) + throws IOException { + try (InputStream inputStream = context.getAssets().open(filename); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + return sb.toString(); + } + } +}