A few notes and questions on FrameBuffers (and other stuff)

Started by mech, March 07, 2013, 08:30:57 AM

Previous topic - Next topic

mech

When developing with jpct-ae I noticed the following:

  • someFramebuffer.display() is not required for a Galaxy SIII, Tegra 3 GPUs seem to require it on the other hand
  • The .display() method does not trigger a screen refresh, only after .onDrawFrame() finished the screen will refreshed, this is troublesome when blitting a loading screen that only should be blitted once because the logic behind the actual loading has to happen in the frame AFTER the blitting. This is probably because of GLSurfaceView, see http://developer.android.com/reference/android/opengl/GLSurfaceView.html for more details.
    Alternativly do the loading outside the render thread, this is discouraged (see jpct documentation)
  • All rotation is done using radians this is nothing bad, but not noted in many places.
  • According to the documentation http://www.jpct.net/jpct-ae/doc/com/threed/jpct/Config.html#nearPlane the near clipping plane is 1 at default, may want to set this to 0.1 if you use the coordinates as meters, else clipping might happen pretty soon if you use a camera for first person view

  • A solution for android picture rescaling, this will spare you the time and RAM needed for resizing loaded textures, all examples on the jpct wiki use a strange way of letting Android rescale a picture then rescale it back and the convert it to textures this is unnecessary complex.

package com.example.game.loaders;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory.Options;

/**
* Contains necessary options to load files correctly with the BitmapFactory
*/
public class BitmapLoadOptions extends Options{
/*Constructor*/
public BitmapLoadOptions(){
this.inPreferQualityOverSpeed = true; //We have the time to load the textures
this.inScaled = false; //Don't scale the textures to fit dpi, this would result in non power of 2 textures which are rejected by JPCT
this.inJustDecodeBounds = false; //Decode the actual data instead of just looking for the boundaries
this.inPreferredConfig = Bitmap.Config.ARGB_8888; //Set the format of the Bitmap
}
}


package com.example.game.loaders;

import java.io.*;

import ai.pathfinding.Map;
import android.content.res.AssetManager;
import android.graphics.*;
import android.util.Log;

import com.threed.jpct.*;

/**
* Helper class to load textures
*/
public class DataLoader {
public static Texture loadTexture(String path, AssetManager assetManager) throws IOException{
return loadTexture(path, assetManager, false);
}

public static Texture loadTexture(String path, AssetManager assetManager, boolean hasAlpha) throws IOException{
if(path == null){ throw new IllegalArgumentException("Path given is null"); }
if(assetManager == null){ throw new IllegalArgumentException("Asset manager is null"); }
return new Texture(BitmapFactory.decodeStream(assetManager.open(path), null, new BitmapLoadOptions()), hasAlpha);
}
}


Also some questions:

  • If the total number of frames  in an Animation is needed a workaround is required: sum the length of all AnimationSequence-s or by call getKeyFrames().length, a separate method would be nice, or is there a better way?
  • Reusing a FrameBuffer after flushing the TextureManager will introduce a memory leak (probably not a bug simply because jpct is coupled loosly together) because the references to the Textures are still in the FrameBuffer. Didn't find this in the documentation: A call to http://www.jpct.net/jpct-ae/doc/com/threed/jpct/FrameBuffer.html#freeMemory%28%29 is required, this should be noted in the documentation in TextureManager.flush() and Framebuffer.freeMemory()
  • A method for getting the x/y/z rotation of a Object3D would be really, really nice, I still have no idea how to get the actual values out of the rotation matrix
  • Mind open sourcing the config chooser for the Tegra chips? No idea how to do this without the SDK from NVidia. You used some constant right?
    http://www.jpct.net/jpct-ae/doc/com/threed/jpct/util/NVDepthConfigChooser.html
    I had to use my own config chooser but I still require the better depth buffer so I can't use yours
  • Probably a bug or a non documented limitation: Textures with a size of 1x1 (2^0) and 2x2 (2^1) can not be created because jpct-ae says the size is too small. Didn't test 4x4 and upwards, or should this be like that? The loader code is the same code as posted above

EgonOlsen

Quote
someFramebuffer.display() is not required for a Galaxy SIII, Tegra 3 GPUs seem to require it on the other hand
It's mandatory to call this method, because it sets up some states for the next frame. Plus it does the render to texture.

