aboutsummaryrefslogtreecommitdiffhomepage
path: root/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering
diff options
context:
space:
mode:
Diffstat (limited to 'platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering')
-rw-r--r--platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/BackgroundRenderer.java190
-rw-r--r--platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java349
-rw-r--r--platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/ShaderUtil.java99
3 files changed, 638 insertions, 0 deletions
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 <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,
+ };
+}
diff --git a/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java
new file mode 100644
index 0000000000..2ef85b049e
--- /dev/null
+++ b/platform_tools/android/apps/skar_java/src/main/java/com/google/skar/examples/helloskar/rendering/DrawManager.java
@@ -0,0 +1,349 @@
+package com.google.skar.examples.helloskar.rendering;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.opengl.Matrix;
+
+import com.google.ar.core.Plane;
+import com.google.ar.core.PointCloud;
+import com.google.ar.core.Pose;
+import com.google.ar.core.TrackingState;
+import com.google.skar.CanvasMatrixUtil;
+import com.google.skar.PaintUtil;
+import com.google.skar.SkARFingerPainting;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Sample class that handles drawing different types of geometry using the matrices provided
+ * by ARCore. The matrices are handled by SkARMatrix in order to be passed to the drawing
+ * Canvas.
+ */
+
+public class DrawManager {
+ private float[] projectionMatrix = new float[16];
+ private float[] viewMatrix = new float[16];
+ private float viewportWidth;
+ private float viewportHeight;
+ private ColorFilter lightFilter;
+ private BitmapShader planeShader;
+ public ArrayList<float[]> modelMatrices = new ArrayList<>();
+ public SkARFingerPainting fingerPainting = new SkARFingerPainting(false);
+
+ public void updateViewport(float width, float height) {
+ viewportWidth = width;
+ viewportHeight = height;
+ }
+
+ public void updateProjectionMatrix(float[] projectionMatrix) {
+ this.projectionMatrix = projectionMatrix;
+ }
+
+ public void updateViewMatrix(float[] viewMatrix) {
+ this.viewMatrix = viewMatrix;
+ }
+
+ public void updateLightColorFilter(float[] colorCorr) {
+ lightFilter = PaintUtil.createLightCorrectionColorFilter(colorCorr);
+ }
+
+ // Sample function for drawing a circle
+ public void drawCircle(Canvas canvas) {
+ if (modelMatrices.isEmpty()) {
+ return;
+ }
+ Paint p = new Paint();
+ p.setColorFilter(lightFilter);
+ p.setARGB(180, 100, 0, 0);
+
+ canvas.save();
+ android.graphics.Matrix m = CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0),
+ viewMatrix, projectionMatrix, viewportWidth, viewportHeight);
+ canvas.setMatrix(m);
+
+ canvas.drawCircle(0, 0, 0.1f, p);
+ canvas.restore();
+ }
+
+ // Sample function for drawing an animated round rect
+ public void drawAnimatedRoundRect(Canvas canvas, float radius) {
+ if (modelMatrices.isEmpty()) {
+ return;
+ }
+ Paint p = new Paint();
+ p.setColorFilter(lightFilter);
+ p.setARGB(180, 100, 0, 100);
+
+ canvas.save();
+ canvas.setMatrix(CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0),
+ viewMatrix, projectionMatrix, viewportWidth, viewportHeight));
+ canvas.drawRoundRect(0,0, 0.5f, 0.5f, radius, radius, p);
+ canvas.restore();
+ }
+
+ // Sample function for drawing a rect
+ public void drawRect(Canvas canvas) {
+ if (modelMatrices.isEmpty()) {
+ return;
+ }
+ Paint p = new Paint();
+ p.setColorFilter(lightFilter);
+ p.setARGB(180, 0, 0, 255);
+ canvas.save();
+ canvas.setMatrix(CanvasMatrixUtil.createPerspectiveMatrix(modelMatrices.get(0),
+ viewMatrix, projectionMatrix, viewportWidth, viewportHeight));
+ RectF rect = new RectF(0, 0, 0.2f, 0.2f);
+ canvas.drawRect(rect, p);
+ canvas.restore();
+ }
+
+ // Sample function for drawing text on a canvas
+ public void drawText(Canvas canvas, String text) {
+ if (modelMatrices.isEmpty()) {
+ return;
+ }
+ Paint p = new Paint();
+ float textSize = 100;
+ p.setColorFilter(lightFilter);
+ p.setARGB(255, 0, 255, 0);
+ p.setTextSize(textSize);
+
+ float[] scaleMatrix = getTextScaleMatrix(textSize);
+ float[] rotateMatrix = CanvasMatrixUtil.createXYtoXZRotationMatrix();
+ float[][] matrices = { scaleMatrix, rotateMatrix, modelMatrices.get(0), viewMatrix,
+ projectionMatrix,
+ CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)};
+
+ canvas.save();
+ canvas.setMatrix(CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices)));
+ canvas.drawText(text, 0, 0, p);
+ canvas.restore();
+ }
+
+ public void drawFingerPainting(Canvas canvas) {
+ // Build the path before rendering
+ fingerPainting.buildPath();
+
+ // If path empty, return
+ if (fingerPainting.getPaths().isEmpty()) {
+ return;
+ }
+
+ // Get finger painting model matrix
+ float[] model = fingerPainting.getModelMatrix();
+ float[] in = new float[16];
+ Matrix.setIdentityM(in, 0);
+ Matrix.translateM(in, 0, model[12], model[13], model[14]);
+
+ float[] initRot = CanvasMatrixUtil.createXYtoXZRotationMatrix();
+
+ float[] scale = new float[16];
+ float s = 0.001f;
+ Matrix.setIdentityM(scale, 0);
+ Matrix.scaleM(scale, 0, s, s, s);
+
+ // Matrix = mvpv
+ float[][] matrices = {scale, initRot, in, viewMatrix, projectionMatrix, CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)};
+ android.graphics.Matrix mvpv = CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices));
+
+ // Paint set up
+ Paint p = new Paint();
+ p.setStyle(Paint.Style.STROKE);
+ p.setStrokeWidth(30f);
+ p.setAlpha(120);
+
+ for (Path path : fingerPainting.getPaths()) {
+ if (path.isEmpty()) {
+ continue;
+ }
+ p.setColor(fingerPainting.getPathColor(path));
+
+ // Scaling issues appear to happen when drawing a Path and transforming the Canvas
+ // directly with a matrix on Android versions less than P. Ideally we would
+ // switch true to be (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
+
+ if (true) {
+ // Transform applied through canvas
+ canvas.save();
+ canvas.setMatrix(mvpv);
+ canvas.drawPath(path, p);
+ canvas.restore();
+ } else {
+ // Transform path directly
+ Path pathDst = new Path();
+ path.transform(mvpv, pathDst);
+
+ // Draw dest path
+ canvas.save();
+ canvas.setMatrix(new android.graphics.Matrix());
+ canvas.drawPath(pathDst, p);
+ canvas.restore();
+ }
+ }
+
+ }
+
+ // Sample function for drawing the AR point cloud
+ public void drawPointCloud(Canvas canvas, PointCloud cloud) {
+ FloatBuffer points = cloud.getPoints();
+ int numberOfPoints = points.remaining() / 4;
+
+ float[][] matrices = {viewMatrix, projectionMatrix, CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)};
+ float[] vpv = CanvasMatrixUtil.multiplyMatrices4x4(matrices);
+
+ float[] pointsToDraw = new float[numberOfPoints * 2];
+ for (int i = 0; i < numberOfPoints; i++) {
+ float[] point = {points.get(i * 4), points.get(i * 4 + 1), points.get(i * 4 + 2), 1};
+ float[] result = CanvasMatrixUtil.multiplyMatrixVector(vpv, point, true);
+ pointsToDraw[i * 2] = result[0];
+ pointsToDraw[i * 2 + 1] = result[1];
+ }
+
+ Paint p = new Paint();
+ p.setARGB(220, 20, 232, 255);
+ p.setStrokeCap(Paint.Cap.SQUARE);
+ p.setStrokeWidth(6.0f);
+
+ canvas.save();
+ float[] id = new float[16];
+ Matrix.setIdentityM(id, 0);
+ android.graphics.Matrix identity = CanvasMatrixUtil.createMatrixFrom4x4(id);
+ canvas.setMatrix(identity);
+ canvas.drawPoints(pointsToDraw, p);
+ canvas.restore();
+ }
+
+
+ // Sample function for drawing AR planes
+ public void drawPlanes(Canvas canvas, Pose cameraPose, Collection<Plane> allPlanes) {
+ if (allPlanes.size() <= 0) {
+ return;
+ }
+
+ for (Plane plane : allPlanes) {
+ Plane subsumePlane = plane.getSubsumedBy();
+ if (plane.getTrackingState() != TrackingState.TRACKING || subsumePlane != null) {
+ continue;
+ }
+
+ float distance = calculateDistanceToPlane(plane.getCenterPose(), cameraPose);
+ if (distance < 0) { // Plane is back-facing.
+ continue;
+ }
+
+ // Get plane model matrix
+ float[] model = new float[16];
+ plane.getCenterPose().toMatrix(model, 0);
+
+ // Initial rotation
+ float[] initRot = CanvasMatrixUtil.createXYtoXZRotationMatrix();
+
+ // Matrix = mvpv
+ float[][] matrices = {initRot, model, viewMatrix, projectionMatrix, CanvasMatrixUtil.createViewportMatrix(viewportWidth, viewportHeight)};
+ android.graphics.Matrix mvpv = CanvasMatrixUtil.createMatrixFrom4x4(CanvasMatrixUtil.multiplyMatrices4x4(matrices));
+
+ drawPlaneAsPath(canvas, mvpv, plane);
+ }
+ }
+
+ // Helper function that draws an AR plane using a path
+ private void drawPlaneAsPath(Canvas canvas, android.graphics.Matrix mvpv, Plane plane) {
+ int vertsSize = plane.getPolygon().limit() / 2;
+ FloatBuffer polygon = plane.getPolygon();
+ polygon.rewind();
+
+ // Build source path from polygon data
+ Path pathSrc = new Path();
+ pathSrc.moveTo(polygon.get(0), polygon.get(1));
+ for (int i = 1; i < vertsSize; i++) {
+ pathSrc.lineTo(polygon.get(i * 2), polygon.get(i * 2 + 1));
+ }
+ pathSrc.close();
+
+ // Set up paint
+ Paint p = new Paint();
+
+ if (false) {
+ //p.setShader(planeShader);
+ p.setColorFilter(new PorterDuffColorFilter(Color.argb(0.4f, 1, 0, 0),
+ PorterDuff.Mode.SRC_ATOP));
+ }
+
+ p.setColor(Color.RED);
+ p.setAlpha(100);
+ p.setStrokeWidth(0.01f);
+ p.setStyle(Paint.Style.STROKE);
+
+
+ if (true) {
+ // Shader local matrix
+ android.graphics.Matrix lm = new android.graphics.Matrix();
+ lm.setScale(0.00005f, 0.00005f);
+ planeShader.setLocalMatrix(lm);
+ // Draw dest path
+ canvas.save();
+ canvas.setMatrix(mvpv);
+ canvas.drawPath(pathSrc, p);
+ canvas.restore();
+ } else {
+ // Build destination path by transforming source path
+ Path pathDst = new Path();
+ pathSrc.transform(mvpv, pathDst);
+
+ // Shader local matrix
+ android.graphics.Matrix lm = new android.graphics.Matrix();
+ lm.setScale(0.00005f, 0.00005f);
+ lm.postConcat(mvpv);
+ planeShader.setLocalMatrix(lm);
+
+ // Draw dest path
+ canvas.save();
+ canvas.setMatrix(new android.graphics.Matrix());
+ canvas.drawPath(pathDst, p);
+ canvas.restore();
+ }
+ }
+
+ public void initializePlaneShader(Context context, String gridDistanceTextureName) throws IOException {
+ // Read the texture.
+ Bitmap planeTexture =
+ BitmapFactory.decodeStream(context.getAssets().open(gridDistanceTextureName));
+ // Set up the shader
+ planeShader = new BitmapShader(planeTexture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
+ planeShader.setLocalMatrix(new android.graphics.Matrix());
+ }
+
+ private float[] getTextScaleMatrix(float size) {
+ float scaleFactor = 1 / (size * 10);
+ float[] initScale = new float[16];
+ android.opengl.Matrix.setIdentityM(initScale, 0);
+ android.opengl.Matrix.scaleM(initScale, 0, scaleFactor, scaleFactor, scaleFactor);
+ return initScale;
+ }
+
+ public static float calculateDistanceToPlane(Pose planePose, Pose cameraPose) {
+ float[] normal = new float[3];
+ float cameraX = cameraPose.tx();
+ float cameraY = cameraPose.ty();
+ float cameraZ = cameraPose.tz();
+ // Get transformed Y axis of plane's coordinate system.
+ planePose.getTransformedAxis(1, 1.0f, normal, 0);
+ // Compute dot product of plane's normal with vector from camera to plane center.
+ return (cameraX - planePose.tx()) * normal[0]
+ + (cameraY - planePose.ty()) * normal[1]
+ + (cameraZ - planePose.tz()) * normal[2];
+ }
+}
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();
+ }
+ }
+}