Why does jPCT-AE need to keep the texture in the VM memory?

Started by AeroShark333, December 01, 2016, 04:59:09 AM

Previous topic - Next topic

EgonOlsen

No, it's not possible to stream them into the GPU directly. preWarm() uploads every texture that the texture manager knows when you call the method. It has to be called in onDraw(), of course because it has to have access to a current, active GL context.

AeroShark333

#16
What will preWarm() do if a Texture has already been uploaded to the GPU? Nothing I suppose?
And what about Textures that will only be blit? Those don't need to be uploaded, do they? :|
Is there some way to not make them get uploaded in case they do?

_________________________

I wondered about something else as well:
// Test zone
IntBuffer max = IntBuffer.allocate(1);
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, max);
Log.d("e3d-tag","Texture units (shader): " + max.get());

max = IntBuffer.allocate(1);
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, max);
Log.d("e3d-tag","Texture size: " + max.get());

max = IntBuffer.allocate(1);
GLES20.glGetIntegerv(GLES20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, max);
Log.d("e3d-tag","Texture units (shaders): " + max.get());


Output:
D/e3d-tag(31156): Texture units (shader): 16
D/e3d-tag(31156): Texture size: 16384
D/e3d-tag(31156): Texture units (shaders): 96

So I read somewhere that jPCT-AE supports up to 8192x8192 pixels textures; what happens if I try 16384x16383 since my device seems to support it...?
And I don't understand these texture units values...
The first output line is for the texture units available to the fragment shader I thought...
And the third line for the texture units available for the fragment shader AND vertex shader combined, right?
So does this apply for one Object3D or for all?
And how does it work when a Texture is shared between Object3D's, is there something I need to keep in mind?

_________________________

And another side question: Will there be support for OpenGLES 3.0 someday maybe?

EgonOlsen

preWarm() uploads anything that hasn't been uploaded yet and that is known to the TextureManager. Textures for blitting have to be uploaded as well but preWarm won't do this unless you've added them to the manager (what you can do, it doesn't hurt).
16384*16384 should work in theory...but honestly...why would you want to do this? Just do the math: A 16384 textures needs 1 GB of memory without mipmaps and without the actual data in the VM's memory. Add those, and you are somewhere around 2.5 GB for ONE texture. That's just insane.

AeroShark333

#18
I see, thanks for answering :)
And eh, nevermind the 16k resolution textures...
I guess I just got curious there

I have a small request however...
May I have the source code of the LensFlare class, learn from it, modify it and use it? (I'll probably end up writing my own class then)
So why?...
Well there's a few things I want to change/add I guess:
-> I want to use 5 textures in total
-> I don't want the burst texture to be repeated in the lens flare (that's where the 5th texture comes in)
-> I want to give the burst it's own scaling which isn't done with the global scaling
-> I don't really know how to get the 2D coordinates for blitting from this 3D world
-> I want to use 1/4th part of a 'complete texture'

My lens flare images are all spherical so...
I basically could just use 1/4th of this texture and blit it 4 times (by flipping the blit). (relying on the assumption that the LensFlare class uses blits and not it's own Object3D's)

And Merry Christmas by the way :D

EgonOlsen

Sure, here it is:


package com.threed.jpct.util;

import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Interact2D;
import com.threed.jpct.Object3D;
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;

