More FrameBuffers in one program

Started by Darai, January 27, 2016, 02:39:41 PM

Previous topic - Next topic

Darai

Hello,

I am trying to test bliting to an existing texture. I know, there is a warning sign don't do it, but I wanted to test it and I found a strange behaviour:

I have the renderer's function onSurfaceChange, creating the world and it's objects. In this function, I create 2 frame buffers, one for the standard rendering and one for the rendering into the texture. BUT once I make a second FrameBuffer with the same GL10 the screen shrinks to the size of the new frame buffer no matter what. So, I should maybe call different GL10? Or how can I create more independent FrameBuffers?


FrameBuffer fb;
FrameBuffer toTex;

public void onSurfaceChanged(GL10 gl, int w, int h) {
fb = new FrameBuffer(gl, w, h);
toTex = new FrameBuffer(gl, 256, 256);
...
}

public void onDrawFrame(GL10 gl) {
...
fb.display;
}


And to the larger problem, I would like to blit a text to a texture. I see here 4 ways:
1) blit to screen every frame: Too slow and can't skew the text.
2) create an object with 2 Triangle Sprite for every letter: Fine, but creates a complex Object3D
3) blit to bitmap using Canvas and replace texture in TextureManager: Unrecomended since texture replace(== upload a new texture to GPU), Canvas and Bitmap object is (i expect) much slower than Texture and FrameBuffer operations.
4) blit directly to texture using FrameBuffer.setRenderTarget(): This is unrecomended in the documentation by Egon but I don't know why.

So my question to the larger problem is: Why is the 4th approach not recomended since from this point of view it is probably the fastest one. (For a text which will not change every frame) And what would you pick and why?

Thanks for all advices.

EgonOlsen

You can't use multiple FrameBuffer instances on the same GL context. The FrameBuffer contains a GLRenderer which itself is bound to the context and manages the GL states. If you have two buffers, you have two renderers and because one doesn't know the managed states of the other, you will end up with everything being screwed up. With that out of the way, on to your actual question...

...4) is the way to go. The docs state that it's not recommended and if you've already tried it, you should know the reason: It doesn't work... ;) Except that it actually does...for my RPG's quest book, I'm doing exactly the same thing (Old screen shot: http://jpct.de/jpctnet/img/rpg_and57.png). But here comes the catch: You have to blit with modified coordinates and jPCT doesn't do this modification for you...because of...reasons...that I can't remember...anyway...what you have to do is this:

A blit like this:

buffer.blit(texture, left, top, destX, destY, width, height, destWidth, destHeight, transparency, additive, color);


has to use modified coordinates like this:


destX = convertX(buffer, destX);
destY = convertY(buffer, cm.getHeight() - destY);
destWidth = convertX(buffer, destWidth);
destHeight = -convertY(buffer, destHeight);

buffer.blit(texture, left, top, destX, destY, width, height, destWidth, destHeight, transparency, additive, color);


with convertX(), convertY() and getHeigth() being (with target being the texture that is used as a render target):


public int convertX(FrameBuffer buffer, int x) {
return (int) ((float) x * ((float) buffer.getWidth() / (float) target.getWidth()));
}

public int convertY(FrameBuffer buffer, int y) {
return (int) ((float) y * ((float) buffer.getHeight() / (float) target.getHeight()));
}

public int getHeight() {
return target.getHeight();
}


And you might want to add a call to


buffer.sync();


before starting the actual render to texture process, because it makes it more compatible with some strange devices.

Darai

Hello Egon,

Thanks for the quick answer, I love the screenshot you posted.

To summarize this topic:

More FrameBuffers is definitley nogo, since there is only one GLcontext. That means that I have to use the one FrameBuffer I have (?) which means to use the process described in the thread http://www.jpct.net/forum2/index.php/topic,3716.0.html.

The meaning is to temporary (just for the proces of remaking the texture) use the setRenderTarget() and then end with removeRenderTarget() to redirect the main Buffer.

AND:
You suggest to scale the blit according the texture size viz the code snippet you wrote (and reverse its Y axis) and SYNC the Buffer before (?) the manipulation with the texture.

If I misunderstood you, just say so please. :-)
Thanks once again, I am going to try it out.

EgonOlsen


Darai

Hmm,

ok, I am still doing something wrong... I know, that now I moved to the wider topic (blitting to a texture) but I hope you don't mind to finish it.

