Showing posts with label OpenGL ES. Show all posts
Showing posts with label OpenGL ES. Show all posts

Thursday, 26 March 2015

Game Performance: Layout Qualifiers

Today, we want to share some best practices on using the OpenGL Shading Language (GLSL) that can optimize the performance of your game and simplify your workflow. Specifically, Layout qualifiers make your code more deterministic and increase performance by reducing your work.



Let’s start with a simple vertex shader and change it as we go along.



This basic vertex shader takes position and texture coordinates, transforms the position and outputs the data to the fragment shader:

attribute vec4 vertexPosition;
attribute vec2 vertexUV;

uniform mat4 matWorldViewProjection;

varying vec2 outTexCoord;

void main()
{
  outTexCoord = vertexUV;
  gl_Position = matWorldViewProjection * vertexPosition;
}

Vertex Attribute Index


To draw a mesh on to the screen, you need to create a vertex buffer and fill it with vertex data, including positions and texture coordinates for this example.



In our sample shader, the vertex data may be laid out like this:

struct Vertex
{
Vector4 Position;
Vector2 TexCoords;
};

Therefore, we defined our vertex shader attributes like this:

attribute vec4 vertexPosition;
attribute vec2 vertexUV;

To associate the vertex data with the shader attributes, a call to glGetAttribLocation will get the handle of the named attribute. The attribute format is then detailed with a call to glVertexAttribPointer.

GLint handleVertexPos = glGetAttribLocation( myShaderProgram, "vertexPosition" );
glVertexAttribPointer( handleVertexPos, 4, GL_FLOAT, GL_FALSE, 0, 0 );

GLint handleVertexUV = glGetAttribLocation( myShaderProgram, "vertexUV" );
glVertexAttribPointer( handleVertexUV, 2, GL_FLOAT, GL_FALSE, 0, 0 );

But you may have multiple shaders with the vertexPosition attribute and calling glGetAttribLocation for every shader is a waste of performance which increases the loading time of your game.



Using layout qualifiers you can change your vertex shader attributes declaration like this:

layout(location = 0) in vec4 vertexPosition;
layout(location = 1) in vec2 vertexUV;

To do so you also need to tell the shader compiler that your shader is aimed at GL ES version 3.1. This is done by adding a version declaration:

#version 300 es

Let’s see how this affects our shader, changes are marked in bold:

#version 300 es

layout(location = 0) in vec4 vertexPosition;
layout(location = 1) in vec2 vertexUV;


uniform mat4 matWorldViewProjection;

out vec2 outTexCoord;

void main()
{
  outTexCoord = vertexUV;
  gl_Position = matWorldViewProjection * vertexPosition;
}

Note that we also changed outTexCoord from varying to out. The varying keyword is deprecated from version 300 es and requires changing for the shader to work.



Note that Vertex Attribute qualifiers and #version 300 es are supported from OpenGL ES 3.0. The desktop equivalent is supported on OpenGL 3.3 and using #version 330.



Now you know your position attributes always at 0 and your texture coordinates will be at 1 and you can now bind your shader format without using glGetAttribLocation:

const int ATTRIB_POS = 0;
const int ATTRIB_UV = 1;

glVertexAttribPointer( ATTRIB_POS, 4, GL_FLOAT, GL_FALSE, 0, 0 );
glVertexAttribPointer( ATTRIB_UV, 2, GL_FLOAT, GL_FALSE, 0, 0 );

This simple change leads to a cleaner pipeline, simpler code and saved performance during loading time.


To learn more about performance on Android, check out the Android Performance Patterns series.



Posted by Shanee Nishry, Games Developer Advocate

Friday, 24 April 2009

Introducing GLSurfaceView

GLSurfaceView is a new API class in Android 1.5. GLSurfaceView makes OpenGL ES applications easier to write by:

  • Providing the glue code to connect OpenGL ES to the View system.
  • Providing the glue code to make OpenGL ES work with the Activity life-cycle.
  • Making it easy to choose an appropriate frame buffer pixel format.
  • Creating and managing a separate rendering thread to enable smooth animation.
  • Providing easy-to-use debugging tools for tracing OpenGL ES API calls and checking for errors.

GLSurfaceView is a good base for building an application that uses OpenGL ES for part or all of its rendering. A 2D or 3D action game would be a good candidate, as would a 2D or 3D data visualization application such as Google Maps StreetView.