/**
* A simple lens flare effect. You need four different textures for this, one burst and three halos. How
* to create/get these textures is up to you. jPCT doesn't come with any.
* @author EgonOlsen
*/
public class LensFlare implements java.io.Serializable {

private static final long serialVersionUID = 1L;

private Texture burst=null;
private Texture halo1=null;
private Texture halo2=null;
private Texture halo3=null;
private SimpleVector sunPos=null;

private Texture[] types=new Texture[7];
private float[][] scales=new float[7][2];
private float globalScale=1;
private int trans=1;
private boolean cover=true;
private float maxDistance=-1;
private boolean revertDirection=false;

private boolean visible=false;
private SimpleVector light2D=null;

private SimpleVector tmp1=new SimpleVector();
private SimpleVector tmp2=new SimpleVector();
private SimpleVector tmp3=new SimpleVector();

/**
* Create a new lens flare for a light source.
* @param lightPos the position of the light source
* @param burst the name of the burst texture as added to the TextureManager
* @param halo1 the name of the first halo texture as added to the TextureManager
* @param halo2 the name of the second halo as added to the TextureManager
* @param halo3 the name of the third halo texture added to the TextureManager
*/
public LensFlare(SimpleVector lightPos, String burst, String halo1, String halo2, String halo3) {
TextureManager tm=TextureManager.getInstance();
this.burst=tm.getTexture(burst);
this.halo1=tm.getTexture(halo1);
this.halo2=tm.getTexture(halo2);
this.halo3=tm.getTexture(halo3);
this.sunPos=new SimpleVector(lightPos);

types[0]=this.burst;
types[1]=this.halo1;
types[2]=this.burst;
types[3]=this.halo2;
types[4]=this.burst;
types[5]=this.halo3;
types[6]=this.burst;

scales[0][0]=1;
scales[0][1]=1;
scales[1][0]=2;
scales[1][1]=0.5f;
scales[2][0]=3;
scales[2][1]=0.25f;
scales[3][0]=8;
scales[3][1]=1;
scales[4][0]=-2;
scales[4][1]=0.5f;
scales[5][0]=-4;
scales[5][1]=0.25f;
scales[6][0]=-5.5f;
scales[6][1]=0.25f;
}

/**
* Sets the transparency of the effect.
* @param trans the transparency. 0 is lowest,
*/
public void setTransparency(int trans) {
this.trans=trans;
}

/**
* Sets a new light position.
* @param lightPos the new position
*/
public void setLightPosition(SimpleVector lightPos) {
sunPos.set(lightPos);
}

/**
* Sets the global scale of the effect.
* @param scale the scale
*/
public void setGlobalScale(float scale) {
globalScale=scale;
}

/**
* If true (default), all geometry that is a potential collider hides the effect if it's located in
* a direct line between the camera and the light source.
* @param hides should geometry hide it or not?
*/
public void setHiding(boolean hides) {
cover=hides;
}

/**
* If hiding is enabled, this value specifies how far away from the camera a polygon can maximally be
* to be considered as a blocker. Anything farer away can't hide the effect. Lowering this value can improve
* performance but may lead to flares where non should be. -1 is default, which means no limits.
* @param distance the distance
*/
public void setMaximumDistance(float distance) {
this.maxDistance=distance;
}

/**
* If hiding is enabled, the visibility calculations can be done camera->light (default) or light->camera. Depending
* on the scene, one or the other will be faster.
* @param lightToCam do it light->camera or vice versa
*/
public void setDirection(boolean lightToCam) {
revertDirection=lightToCam;
}

/**
* Updates the lens flare. Skipping this method and calling render only will cause the lens flare to remain static.
* This method should be called if either the camera or, if hiding is enabled, the hiding objects are moving.
* @param buffer the frame buffer
* @param world the world
*/
public void update(FrameBuffer buffer, World world) {
Camera cam=world.getCamera();
light2D=Interact2D.project3D2D(cam, buffer, sunPos, tmp3);
visible=true;
if (cover) {
SimpleVector camPos=cam.getPosition(tmp1);
if (!revertDirection) {
// From camera to light
SimpleVector delta=camPos;
tmp2.set(camPos);
delta.scalarMul(-1);
delta.add(sunPos);
float dlen=delta.length();
float dist=world.calcMinDistance(tmp2, delta.normalize(delta), maxDistance!=-1?Math.min(maxDistance, dlen*1.05f):dlen*1.05f);
//System.out.println("1: "+ dist+"/"+(dlen-5));
visible=(dist==Object3D.COLLISION_NONE || dist>dlen-5);
} else {
// From light to camera
tmp2.set(sunPos);
tmp2.scalarMul(-1);
SimpleVector delta=camPos;
delta.add(tmp2);
float dlen=delta.length();
float dist=world.calcMinDistance(sunPos, delta.normalize(delta), maxDistance!=-1?Math.min(maxDistance, dlen*1.05f):dlen*1.05f);
visible=(dist==Object3D.COLLISION_NONE || dist>dlen-5);
//System.out.println("2: "+ dist+"/"+(dlen-5));
}
}
}

/**
* Renders the effect.
* @param buffer the frame buffer
*/
public void render(FrameBuffer buffer) {

if (light2D!=null && visible) {
SimpleVector lp=tmp1;
lp.set(light2D);
float mx=buffer.getCenterX();
float my=buffer.getCenterY();
lp.z=0;
SimpleVector cp=tmp2;
cp.set(mx,my,0);
cp.scalarMul(-1);
lp.add(cp);
SimpleVector dir=lp;
float len=dir.length();
dir=dir.normalize(dir);
SimpleVector d=tmp2;
d.set(0, 0, 0);

for (int i=0; i<types.length; i++) {
d.set(dir);
Texture t=types[i];
float l=scales[i][0];
float scale=scales[i][1]*globalScale;
d.scalarMul((1f/l)*len);
int tw=t.getWidth();
int th=t.getHeight();
int x=(int)(d.x-((tw>>1)*scale));
int y=(int)(d.y-((th>>1)*scale));
buffer.blit(t, 0, 0, x+(int)mx, y+(int)my, tw, th, (int)((float)tw*scale), (int)((float)th*scale), trans, true, RGBColor.WHITE);
}
}
}
}