Quote
The .display() method does not trigger a screen refresh, only after .onDrawFrame() finished the screen will refreshed...
Yes. That's how it works on Android. It outside of the scope of the engine.

Quote
All rotation is done using radians this is nothing bad, but not noted in many places.
True. I'll try to add that info if i stumble across docs where it's missing.

Quote
According to the documentation http://www.jpct.net/jpct-ae/doc/com/threed/jpct/Config.html#nearPlane the near clipping plane is 1 at default, may want to set this to 0.1 if you use the coordinates as meters, else clipping might happen pretty soon if you use a camera for first person view
You then have to adjust Config.glIgnoreNearPlane as well or your setting will have no effect. I encourage you to leave it 1 if possible. Setting it to 0.1 wastes a lot of depth buffer accuracy (which is limited on mobile device) in the closer sections where you actually don't need it.


Quote...all examples on the jpct wiki use a strange way of letting Android rescale a picture then rescale it back and the convert it to textures this is unnecessary complex.
Which example does this? I know that HelloWorld abuses the app icon as a texture and it does some rescale, but apart from that...?

Quote
If the total number of frames  in an Animation is needed a workaround is required: sum the length of all AnimationSequence-s or by call getKeyFrames().length, a separate method would be nice, or is there a better way?
The 0-sequence represents the animation as a whole.

Quote
Reusing a FrameBuffer after flushing the TextureManager will introduce a memory leak (probably not a bug simply because jpct is coupled loosly together) because the references to the Textures are still in the FrameBuffer. Didn't find this in the documentation: A call to http://www.jpct.net/jpct-ae/doc/com/threed/jpct/FrameBuffer.html#freeMemory%28%29 is required, this should be noted in the documentation in TextureManager.flush() and Framebuffer.freeMemory()
Agreed, i'll add that.

Quote
A method for getting the x/y/z rotation of a Object3D would be really, really nice, I still have no idea how to get the actual values out of the rotation matrix
Like this: http://www.jpct.net/forum2/index.php/topic,576.0.html. I'm undecided if i want that to be part of the API, because it encourages people to continue thinking in euler angles, which isn't a very good idea IMHO.

Quote
Mind open sourcing the config chooser for the Tegra pseudo AA? No idea how to do this without the SDK from NVidia. You used some constant right?
Sure, no problem:


package com.threed.jpct.util;

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;

import android.opengl.GLSurfaceView;

import com.threed.jpct.Config;
import com.threed.jpct.Logger;