The Simplest GLSurfaceView Application

Here's the source code to the simplest possible OpenGL ES application:

package com.example.android.apis.graphics;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;

public class ClearActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(this);
mGLView.setRenderer(new ClearRenderer());
setContentView(mGLView);
}

@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}

@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}

private GLSurfaceView mGLView;
}

class ClearRenderer implements GLSurfaceView.Renderer {
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Do nothing special.
}

public void onSurfaceChanged(GL10 gl, int w, int h) {
gl.glViewport(0, 0, w, h);
}

public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
}
}

This program doesn't do much: it clears the screen to black on every frame. But it is a complete OpenGL application, that correctly implements the Android activity life-cycle. It pauses rendering when the activity is paused, and resumes it when the activity is resumed. You could use this application as the basis for non-interactive demonstration programs. Just add more OpenGL calls to the ClearRenderer.onDrawFrame method. Notice that you don't even need to subclass the GLSurfaceView view.

Note that the GLSurfaceView.Renderer interface has three methods:

The onSurfaceCreated() method is called at the start of rendering, and whenever the OpenGL ES drawing context has to be recreated. (The drawing context is typically lost and recreated when the activity is paused and resumed.) OnSurfaceCreated() is a good place to create long-lived OpenGL resources like textures.

The onSurfaceChanged() method is called when the surface changes size. It's a good place to set your OpenGL viewport. You may also want to set your camera here, if it's a fixed camera that doesn't move around the scene.

The onDrawFrame() method is called every frame, and is responsible for drawing the scene. You would typically start by calling glClear to clear the framebuffer, followed by other OpenGL ES calls to draw the current scene.

How about User Input?

If you want an interactive application (like a game), you will typically subclass GLSurfaceView, because that's an easy way of obtaining input events. Here's a slightly longer example showing how to do that:

package com.google.android.ClearTest;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.MotionEvent;

public class ClearActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLView = new ClearGLSurfaceView(this);
setContentView(mGLView);
}

@Override
protected void onPause() {
super.onPause();
mGLView.onPause();
}

@Override
protected void onResume() {
super.onResume();
mGLView.onResume();
}

private GLSurfaceView mGLView;
}

class ClearGLSurfaceView extends GLSurfaceView {
public ClearGLSurfaceView(Context context) {
super(context);
mRenderer = new ClearRenderer();
setRenderer(mRenderer);
}

public boolean onTouchEvent(final MotionEvent event) {
queueEvent(new Runnable(){
public void run() {
mRenderer.setColor(event.getX() / getWidth(),
event.getY() / getHeight(), 1.0f);
}});
return true;
}

ClearRenderer mRenderer;
}

class ClearRenderer implements GLSurfaceView.Renderer {
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Do nothing special.
}

public void onSurfaceChanged(GL10 gl, int w, int h) {
gl.glViewport(0, 0, w, h);
}

public void onDrawFrame(GL10 gl) {
gl.glClearColor(mRed, mGreen, mBlue, 1.0f);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
}

public void setColor(float r, float g, float b) {
mRed = r;
mGreen = g;
mBlue = b;
}

private float mRed;
private float mGreen;
private float mBlue;
}

This application clears the screen every frame. When you tap on the screen, it sets the clear color based on the (x,y) coordinates of your touch event. Note the use of queueEvent() in ClearGLSurfaceView.onTouchEvent(). The queueEvent() method is used to safely communicate between the UI thread and the rendering thread. If you prefer you can use some other Java cross-thread communication technique, such as synchronized methods on the Renderer class itself. But queueing events is often the simplest way of dealing with cross-thread communication.

Other GLSurfaceView Samples

Tired of just clearing the screen? You can find more interesting samples in the API Demos sample in the SDK. All the OpenGL ES samples have been converted to use the GLSurfaceView view:

  • GLSurfaceView - a spinning triangle
  • Kube - a cube puzzle demo
  • Translucent GLSurfaceView - shows how to display 3D graphics on a translucent background
  • Textured Triangle - shows how to draw a textured 3D triangle
  • Sprite Text - shows how to draw text into a texture and then composite it into a 3D scene
  • Touch Rotate - shows how to rotate a 3D object in response to user input.