AeroShark333

My version:
import com.threed.jpct.Camera;
import com.threed.jpct.FrameBuffer;
import com.threed.jpct.Interact2D;
import com.threed.jpct.Object3D;
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;

/**
* A simple lens flare effect. You need five different textures for this, one burst and four halos. How
* to create/get these textures is up to you. jPCT doesn't come with any.
* @author EgonOlsen & AeroShark333
*/
public class CustomLensFlare implements java.io.Serializable {
private static final long serialVersionUID = 1L;

private final SimpleVector sunPos;

private final Texture[] types;
private final float[][] scales;

private float globalScale=1;
private float burstScale=1;
private int trans=1;
private int burstTrans = 1;

private boolean cover=true;
private float maxDistance=-1;
private boolean revertDirection=false;

private boolean visible=false;
private SimpleVector light2D=null;

private final SimpleVector tmp1;
private final SimpleVector tmp2;
private final SimpleVector tmp3;

/**
* Create a new lens flare for a light source.
* Note: call initializeLensFlare(String texturenames...) before using this LensFlare
* @param lightPos the position of the light source
*/
public CustomLensFlare(final SimpleVector lightPos) {
this.sunPos=new SimpleVector(lightPos);
this.types=new Texture[7];

scales = new float[7][2];
scales[0][0]=1;
scales[0][1]=1;
scales[1][0]=2;
scales[1][1]=0.5f;
scales[2][0]=3;
scales[2][1]=0.25f;
scales[3][0]=8;
scales[3][1]=1;
scales[4][0]=-2;
scales[4][1]=0.5f;
scales[5][0]=-4;
scales[5][1]=0.25f;
scales[6][0]=-5.5f;
scales[6][1]=0.25f;

tmp1=new SimpleVector();
tmp2=new SimpleVector();
tmp3=new SimpleVector();
}
/**
* Create a new lens flare for a light source.
* @param lightPos the position of the light source
* @param burst the name of the burst texture as added to the TextureManager
* @param halo1 the name of the first halo texture as added to the TextureManager
* @param halo2 the name of the second halo as added to the TextureManager
* @param halo3 the name of the third halo texture added to the TextureManager
* @param halo4 the name of the fourth (repeating) halo texture added to the TextureManager
*/
public CustomLensFlare(final SimpleVector lightPos, final String burst, final String halo1, final String halo2, final String halo3, final String halo4) {
this(lightPos);
this.initializeLensFlare(burst, halo1, halo2, halo3, halo4);
}

/**
* Call this when you are sure the textures needed for this lensflare have been set
* @param burst the name of the burst texture as added to the TextureManager
* @param halo1 the name of the first halo texture as added to the TextureManager
* @param halo2 the name of the second halo as added to the TextureManager
* @param halo3 the name of the third halo texture added to the TextureManager
* @param halo4 the name of the fourth (repeating) halo texture added to the TextureManager
*/
public void initializeLensFlare(final String burst, final String halo1, final String halo2, final String halo3, final String halo4){
final TextureManager tm=TextureManager.getInstance();
final Texture repeatedHalo=tm.getTexture(halo4);

types[0]=tm.getTexture(burst);
types[1]=tm.getTexture(halo1);
types[2]=repeatedHalo;
types[3]=tm.getTexture(halo2);
types[4]=repeatedHalo;
types[5]=tm.getTexture(halo3);
types[6]=repeatedHalo;
}

/**
* Sets the transparency of the effect.
* @param trans the transparency. 0 is lowest,
*/
public void setGlobalTransparency(int trans) {
this.trans=trans;
}

/**
* Sets the transparency of the burst effect.
* @param trans the transparency. 0 is lowest,
*/
public void setBurstTransparency(int trans) {
this.burstTrans=trans;
}


/**
* Sets a new light position.
* @param lightPos the new position
*/
public void setLightPosition(SimpleVector lightPos) {
sunPos.set(lightPos);
}

/**
* Sets the burst scale of the effect.
* @param scale the scale
*/
public void setBurstScale(float scale) {
burstScale=scale;
}

/**
* Sets the global scale of the effect.
* @param scale the scale
*/
public void setGlobalScale(float scale) {
globalScale=scale;
}

/**
* If true (default), all geometry that is a potential collider hides the effect if it's located in
* a direct line between the camera and the light source.
* @param hides should geometry hide it or not?
*/
public void setHiding(boolean hides) {
cover=hides;
}

/**
* If hiding is enabled, this value specifies how far away from the camera a polygon can maximally be
* to be considered as a blocker. Anything farer away can't hide the effect. Lowering this value can improve
* performance but may lead to flares where non should be. -1 is default, which means no limits.
* @param distance the distance
*/
public void setMaximumDistance(float distance) {
this.maxDistance=distance;
}

/**
* If hiding is enabled, the visibility calculations can be done camera->light (default) or light->camera. Depending
* on the scene, one or the other will be faster.
* @param lightToCam do it light->camera or vice versa
*/
public void setDirection(boolean lightToCam) {
revertDirection=lightToCam;
}

/**
* Updates the lens flare. Skipping this method and calling render only will cause the lens flare to remain static.
* This method should be called if either the camera or, if hiding is enabled, the hiding objects are moving.
* @param buffer the frame buffer
* @param world the world
*/
public void update(FrameBuffer buffer, World world) {
Camera cam=world.getCamera();
light2D=Interact2D.project3D2D(cam, buffer, sunPos, tmp3);
visible=true;
if (cover) {
SimpleVector camPos=cam.getPosition(tmp1);
if (!revertDirection) {
// From camera to light
SimpleVector delta=camPos;
tmp2.set(camPos);
delta.scalarMul(-1);
delta.add(sunPos);
float dlen=delta.length();
float dist=world.calcMinDistance(tmp2, delta.normalize(delta), maxDistance!=-1?Math.min(maxDistance, dlen*1.05f):dlen*1.05f);
//System.out.println("1: "+ dist+"/"+(dlen-5));
visible=(dist==Object3D.COLLISION_NONE || dist>dlen-5);
} else {
// From light to camera
tmp2.set(sunPos);
tmp2.scalarMul(-1);
SimpleVector delta=camPos;
delta.add(tmp2);
float dlen=delta.length();
float dist=world.calcMinDistance(sunPos, delta.normalize(delta), maxDistance!=-1?Math.min(maxDistance, dlen*1.05f):dlen*1.05f);
visible=(dist==Object3D.COLLISION_NONE || dist>dlen-5);
//System.out.println("2: "+ dist+"/"+(dlen-5));
}
}
}

/**
* Renders the effect.
* @param buffer the frame buffer
*/
public void render(FrameBuffer buffer) {

if (light2D!=null && visible) {
SimpleVector lp=tmp1;
lp.set(light2D);
float mx=buffer.getCenterX();
float my=buffer.getCenterY();
lp.z=0;
SimpleVector cp=tmp2;
cp.set(mx,my,0);
cp.scalarMul(-1);
lp.add(cp);
SimpleVector dir=lp;
float len=dir.length();
dir=dir.normalize(dir);
SimpleVector d=tmp2;
d.set(0, 0, 0);

for (int i=0; i<types.length; i++) {
d.set(dir);
Texture t=types[i];
float l=scales[i][0];
float scale=scales[i][1]*(i==0? burstScale:globalScale);
int trans2 = (i==0? this.burstTrans:this.trans);
d.scalarMul((1f/l)*len);
int tw=t.getWidth();
int th=t.getHeight();
int x=(int)(d.x-((tw>>1)*scale))+(int)mx;
int y=(int)(d.y-((th>>1)*scale))+(int)my;
int dw = (int)((float)tw*scale*0.5f);
int dh = (int)((float)th*scale*0.5f);
// upper-left
buffer.blit(t, 0, 0, x, y, tw, th, dw, dh, trans2, true, RGBColor.WHITE);
// upper-right
buffer.blit(t, 0, 0, x+2*dw, y, tw, th, -dw, dh, trans2, true, RGBColor.WHITE);
// bottom-left
buffer.blit(t, 0, 0, x, y+2*dh, tw, th, dw, -dh, trans2, true, RGBColor.WHITE);
// bottom-right
buffer.blit(t, 0, 0, x+2*dw, y+2*dh, tw, th, -dw, -dh, trans2, true, RGBColor.WHITE);
}
}
}
}


