/* * OpenGLDemoField.java * * Copyright � 1998-2011 Research In Motion Limited * * 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. * * Note: For the sake of simplicity, this sample application may not leverage * resource bundles and resource strings. However, it is STRONGLY recommended * that application developers make use of the localization features available * within the BlackBerry development platform to ensure a seamless application * experience across a variety of languages and geographies. For more information * on localizing your application, please refer to the BlackBerry Java Development * Environment Development Guide associated with this release. */ package com.rim.samples.device.opengl20demo; import java.io.IOException; import java.io.InputStream; import javax.microedition.khronos.opengles.GL; import net.rim.device.api.animation.AnimatedScalar; import net.rim.device.api.animation.Animation; import net.rim.device.api.animation.Animator; import net.rim.device.api.io.IOUtilities; import net.rim.device.api.math.Matrix4f; import net.rim.device.api.math.Vector3f; import net.rim.device.api.opengles.GL20; import net.rim.device.api.opengles.GLField; import net.rim.device.api.opengles.GLUtils; import net.rim.device.api.system.Display; import net.rim.device.api.system.EncodedImage; /** * A GLField subclass that renders a cube using OpenGL ES 2.0 */ class OpenGLDemoField extends GLField { private static final int TARGET_FRAMERATE = 60; // Shader filenames private static final String VERTEX_SHADER = "/shaders/TextureLight.vsh"; private static final String FRAGMENT_SHADER = "/shaders/TextureLight.fsh"; private Cube _cube; private boolean _sizeChanged = true; // Matrix for transforming the cube private final Matrix4f _matrix = new Matrix4f(); // Projection matrix private final Matrix4f _projection = new Matrix4f(); // Axis to rotate the cube around private final Vector3f _rotationAxis = new Vector3f(1.0f, 1.0f, 0.0f); // Shader program private int _program; // Vertex attribute locations private int _positionLoc; private int _texCoordLoc; private int _normalLoc; // Uniform locations private int _matrixLoc; private int _lightDirectionLoc; private int _lightAmbientLoc; private int _lightDiffuseLoc; private int _textureLoc; private Animator _animator; private final AnimatedScalar _rotation = new AnimatedScalar(0.0f); /** * Creates a new OpenGLDemoField object */ OpenGLDemoField() { super(GLField.VERSION_2_0); setTargetFrameRate(TARGET_FRAMERATE); initializeAnimation(); } /** * @see net.rim.device.api.opengles.GLField#initialize(GL) */ protected void initialize(final GL g) { final GL20 gl = (GL20) g; // Create geometry for drawing a cube _cube = new Cube(); _cube.init(gl); // Initialize OpenGL state and load all OpenGL resources gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); gl.glEnable(GL20.GL_DEPTH_TEST); gl.glEnable(GL20.GL_CULL_FACE); // Load the shaders _program = createShaderProgram(gl, getSource(VERTEX_SHADER), getSource(FRAGMENT_SHADER)); gl.glUseProgram(_program); // Get attribute locations _positionLoc = gl.glGetAttribLocation(_program, "position"); _texCoordLoc = gl.glGetAttribLocation(_program, "texCoord"); _normalLoc = gl.glGetAttribLocation(_program, "normal"); // Get uniform locations _matrixLoc = gl.glGetUniformLocation(_program, "matrix"); _lightDirectionLoc = gl.glGetUniformLocation(_program, "lightDirection"); _lightAmbientLoc = gl.glGetUniformLocation(_program, "lightAmbient"); _lightDiffuseLoc = gl.glGetUniformLocation(_program, "lightDiffuse"); _textureLoc = gl.glGetUniformLocation(_program, "texture"); // Set uniform values gl.glUniform1i(_textureLoc, 0); // Light direction (normalized) gl.glUniform3f(_lightDirectionLoc, 0.0f, 0.0f, -1.0f); // Ambient light color gl.glUniform3f(_lightAmbientLoc, 0.2f, 0.2f, 0.2f); // Diffuse light color gl.glUniform3f(_lightDiffuseLoc, 1.0f, 1.0f, 1.0f); // Load texture gl.glActiveTexture(GL20.GL_TEXTURE0); final EncodedImage encodedImage = EncodedImage.getEncodedImageResource("BlackBerry.png"); createTexture(gl, encodedImage, GL20.GL_RGB, GL20.GL_UNSIGNED_SHORT_5_6_5); checkError(gl); } /** * Initializes the animations */ private void initializeAnimation() { _animator = new Animator(0); // Add an animation that will animate indefinitely // from 0.0 to 2.0*PI over 5 seconds. final float r = (float) (Math.PI * 2.0); final Animation animation = _animator.addAnimationFromTo(_rotation, AnimatedScalar.ANIMATION_PROPERTY_SCALAR, 0.0f, r, Animation.EASINGCURVE_LINEAR, 5000L); animation.setRepeatCount(Animation.REPEAT_COUNT_INDEFINITE); _animator.begin(0L); } /** * @see net.rim.device.api.opengles.GLField#render(GL) */ protected void render(final GL g) { final GL20 gl = (GL20) g; if (_sizeChanged) { sizeChanged(gl, getWidth(), getHeight()); _sizeChanged = false; } gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); gl.glUseProgram(_program); // Transform the cube _matrix.setIdentity(); _matrix.translate(0.0f, 0.0f, -3.5f); _matrix.rotate(_rotationAxis, _rotation.getFloat()); // There are no matrix modes in GL20. // Multiply the model-view matrix with the projection // matrix and pass it to the shader program. Matrix4f.multiply(_projection, _matrix, _matrix); gl.glUniformMatrix4fv(_matrixLoc, 1, false, _matrix.getArray(), 0); // Set the attribute location of the position, normal and texture // coordinates _cube.enableVertexAttrib(_positionLoc); _cube.enableTexcoordAttrib(_texCoordLoc); _cube.enableNormalAttrib(_normalLoc); // Draw the cube _cube.render(gl); checkError(gl); } /** * @see net.rim.device.api.ui.Field#layout(int, int) */ protected void layout(final int width, final int height) { // Use all available width and height up to the screen's size setExtent(Math.min(width, Display.getWidth()), Math.min(height, Display .getHeight())); _sizeChanged = true; } /** * Called when the size of the drawable area changes * * @param gl * The GL context * @param width * The new width * @param height * The new height */ public void sizeChanged(final GL20 gl, final int width, final int height) { // Update the viewport to reflect the new size gl.glViewport(0, 0, width, height); // Set up a perspective projection Matrix4f.createPerspective(45.0f, (float) width / (float) height, 0.15f, 100.0f, _projection); } /** * @see net.rim.device.api.opengles.GLField#update() */ public void update() { // Updates the animator at the same frequency as the GLField _animator.update(); } /** * Creates a shader program from the given vertex shader and fragment shader * source * * @param gl * The GL context * @param vertexShaderSource * The vertex shader source code * @param fragmentShaderSource * The fragment shader source code * @return The program object id * @throws RuntimeException * If there was an error compiling or linking the shaders */ private static int createShaderProgram(final GL20 gl, final String vertexShaderSource, final String fragmentShaderSource) { final String[] infolog = new String[2]; // Load and compile shaders final int vertexShader = GLUtils.glLoadShader(gl, GL20.GL_VERTEX_SHADER, vertexShaderSource, infolog, 0); final int fragmentShader = GLUtils.glLoadShader(gl, GL20.GL_FRAGMENT_SHADER, fragmentShaderSource, infolog, 1); // Check for shader compile errors if (vertexShader == 0) { throw new RuntimeException("Vertex shader compile error. \n" + infolog[0]); } if (fragmentShader == 0) { throw new RuntimeException("Fragment shader compile error. \n" + infolog[1]); } // Create the program object final int program = gl.glCreateProgram(); if (program == 0) { throw new RuntimeException("Error creating shader program."); } // Attach the shader gl.glAttachShader(program, vertexShader); gl.glAttachShader(program, fragmentShader); // Link the program gl.glLinkProgram(program); // Check the link status final int[] linked = new int[1]; gl.glGetProgramiv(program, GL20.GL_LINK_STATUS, linked, 0); if (linked[0] == GL20.GL_FALSE) { // Get the error string before deleting the program final String log = gl.glGetProgramInfoLog(program); gl.glDeleteProgram(program); throw new RuntimeException("Program link error. " + log); } // Delete the shader objects now that the program is linked gl.glDeleteShader(vertexShader); gl.glDeleteShader(fragmentShader); return program; } /** * Creates and binds a new texture from the given EncodedImage * * @param gl * The GL context * @param encodedImage * EncodedImage containing the texture data to load * @param format * Format for the texture * @param type * Data type for the texture * @return Texture name of the created texture */ private static int createTexture(final GL20 gl, final EncodedImage encodedImage, final int format, final int type) { final int[] textures = new int[1]; // Generate 1 texture gl.glGenTextures(1, textures, 0); final int texture = textures[0]; // Bind the newly generated texture gl.glBindTexture(GL20.GL_TEXTURE_2D, texture); // Load the image data from the encodedImage into the texture GLUtils.glTexImage2D(gl, GL20.GL_TEXTURE_2D, 0, format, type, encodedImage, null); // Turn on automatic mipmap generation gl.glGenerateMipmap(GL20.GL_TEXTURE_2D); // Turn on tri-linear filtering gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_LINEAR_MIPMAP_LINEAR); gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_LINEAR); gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_REPEAT); gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_REPEAT); return texture; } /** * Returns the text from the given file in the given module * * @param filename * The name of the file * @return The text in the file as a String or a blank String if there was * an error */ private String getSource(final String filename) { try { return readTextFromFile(filename); } catch (final IOException e) { return ""; } } /** * Returns the text from the given file in the given module * * @param filename * The name of the file * @return The text in the file as a String * @throws IOException * If there was an error */ private String readTextFromFile(final String filename) throws IOException { if (filename == null) { return ""; } final InputStream stream = getClass().getResourceAsStream(filename); final byte[] bytes = IOUtilities.streamToBytes(stream); final String text = new String(bytes); stream.close(); return text; } /** * Throws a RuntimeException if a GL error was detected * * @param gl * The GL context * @throws RuntimeException * If a GL error was detected */ private static void checkError(final GL20 gl) { final int error = gl.glGetError(); if (error != 0) { throw new RuntimeException("GL Error: " + error); } } }