I rewrote the problem into the following simple example. It is all inside the onSurfaceChanged() method. I have 2 squares, the left one with the original texture and the right one, where I would expect a BLUE texture, or copy of the right-square texture. But no, I have still the dull black empty texture, so what am I missing? And what is more, during the fb.display() it anounces a warning: "<core_glCopyTexSubImage2D:318>: GL_INVALID_OPERATION"

Thanks again for your help.


public void onSurfaceChanged(GL10 gl, int w, int h) {
if(master != null){return;}
master = HelloWorld.this;

if (fb != null) {
fb.dispose();
}

fb = new FrameBuffer(gl, w, h);
world = new World();
world.setAmbientLight(150, 150, 150);
sun = new Light(world);
sun.setIntensity(100, 100, 100);
SimpleVector sv = new SimpleVector(0, -50, -80);
sun.setPosition(sv);
try {
tx1 = new Texture(HelloWorld.getApp().getAssets().open("Texture.png"), true);
TextureManager.getInstance().addTexture("tx1", tx1);
} catch (IOException e) {
e.printStackTrace();
}
tx2 = new Texture(256,256);
TextureManager.getInstance().addTexture("tx2", tx2);
int tx1ID = TextureManager.getInstance().getTextureID("tx1");
int tx2ID = TextureManager.getInstance().getTextureID("tx2");

int destX = convertX(fb, 0);
int destY = convertY(fb, tx2.getHeight() - 0);
int destWidth = convertX(fb, 256);
int destHeight = -convertY(fb, 256);

fb.setRenderTarget(tx2);
fb.sync();
fb.clear(RGBColor.BLUE);
fb.blit(tx1, 0, 0, destX, destY, 512, 512, destWidth, destHeight, 255, false);
fb.display();
fb.removeRenderTarget();

camera = world.getCamera();
camera.setPosition(0, 0, -50);
camera.lookAt(SimpleVector.ORIGIN);

try {
obj1 = new Object3D(2);
obj1.addTriangle(new SimpleVector(-1f,-1f,0), 0, 0, new SimpleVector(-1f,1f,0), 0, 1f, new SimpleVector(1f,1f,0), 1f, 1f, tx1ID);
obj1.addTriangle(new SimpleVector(-1f,-1f,0), 0, 0, new SimpleVector(1f,1f,0), 1f, 1f, new SimpleVector(1f,-1f,0), 1f, 0, tx1ID);
obj1.scale(10f);
world.addObject(obj1);

obj1.translate(-15f, 0f, 0f);

obj2 = new Object3D(2);
obj2.addTriangle(new SimpleVector(-1f,-1f,0), 0, 0, new SimpleVector(-1f,1f,0), 0, 1f, new SimpleVector(1f,1f,0), 1f, 1f, tx2ID);
obj2.addTriangle(new SimpleVector(-1f,-1f,0), 0, 0, new SimpleVector(1f,1f,0), 1f, 1f, new SimpleVector(1f,-1f,0), 1f, 0, tx2ID);
obj2.scale(10f);
world.addObject(obj2);

obj2.translate(15f, 0f, 0f);
} catch (Exception e) {
e.printStackTrace();
}
MemoryHelper.compact();
}

EgonOlsen

You can't do that in onSurfaceChanged(), you have to do it in onDraw(), because only onDraw() actually...draws... ;)

Darai

Still no success,

The renderer requires the onDrawFrame(GL10 gl) method, so I am using this for the normal onDraw stuff. I tried to add the texture alternating code to the beginning of this onDrawFrame() and also to the isolated onDraw() method, but without success. So again, I expect that I am something missing, but what? obj2.build(false)? obj2.touch()? I tried those with the same result.


public void onDrawFrame(GL10 gl) {
if(!textureChanged){
textureChanged = true;

int destX = convertX(fb, 0);
int destY = convertY(fb, tx2.getHeight() - 0);
int destWidth = convertX(fb, 256);
int destHeight = -convertY(fb, 256);

fb.setRenderTarget(tx2);
fb.sync();
fb.clear(RGBColor.BLUE);
fb.blit(tx1, 0, 0, destX, destY, 512, 512, destWidth, destHeight, 255, false);
fb.display();
fb.removeRenderTarget();
}
fb.clear(RGBColor.BLUE);
world.renderScene(fb);
world.draw(fb);
fb.display();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}


EgonOlsen

onDrawFrame() is correct, my fault...