I hope it's okay to share this; if not then I guess you can delete my post :P
This modified class should be suitable for circular shaped lensflare textures.
So your texture images can be reduced to this part of the circular texture image: http://etc.usf.edu/clipart/40500/40550/pie_01-04n_40550_lg.gif :)
I guess the original lens flare textures don't take up that much memory anyway but oh well...

AeroShark333

I wanted to optimize texture loading so I tried splitting the texture into 4 parts.
So a 8192x4096 texture now is 4 * 4096x2048 textures.

So why split?: Because I'll be uploading the texture after it has loaded and textures use a lot in VM memory and I thought this would be a nice solution to split the textures, so the VM memory doesn't go up as much as with a complete texture...

I added these 4 textures to the Object3D and in the shader I'd use the texcoords to select which texture to use (and change the texcoords so it draws the texture properly on the Object3D's vertices)...
However, the results seem kind of ugly and it's not as I hoped it would be... Using a complete texture in the shader looked better than 4 split textures. texture.setClamping(false) helped but it's still kind of visible on the Object3D that the textures are split...

Is there some way to merge these 4 textures into 1 texture without the Bitmap stuff in Android since that obviously just uses memory? The textures are immediately uploaded after loading so the VM memory only 'spikes' up when the Bitmap instance is still used for uploading...
I suppose jPCT and OpenGL see these textures as texture layers but actually they're just next to each other.

