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 <reed@google.com>
This commit is contained in:
ziadb 2018-07-26 15:31:01 -04:00 committed by Ziad Ben Hadj-Alouane
parent 3ab3b562b1
commit f27b479f95
11 changed files with 748 additions and 4 deletions

View File

@ -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.content.Context;
import android.graphics.Color; import android.graphics.Color;

View File

@ -14,7 +14,7 @@
* limitations under the License. * 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.PropertyValuesHolder;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;

View File

@ -1,4 +1,4 @@
package com.google.skar; package com.google.skar.examples.helloskar.app;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Path; import android.graphics.Path;

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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<Snackbar>() {
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
super.onDismissed(transientBottomBar, event);
activity.finish();
}
});
}
}
messageSnackbar.show();
}
});
}
}

View File

@ -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<MotionEvent> queuedSingleTaps = new ArrayBlockingQueue<>(16);
private final BlockingQueue<ScrollEvent> 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;
}
}

View File

@ -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 <b>before</b> 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,
};
}

View File

@ -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.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;

View File

@ -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();
}
}
}