Make sure that you are using OpenGL ES 2.0 (http://www.jpct.net/wiki/index.php?title=OpenGL_ES_2.0_support). Render to texture on OpenGL ES 1.x usually doesn't work too well...

EgonOlsen

Also, change this


fb.blit(tx1, 0, 0, destX, destY, 512, 512, destWidth, destHeight, 255, false);
fb.display();


to this


fb.blit(tx1, 0, 0, destX, destY, 512, 512, destWidth, destHeight, 255, false);
emptyBlitWorld.renderScene(fb);
emptyBlitWorld.draw(fb);
fb.display();


with emptyBlitWorld being an additional world instance with no further content.

Darai

Finally, it is alive!!  :)

Thank you. So, to summarize it (how to blit into a texture):

  • It is doable, so if you need to change a texture dynamically, ForExample to write a text on it once in a while, go for it.
  • It doesn't work without GL2.0 http://www.jpct.net/wiki/index.php?title=OpenGL_ES_2.0_support Beware of the difference in the FrameBuffer constructor.
  • The draw to a texture has to be done in the onDraw function (well, actualy onDrawFrame() function) (Which sucks, since up until now I had all model preparation in onSurfaceChange() and now I will have to divide it, but hey that is life.)
  • The draw has to be done using the one and only FrameBuffer in the app. No other FrameBuffers allowed.
  • The best command sequence to blit into the texture is in the previous posts, combining setRenderTarget() method and a blank emptyBlitWorld World.
And that is that... thanks again for all your help.

Darai

One small addition, sorry :)

possibly a bug report, but first evaluate this by yourself, maybe you allready fixed this in the new beta version:
The scaling of the blitting to the texture is wrong. Even the convert function is not entirely helping. The image on the new texture was suspiciously scaled in a simmilar ratio as the screen (== the buffer) was, so I was experimenting and it turned out that if you want to blit one texture to another one using FrameBuffer fb, the variables you need are not:

int destX = 0;
int destY = -fb.getHeight();
int destWidth = fb.getWidth();
int destHeight = fb.getHeight();

as you suggested but actually:

int destX = 0;
int destY = (int)((float)(fb.getWidth()+fb.getHeight())/2f);
int destWidth = fb.getWidth();
int destHeight = -fb.getWidth();

Including the strange int destHeight = fb.getWidth();, that is not an error in my transcription I checked it and rechecked it it really works this way, check it please too if it is only my bug, and I should download your beta or if it is really still there.

EgonOlsen

#11
I'm not sure what the issue is here...I'm doing it exactly the way that I posted and it works just fine. I even created a quick and dirty test case for it using the same approach and it works just fine as well.
Here it is for reference:


package com.threed.jpct.example;

import java.lang.reflect.Field;

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

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

import com.threed.jpct.Camera;
import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Light;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.BitmapHelper;
import com.threed.jpct.util.MemoryHelper;

/**
* @author EgonOlsen
*
*/
public class HelloWorld extends Activity {

// Used to handle pause and resume...
private static HelloWorld master = null;

private GLSurfaceView mGLView;
private MyRenderer renderer = null;
private FrameBuffer fb = null;
private World world = null;
private RGBColor back = new RGBColor(50, 50, 100);

private float touchTurn = 0;
private float touchTurnUp = 0;

private float xpos = -1;
private float ypos = -1;

private Object3D cube = null;
private int fps = 0;
private boolean gl2 = true;

private Light sun = null;

private Texture target = null;
private Texture texture = null;
private World dummyWorld = new World();

protected void onCreate(Bundle savedInstanceState) {

Logger.log("onCreate");

if (master != null) {
copy(master);
}

super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(getApplication());

if (gl2) {
mGLView.setEGLContextClientVersion(2);
} else {
mGLView.setEGLConfigChooser(new GLSurfaceView.EGLConfigChooser() {
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] attributes = new int[] { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE };
EGLConfig[] configs = new EGLConfig[1];
int[] result = new int[1];
egl.eglChooseConfig(display, attributes, configs, 1, result);
return configs[0];
}
});

}

renderer = new MyRenderer();
mGLView.setRenderer(renderer);
setContentView(mGLView);
}

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

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

@Override
protected void onStop() {
super.onStop();
}