EgonOlsen

I'm not sure if I understand you your "merge" them in the shader!?

AeroShark333

#23
Basically like in this fragment shader:
precision highp float;
precision highp int;

uniform sampler2D textureUnit0;
uniform sampler2D textureUnit1;
uniform sampler2D textureUnit2;
uniform sampler2D textureUnit3;
varying vec2 texCoord0;

void main() {
float xVal = texCoord0.x;
float yVal = texCoord0.y;

vec4 base;

if(xVal < 0.5){
// tex0 or tex2
if(yVal < 0.5){
// tex0
base = texture2D(textureUnit0, vec2(xVal*2.0,yVal*2.0));
}else{
base = texture2D(textureUnit2, vec2(xVal*2.0, yVal*2.0 - 1.0));
}
}else{
// tex1 or tex3
if(yVal < 0.5){
// tex1
base = texture2D(textureUnit1, vec2(xVal*2.0 - 1.0,yVal*2.0));
}else{
// tex3
base = texture2D(textureUnit3, vec2(xVal*2.0 - 1.0,yVal*2.0 - 1.0));
}
}

gl_FragColor = base;
}

It works but it kind of looks ugly around the borders of the textures...
I actually used texture.setClamping(true) so not 'false' to make it look better around the borders which kind of worked.
But it only looks better for higher resolution textures.
Let's say I have 4 lower resolution textures, then these borders become visible again which I don't really want. (it should just work like a single texture)

See attachment for clarification :)

EgonOlsen

Yes, that will look ugly, because the texture filtering will be wrong at the borders. I guess you would have to reimplement bilinear filtering (at least) in your shader between different texture stages to fix this...which would be just insane.
Short: I wouldn't do it that way in the first place.

AeroShark333

Is there another solution? To load textures this way to not hit high VM memory usage and still get the textures to work properly on the Object3D's vertices?

EgonOlsen

Yes...just don't use such huge textures. 8192*4096 means 128 MB of texture data on the GPU alone (plus mipmaps). This won't work on older Android versions anyway, so why bother with it? What do you need sich huge texture for?

AeroShark333

Well for example this texture: https://commons.wikimedia.org/wiki/File:ESO_-_Milky_Way.jpg
On lower resolution it looks uglier than with 8192x4096...
So I suppose that's why I thought splitting it into 4 parts would reduce the maximum VM memory usage at a point. (which does happen; but the result is not satisfactory...)