/**
* ConfigChooser for anti-aliasing modes. Use this in a call to
* GLSurfaceView.setEGLConfigChooser(...) in your onCreate()-method of the
* Activity. Make sure that your hardware supports OpenGL ES2.0, because this
* requires it and will try to enable it.<br/>
* This implementation is based on the example from the GDC11.
*
* @author EgonOlsen
*/
public class AAConfigChooser implements GLSurfaceView.EGLConfigChooser {

private GLSurfaceView view = null;
private boolean withAlpha = false;
private int depth = 16;

/**
* Creates a new EGLConfigChooser that supports anti-aliasing.
*
* @param view
*            the GLSurfaceView
*/
public AAConfigChooser(GLSurfaceView view) {
this.view = view;
}

/**
* Creates a new EGLConfigChooser that supports anti-aliasing with optional
* alpha for the framebuffer.
*
* @param view
*            the GLSurfaceView
* @param withAlpha
*            alpha channel (for transparent framebuffers)?
*/
public AAConfigChooser(GLSurfaceView view, boolean withAlpha) {
this.view = view;
this.withAlpha = withAlpha;
}

/**
* Creates a new EGLConfigChooser that supports anti-aliasing with optional
* alpha for the framebuffer and a given depth for the depth buffer (16/32).
*
* @param view
*            the GLSurfaceView
* @param withAlpha
*            alpha channel (for transparent framebuffers)?
* @param depth
*            the depth of the framebuffer in bit.
*/
@SuppressWarnings("unused")
private AAConfigChooser(GLSurfaceView view, boolean withAlpha, int depth) {
this.view = view;
this.withAlpha = withAlpha;
this.depth = depth;
}

@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {

int greenSize = withAlpha ? 5 : 6;
int alphaSize = withAlpha ? 1 : 0;
int redSize = 5;
int blueSize = 5;

if (depth > 24) {
redSize = 8;
blueSize = 8;
greenSize = 8;
alphaSize = withAlpha ? 8 : 0;
}

try {
view.setEGLContextClientVersion(2);
} catch (IllegalStateException e) {
// Does not matter!
} catch (Throwable t) {
Logger.log("Couldn't initialize OpenGL ES 2.0", Logger.ERROR);
return null;
}

int[] val = new int[1];

// Try to find a normal multisample configuration first.
int[] configSpec = { EGL10.EGL_RED_SIZE, redSize, EGL10.EGL_GREEN_SIZE, greenSize, EGL10.EGL_BLUE_SIZE, blueSize, EGL10.EGL_ALPHA_SIZE, alphaSize, EGL10.EGL_DEPTH_SIZE,
depth, EGL10.EGL_RENDERABLE_TYPE, 4, EGL10.EGL_SAMPLE_BUFFERS, 1, EGL10.EGL_SAMPLES, 2, EGL10.EGL_NONE };

if (!egl.eglChooseConfig(display, configSpec, null, 0, val)) {
error();
}
int numConfigs = val[0];

if (numConfigs <= 0) {
// No normal multisampling config was found. Try to create a
// converage multisampling configuration, for the nVidia Tegra2.

final int EGL_COVERAGE_BUFFERS_NV = 0x30E0;
final int EGL_COVERAGE_SAMPLES_NV = 0x30E1;
final int EGL_DEPTH_ENCODING_NV = 0x30E2;
final int EGL_DEPTH_ENCODING_NONLINEAR_NV = 0x30E3;

configSpec = new int[] { EGL10.EGL_RED_SIZE, redSize, EGL10.EGL_GREEN_SIZE, greenSize, EGL10.EGL_BLUE_SIZE, blueSize, EGL10.EGL_ALPHA_SIZE, alphaSize,
EGL10.EGL_DEPTH_SIZE, depth, EGL10.EGL_RENDERABLE_TYPE, 4, EGL_DEPTH_ENCODING_NV, EGL_DEPTH_ENCODING_NONLINEAR_NV, EGL_COVERAGE_BUFFERS_NV, 1,
EGL_COVERAGE_SAMPLES_NV, 2, EGL10.EGL_NONE };

if (!egl.eglChooseConfig(display, configSpec, null, 0, val)) {
error();
}
numConfigs = val[0];

if (numConfigs <= 0) {
Logger.log("No AA config found...defaulting to non-AA modes!");
configSpec = new int[] { EGL10.EGL_RED_SIZE, redSize, EGL10.EGL_GREEN_SIZE, greenSize, EGL10.EGL_BLUE_SIZE, blueSize, EGL10.EGL_ALPHA_SIZE, alphaSize,
EGL10.EGL_DEPTH_SIZE, depth, EGL10.EGL_RENDERABLE_TYPE, 4, EGL10.EGL_NONE };

if (!egl.eglChooseConfig(display, configSpec, null, 0, val)) {
error();
}
numConfigs = val[0];

if (numConfigs <= 0) {
error();
}
Config.aaMode = 0;
Logger.log("No AA enabled!");
} else {
Config.aaMode = 2;
Logger.log("CSAA enabled!");
}
} else {
Config.aaMode = 1;
Logger.log("MSAA enabled with 2 samples!");
}

// Get all matching configurations.
EGLConfig[] configs = new EGLConfig[numConfigs];
if (!egl.eglChooseConfig(display, configSpec, configs, numConfigs, val)) {
error();
}

int index = -1;
for (int i = 0; i < configs.length; ++i) {
if (findConfigAttrib(egl, display, configs[i], EGL10.EGL_RED_SIZE, 0) == 5) {
index = i;
break;
}
}
if (index == -1) {
Logger.log("Unable to find a matching config...using default!");
index = 0;
}
EGLConfig config = configs.length > 0 ? configs[index] : null;
if (config == null) {
error();
}
return config;
}

private void error() {
Config.aaMode = 0;
Logger.log("Failed to choose config!", Logger.ERROR);
}

private int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attribute, int defaultValue) {
int[] val = new int[1];
if (egl.eglGetConfigAttrib(display, config, attribute, val)) {
return val[0];
}
return defaultValue;
}
}


QuoteProbably a bug or a non documented limitation: Textures with a size of 1x1 (2^0) and 2x2 (2^1) can not be created because jpct-ae says the size is too small.
The minimum size is 8x8. I agree that the docs don't make this clear and i've no idea why i choosed that as the minimum. I'll look into it...

EgonOlsen