private void copy(Object src) {
try {
Logger.log("Copying data from master Activity!");
Field[] fs = src.getClass().getDeclaredFields();
for (Field f : fs) {
f.setAccessible(true);
f.set(this, f.get(src));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public boolean onTouchEvent(MotionEvent me) {

if (me.getAction() == MotionEvent.ACTION_DOWN) {
xpos = me.getX();
ypos = me.getY();
return true;
}

if (me.getAction() == MotionEvent.ACTION_UP) {
xpos = -1;
ypos = -1;
touchTurn = 0;
touchTurnUp = 0;
return true;
}

if (me.getAction() == MotionEvent.ACTION_MOVE) {
float xd = me.getX() - xpos;
float yd = me.getY() - ypos;

xpos = me.getX();
ypos = me.getY();

touchTurn = xd / -100f;
touchTurnUp = yd / -100f;
return true;
}

try {
Thread.sleep(15);
} catch (Exception e) {
// No need for this...
}

return super.onTouchEvent(me);
}

protected boolean isFullscreenOpaque() {
return true;
}

class MyRenderer implements GLSurfaceView.Renderer {

private long time = System.currentTimeMillis();

public MyRenderer() {
}

public void onSurfaceChanged(GL10 gl, int w, int h) {
if (fb != null) {
fb.dispose();
}

if (gl2) {
fb = new FrameBuffer(w, h); // OpenGL ES 2.0 constructor
} else {
fb = new FrameBuffer(gl, w, h); // OpenGL ES 1.x constructor
}

Config.viewportOffsetY = -0.15f;

if (master == null) {

world = new World();
world.setAmbientLight(20, 20, 20);

sun = new Light(world);
sun.setIntensity(250, 250, 250);

// Create a texture out of the icon...:-)
texture = new Texture(BitmapHelper.rescale(BitmapHelper.convert(getResources().getDrawable(R.drawable.icon)), 64, 64));
TextureManager.getInstance().addTexture("texture", texture);

cube = Primitives.getCube(10);
cube.calcTextureWrapSpherical();
cube.setTexture("texture");
cube.strip();
cube.build();

target = new Texture(256, 256);

world.addObject(cube);

Camera cam = world.getCamera();
cam.moveCamera(Camera.CAMERA_MOVEOUT, 50);
cam.lookAt(cube.getTransformedCenter());

SimpleVector sv = new SimpleVector();
sv.set(cube.getTransformedCenter());
sv.y -= 100;
sv.z -= 100;
sun.setPosition(sv);
MemoryHelper.compact();

if (master == null) {
Logger.log("Saving master Activity!");
master = HelloWorld.this;
}
}
}

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}

public void onDrawFrame(GL10 gl) {
if (touchTurn != 0) {
cube.rotateY(touchTurn);
touchTurn = 0;
}

if (touchTurnUp != 0) {
cube.rotateX(touchTurnUp);
touchTurnUp = 0;
}

// Snip
fb.setRenderTarget(target);
fb.sync();
int x = convertX(fb, target, 0);
int y = convertY(fb, target, target.getHeight() - 0);
int width = convertX(fb, target, target.getWidth());
int height = -convertY(fb, target, target.getHeight());
fb.blit(texture, 0, 0, x, y, 64, 64, width, height, -1, false, null);
dummyWorld.renderScene(fb);
dummyWorld.draw(fb);
fb.display();
fb.removeRenderTarget();
// Snap

fb.clear(back);
world.renderScene(fb);
world.draw(fb);
world.getCamera().moveCamera(Camera.CAMERA_MOVEOUT, 0.1f);
world.getCamera().rotateCameraZ(0.01f);

// And blit the result
fb.blit(target, 0, 0, 10, 10, 256, 256, 400, 400, -1, false, null);

fb.display();

if (System.currentTimeMillis() - time >= 1000) {
Logger.log(fps + "fps");
fps = 0;
time = System.currentTimeMillis();
}
fps++;
}

public int convertX(FrameBuffer buffer, Texture target, int x) {
return (int) ((float) x * ((float) buffer.getWidth() / (float) target.getWidth()));
}

public int convertY(FrameBuffer buffer, Texture target, int y) {
return (int) ((float) y * ((float) buffer.getHeight() / (float) target.getHeight()));
}

public int getHeight(Texture target) {
return target.getHeight();
}
}
}



Make sure that these dummy world rendering calls come between the blit()- and the display()-call. I know that this sucks and I could explain the reasons...but I'm too lazy... ;)

Darai

I have it,

I was damaging your code up until it did the same thing I was experiencing in my code. The important difference is, that I was doing the new texture rendering only on the first onSurfaceDraw() using this code (I added the firstRun if condition). So that is the problem. On the first onDrawFrame() run, the blit is acting differently.  ;D

public void onDrawFrame(GL10 gl) {
if (touchTurn != 0) {
cube.rotateY(touchTurn);
touchTurn = 0;
}

if (touchTurnUp != 0) {
cube.rotateX(touchTurnUp);
touchTurnUp = 0;
}
if(firstRun){
// Snip
fb.setRenderTarget(target);
fb.sync();
int x = convertX(fb, target, 0);
int y = convertY(fb, target, target.getHeight() - 0);
int width = convertX(fb, target, target.getWidth());
int height = -convertY(fb, target, target.getHeight());
fb.blit(texture, 0, 0, x, y, texture.getWidth(), texture.getHeight(), width, height, -1, false, null);
dummyWorld.renderScene(fb);
dummyWorld.draw(fb);
fb.display();
fb.removeRenderTarget();
firstRun = false;
}
// Snap

fb.clear(back);
world.renderScene(fb);
world.draw(fb);
world.getCamera().moveCamera(Camera.CAMERA_MOVEOUT, 0.1f);
world.getCamera().rotateCameraZ(0.01f);

// And blit the result
fb.blit(target, 0, 0, 10, 10, 256, 256, 400, 400, -1, false, null);

fb.display();

if (System.currentTimeMillis() - time >= 1000) {
Logger.log(fps + "fps");
fps = 0;
time = System.currentTimeMillis();
}
fps++;
}

Darai

Hello, Egon,

I am sorry for pulling this out so many times, but I really need to solve this to know if I can use the feature or abandon this approach and do something else...

What I did:

  • I used your recalculation method to get the proper x,y,width and height
  • I made the textures to render on the second frame so I overcome the first frame issue
  • I forced the program to render to TWO textures to find out how it will behave when I will need him to render everything for the whole scene on the second frame.
And the result is strange, twisted again. I am printing here the example code... it is really only that your code with added second texture to plot to. Can you give me a hint what I did wrong? Thanks.

package com.threed.jpct.example;

import java.lang.reflect.Field;

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

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

import com.threed.jpct.Camera;
import com.threed.jpct.Config;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Light;
import com.threed.jpct.Logger;
import com.threed.jpct.Object3D;
import com.threed.jpct.Primitives;
import com.threed.jpct.RGBColor;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.threed.jpct.World;
import com.threed.jpct.util.BitmapHelper;
import com.threed.jpct.util.MemoryHelper;

/**
* @author EgonOlsen
*
*/
public class HelloWorld extends Activity {

// Used to handle pause and resume...
private static HelloWorld master = null;

private GLSurfaceView mGLView;
private MyRenderer renderer = null;
private FrameBuffer fb = null;
private World world = null;
private RGBColor back = new RGBColor(50, 50, 100);

private float touchTurn = 0;
private float touchTurnUp = 0;

private float xpos = -1;
private float ypos = -1;

private Object3D cube = null;
private int fps = 0;
private boolean gl2 = true;
private boolean first = true;

private Light sun = null;

private Texture target = null;
private Texture target2 = null;
private Texture texture = null;
private World dummyWorld = new World();

private boolean firstRun = true;

protected void onCreate(Bundle savedInstanceState) {

Logger.log("onCreate");

if (master != null) {
copy(master);
}

super.onCreate(savedInstanceState);
mGLView = new GLSurfaceView(getApplication());

if (gl2) {
mGLView.setEGLContextClientVersion(2);
} else {
mGLView.setEGLConfigChooser(new GLSurfaceView.EGLConfigChooser() {
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
int[] attributes = new int[] { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE };
EGLConfig[] configs = new EGLConfig[1];
int[] result = new int[1];
egl.eglChooseConfig(display, attributes, configs, 1, result);
return configs[0];
}
});

}

renderer = new MyRenderer();
mGLView.setRenderer(renderer);
setContentView(mGLView);
}

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

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

@Override
protected void onStop() {
super.onStop();
System.exit(0);
}

private void copy(Object src) {
try {
Logger.log("Copying data from master Activity!");
Field[] fs = src.getClass().getDeclaredFields();
for (Field f : fs) {
f.setAccessible(true);
f.set(this, f.get(src));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public boolean onTouchEvent(MotionEvent me) {

if (me.getAction() == MotionEvent.ACTION_DOWN) {
xpos = me.getX();
ypos = me.getY();
return true;
}

if (me.getAction() == MotionEvent.ACTION_UP) {
xpos = -1;
ypos = -1;
touchTurn = 0;
touchTurnUp = 0;
return true;
}

if (me.getAction() == MotionEvent.ACTION_MOVE) {
float xd = me.getX() - xpos;
float yd = me.getY() - ypos;

xpos = me.getX();
ypos = me.getY();

touchTurn = xd / -100f;
touchTurnUp = yd / -100f;
return true;
}

try {
Thread.sleep(15);
} catch (Exception e) {
// No need for this...
}

return super.onTouchEvent(me);
}

protected boolean isFullscreenOpaque() {
return true;
}

class MyRenderer implements GLSurfaceView.Renderer {

private long time = System.currentTimeMillis();

public MyRenderer() {
}

public void onSurfaceChanged(GL10 gl, int w, int h) {
if (fb != null) {
fb.dispose();
}

if (gl2) {
fb = new FrameBuffer(w, h); // OpenGL ES 2.0 constructor
} else {
fb = new FrameBuffer(gl, w, h); // OpenGL ES 1.x constructor
}

Config.viewportOffsetY = -0.15f;

if (master == null) {

world = new World();
world.setAmbientLight(20, 20, 20);

sun = new Light(world);
sun.setIntensity(250, 250, 250);

// Create a texture out of the icon...:-)
texture = new Texture(BitmapHelper.rescale(BitmapHelper.convert(getResources().getDrawable(R.drawable.icon)), 64, 64));
TextureManager.getInstance().addTexture("texture", texture);

cube = Primitives.getCube(10);
cube.calcTextureWrapSpherical();
cube.setTexture("texture");
cube.strip();
cube.build();

target = new Texture(256, 256);
TextureManager.getInstance().addTexture("target", target);

target2 = new Texture(256, 256);
TextureManager.getInstance().addTexture("target2", target2);

world.addObject(cube);
cube.translate(15f, 0f, 0f);

Camera cam = world.getCamera();
cam.moveCamera(Camera.CAMERA_MOVEOUT, 50);
cam.lookAt(cube.getTransformedCenter());

SimpleVector sv = new SimpleVector();
sv.set(cube.getTransformedCenter());
sv.y -= 100;
sv.z -= 100;
sun.setPosition(sv);
MemoryHelper.compact();

if (master == null) {
Logger.log("Saving master Activity!");
master = HelloWorld.this;
}
}
}

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}

public void onDrawFrame(GL10 gl) {
if (touchTurn != 0) {
cube.rotateY(touchTurn);
touchTurn = 0;
}

if (touchTurnUp != 0) {
cube.rotateX(touchTurnUp);
touchTurnUp = 0;
}
if(firstRun){
if(first){
first = false;
}else{
// Snip on first
fb.setRenderTarget(target);
fb.sync();
int x = convertX(fb, target, 0);
int y = convertY(fb, target, target.getHeight() - 0);
int width = convertX(fb, target, target.getWidth());
int height = -convertY(fb, target, target.getHeight());
fb.blit(texture, 0, 0, x, y, texture.getWidth(), texture.getHeight(), width, height, -1, false, null);
dummyWorld.renderScene(fb);
dummyWorld.draw(fb);
fb.display();
fb.removeRenderTarget();
// Snip on second
fb.setRenderTarget(target2);
fb.sync();
x = convertX(fb, target2, 0);
y = convertY(fb, target2, target2.getHeight() - 0);
width = convertX(fb, target2, target2.getWidth());
height = -convertY(fb, target2, target2.getHeight());
fb.blit(texture, 0, 0, x, y, texture.getWidth(), texture.getHeight(), width, height, -1, false, null);
dummyWorld.renderScene(fb);
dummyWorld.draw(fb);
fb.display();
fb.removeRenderTarget();
firstRun = false;
}
}
// Snap

fb.clear(back);
world.renderScene(fb);
world.draw(fb);
world.getCamera().moveCamera(Camera.CAMERA_MOVEOUT, 0.1f);
world.getCamera().rotateCameraZ(0.01f);

// And blit the result
fb.blit(target, 0, 0, 10, 10, 256, 256, 400, 400, -1, false, null);
fb.blit(target2, 0, 0, 10, 510, 256, 256, 400, 400, -1, false, null);

fb.display();

if (System.currentTimeMillis() - time >= 1000) {
Logger.log(fps + "fps");
fps = 0;
time = System.currentTimeMillis();
}
fps++;
}

public int convertX(FrameBuffer buffer, Texture target, int x) {
return (int) ((float) x * ((float) buffer.getWidth() / (float) target.getWidth()));
}

public int convertY(FrameBuffer buffer, Texture target, int y) {
return (int) ((float) y * ((float) buffer.getHeight() / (float) target.getHeight()));
}
}
}

EgonOlsen

Should actually work and looks fine to me code wise, at least at first glance. What exactly does "twisted again" mean in this context? Screen shot?