Choosing a Surface

GLSurfaceView helps you choose the type of surface to render to. Different Android devices support different types of surfaces, with no common subset. This makes it tricky problem to choose the best available surface on each device. By default GLSurfaceView tries to find a surface that's as close as possible to a 16-bit RGB frame buffer with a 16-bit depth buffer. Depending upon your application's needs you may want to change this behavior. For example, the Translucent GLSurfaceView sample needs an Alpha channel in order to render translucent data. GLSurfaceView provides an overloaded setEGLSurfaceChooser() method to give the developer control over which surface type is chosen:

setEGLConfigChooser(boolean needDepth)
Choose a config that's closest to R5G6B5 with or without a 16-bit framebuffer
setEGLConfigChooser(int redSize, int greenSize,int blueSize, int alphaSize,int depthSize, int stencilSize)
Choose the config with the fewest number of bits per pixel that has at least as many bits-per-channel as specified in the constructor.
setEGLConfigChooser(EGLConfigChooser configChooser)
Allow total control over choosing a configuration. You pass in your own implementation of EGLConfigChooser, which gets to inspect the device's capabilities and choose a configuration.

Continuous Rendering vs. Render When Dirty

Most 3D applications, such as games or simulations, are continuously animated. But some 3D applications are more reactive: they wait passively until the user does something, and then react to it. For those types of applications, the default GLSurfaceView behavior of continuously redrawing the screen is a waste of time. If you are developing a reactive application, you can call GLSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY), which turns off the continuous animation. Then you call GLSurfaceView.requestRender() whenever you want to re-render.

Help With Debugging

GLSurfaceView has a handy built-in feature for debugging OpenGL ES applications: the GLSurfaceView.setDebugFlags() method can be used to enable logging and/or error checking your OpenGL ES calls. Call this method in your GLSurfaceView's constructor, before calling setRenderer():

public ClearGLSurfaceView(Context context) {
super(context);
// Turn on error-checking and logging
setDebugFlags(DEBUG_CHECK_GL_ERROR | DEBUG_LOG_GL_CALLS);
mRenderer = new ClearRenderer();
setRenderer(mRenderer);
}

Learn about Android 1.5 and more at Google I/O. Members of the Android team will be there to give a series of in-depth technical sessions and to field your toughest questions.

Thursday, 11 September 2008

Three new Samples: Triangle, SpriteText and Downloader

I've posted three new open source samples to the apps-for-android project: Triangle, SpriteText and Downloader.


The first two samples, Triangle and SpriteText, show techniques that would be useful to anyone using the OpenGL ES 3D graphics APIs to write Android applications. The samples contain several reusable classes that may eventually be incorporated (in some form) into the SDK. Chief among these is the GLView class, which abstracts the OpenGL ES book-keeping code from the rest of the application. GLView helps handle the extra work OpenGL ES applications have to do when the activity is paused and resumed, and when the display goes to sleep and wakes up. In the Pause/Resume case the OpenGL surface has to be recreated. In the display sleep / wake-up case the entire OpenGL context has to be recreated.

Triangle

The first sample, Triangle, shows how to use the GLView class and the OpenGL ES 3D library to display a spinning textured triangle. Think of it as the "hello, world" of OpenGL ES apps. Because it's relatively simple, it's a good place to start when experimenting with the OpenGL ES API.


SpriteText

The second sample, SpriteText, shows how to efficiently display screen-aligned text using the GL11Ext.glDrawTexiOES method. SpriteText contains a reusable LabelMaker class for drawing static text and screen-aligned images, as well as a Projector class for finding the 2D screen coordinates corresponding to a 3D point, and a MatrixTrackingGL class for keeping track of the current transformation matrix. Finally, it shows how to use these classes to display a milliseconds per frame counter. A ms/f counter can be helpful for tuning graphics performance.


Downloader

The third sample, Downloader, shows how to add a downloader activity to your application. The downloader activity runs at the beginning of your application and makes sure that a set of files have been downloaded from a web server to the phone's SD card. Downloader is useful for applications that need more local data than can fit into an .apk file. For example a game could use Downloader to download the game's artwork, sound effects, and level data. The Downloader activity is designed to be a drop-in addition to your application. You customize it by supplying the URL of an XML configuration file which lists the data files that need to be downloaded.

Followers