@ -1,82 +1,239 @@
package com.jme3.renderer.android ;
package com.jme3.renderer.android ;
import android.graphics.Bitmap ;
import android.graphics.Bitmap ;
import android.opengl.ETC1 ;
import android.opengl.ETC1Util.ETC1Texture ;
import android.opengl.GLES20 ;
import android.opengl.GLES20 ;
import android.opengl.GLUtils ;
import android.opengl.GLUtils ;
import com.jme3.asset.AndroidImageInfo ;
import com.jme3.asset.AndroidImageInfo ;
import com.jme3.math.FastMath ;
import com.jme3.math.FastMath ;
import com.jme3.renderer.RendererException ;
import com.jme3.texture.Image ;
import com.jme3.texture.Image ;
import com.jme3.texture.Image.Format ;
import com.jme3.util.BufferUtils ;
import java.nio.ByteBuffer ;
import java.nio.ByteBuffer ;
import javax.microedition.khronos.opengles.GL10 ;
import java.util.logging.Level ;
import java.util.logging.Logger ;
public class TextureUtil {
public class TextureUtil {
private static void buildMipmap ( Bitmap bitmap ) {
private static final Logger logger = Logger . getLogger ( TextureUtil . class . getName ( ) ) ;
private static boolean ENABLE_COMPRESSION = true ;
private static boolean NPOT = false ;
private static boolean ETC1support = false ;
private static boolean DXT1 = false ;
private static boolean DEPTH24 = false ;
public static void loadTextureFeatures ( String extensionString ) {
ETC1support = extensionString . contains ( "GL_OES_compressed_ETC1_RGB8_texture" ) ;
DEPTH24 = extensionString . contains ( "GL_OES_depth24" ) ;
NPOT = extensionString . contains ( "GL_OES_texture_npot" ) | | extensionString . contains ( "GL_NV_texture_npot_2D_mipmap" ) ;
DXT1 = extensionString . contains ( "GL_EXT_texture_compression_dxt1" ) ;
logger . log ( Level . FINE , "Supports ETC1? {0}" , ETC1support ) ;
logger . log ( Level . FINE , "Supports DEPTH24? {0}" , DEPTH24 ) ;
logger . log ( Level . FINE , "Supports NPOT? {0}" , NPOT ) ;
logger . log ( Level . FINE , "Supports DXT1? {0}" , DXT1 ) ;
}
private static void buildMipmap ( Bitmap bitmap , boolean compress ) {
int level = 0 ;
int level = 0 ;
int height = bitmap . getHeight ( ) ;
int height = bitmap . getHeight ( ) ;
int width = bitmap . getWidth ( ) ;
int width = bitmap . getWidth ( ) ;
logger . log ( Level . FINEST , " - Generating mipmaps for bitmap using SOFTWARE" ) ;
GLES20 . glPixelStorei ( GLES20 . GL_UNPACK_ALIGNMENT , 1 ) ;
while ( height > = 1 | | width > = 1 ) {
while ( height > = 1 | | width > = 1 ) {
//First of all, generate the texture from our bitmap and set it to the according level
//First of all, generate the texture from our bitmap and set it to the according level
GLUtils . texImage2D ( GL10 . GL_TEXTURE_2D , level , bitmap , 0 ) ;
if ( compress ) {
logger . log ( Level . FINEST , " - Uploading LOD level {0} ({1}x{2}) with compression." , new Object [ ] { level , width , height } ) ;
uploadBitmapAsCompressed ( GLES20 . GL_TEXTURE_2D , level , bitmap ) ;
} else {
logger . log ( Level . FINEST , " - Uploading LOD level {0} ({1}x{2}) directly." , new Object [ ] { level , width , height } ) ;
GLUtils . texImage2D ( GLES20 . GL_TEXTURE_2D , level , bitmap , 0 ) ;
}
if ( height = = 1 | | width = = 1 ) {
if ( height = = 1 | | width = = 1 ) {
break ;
break ;
}
}
//Increase the mipmap level
//Increase the mipmap level
level + + ;
height / = 2 ;
height / = 2 ;
width / = 2 ;
width / = 2 ;
Bitmap bitmap2 = Bitmap . createScaledBitmap ( bitmap , width , height , true ) ;
Bitmap bitmap2 = Bitmap . createScaledBitmap ( bitmap , width , height , true ) ;
bitmap . recycle ( ) ;
// Recycle any bitmaps created as a result of scaling the bitmap.
// Do not recycle the original image (mipmap level 0)
if ( level ! = 0 ) {
bitmap . recycle ( ) ;
}
bitmap = bitmap2 ;
bitmap = bitmap2 ;
level + + ;
}
}
}
}
private static void uploadBitmapAsCompressed ( int target , int level , Bitmap bitmap ) {
if ( bitmap . hasAlpha ( ) ) {
logger . log ( Level . FINEST , " - Uploading bitmap directly. Cannot compress as alpha present." ) ;
GLUtils . texImage2D ( target , level , bitmap , 0 ) ;
} else {
// Convert to RGB565
int bytesPerPixel = 2 ;
Bitmap rgb565 = bitmap . copy ( Bitmap . Config . RGB_565 , true ) ;
// Put texture data into ByteBuffer
ByteBuffer inputImage = BufferUtils . createByteBuffer ( bitmap . getRowBytes ( ) * bitmap . getHeight ( ) ) ;
rgb565 . copyPixelsToBuffer ( inputImage ) ;
inputImage . position ( 0 ) ;
// Delete the copied RGB565 image
rgb565 . recycle ( ) ;
// Encode the image into the output bytebuffer
int encodedImageSize = ETC1 . getEncodedDataSize ( bitmap . getWidth ( ) , bitmap . getHeight ( ) ) ;
ByteBuffer compressedImage = BufferUtils . createByteBuffer ( encodedImageSize ) ;
ETC1 . encodeImage ( inputImage , bitmap . getWidth ( ) ,
bitmap . getHeight ( ) ,
bytesPerPixel ,
bytesPerPixel * bitmap . getWidth ( ) ,
compressedImage ) ;
// Delete the input image buffer
BufferUtils . destroyDirectBuffer ( inputImage ) ;
// Create an ETC1Texture from the compressed image data
ETC1Texture etc1tex = new ETC1Texture ( bitmap . getWidth ( ) , bitmap . getHeight ( ) , compressedImage ) ;
// Upload the ETC1Texture
if ( bytesPerPixel = = 2 ) {
int oldSize = ( bitmap . getRowBytes ( ) * bitmap . getHeight ( ) ) ;
int newSize = compressedImage . capacity ( ) ;
logger . log ( Level . FINEST , " - Uploading compressed image to GL, oldSize = {0}, newSize = {1}, ratio = {2}" , new Object [ ] { oldSize , newSize , ( float ) oldSize / newSize } ) ;
GLES20 . glCompressedTexImage2D ( target ,
level ,
ETC1 . ETC1_RGB8_OES ,
bitmap . getWidth ( ) ,
bitmap . getHeight ( ) ,
0 ,
etc1tex . getData ( ) . capacity ( ) ,
etc1tex . getData ( ) ) ;
// ETC1Util.loadTexture(target, level, 0, GLES20.GL_RGB,
// GLES20.GL_UNSIGNED_SHORT_5_6_5, etc1Texture);
// } else if (bytesPerPixel == 3) {
// ETC1Util.loadTexture(target, level, 0, GLES20.GL_RGB,
// GLES20.GL_UNSIGNED_BYTE, etc1Texture);
}
BufferUtils . destroyDirectBuffer ( compressedImage ) ;
}
}
/ * *
/ * *
* < code > uploadTextureBitmap < / code > uploads a native android bitmap
* < code > uploadTextureBitmap < / code > uploads a native android bitmap
* /
* /
public static void uploadTextureBitmap ( final int target , Bitmap bitmap , boolean generateMips , boolean powerOf2 ) {
public static void uploadTextureBitmap ( final int target , Bitmap bitmap , boolean needMips ) {
if ( ! powerOf2 ) {
boolean recycleBitmap = false ;
// Power of 2 images are not supported by this GPU.
if ( ! NPOT | | needMips ) {
// Power of 2 images are not supported by this GPU.
// OR
// Mipmaps were requested to be used.
// Currently OGLES does not support NPOT textures with mipmaps.
int width = bitmap . getWidth ( ) ;
int width = bitmap . getWidth ( ) ;
int height = bitmap . getHeight ( ) ;
int height = bitmap . getHeight ( ) ;
// If the image is not power of 2, rescale it
// If the image is not power of 2, rescale it
if ( ! FastMath . isPowerOfTwo ( width ) | | ! FastMath . isPowerOfTwo ( height ) ) {
if ( ! FastMath . isPowerOfTwo ( width ) | | ! FastMath . isPowerOfTwo ( height ) ) {
// scale to power of two, then recycle the old image.
// Scale to power of two .
width = FastMath . nearestPowerOfTwo ( width ) ;
width = FastMath . nearestPowerOfTwo ( width ) ;
height = FastMath . nearestPowerOfTwo ( height ) ;
height = FastMath . nearestPowerOfTwo ( height ) ;
logger . log ( Level . WARNING , " - Image is not POT, so scaling it to new resolution: {0}x{1}" , new Object [ ] { width , height } ) ;
Bitmap bitmap2 = Bitmap . createScaledBitmap ( bitmap , width , height , true ) ;
Bitmap bitmap2 = Bitmap . createScaledBitmap ( bitmap , width , height , true ) ;
bitmap . recycle ( ) ;
bitmap = bitmap2 ;
bitmap = bitmap2 ;
// Flag to indicate that bitmap
// should be recycled at the end.
recycleBitmap = true ;
}
}
}
}
if ( generateMips ) {
boolean willCompress = ENABLE_COMPRESSION & & ETC1support & & ! bitmap . hasAlpha ( ) ;
buildMipmap ( bitmap ) ;
if ( needMips & & willCompress ) {
// Image is compressed and mipmaps are desired, generate them
// using software.
buildMipmap ( bitmap , willCompress ) ;
} else {
if ( willCompress ) {
// Image is compressed but mipmaps are not desired, upload directly.
logger . log ( Level . FINEST , " - Uploading compressed bitmap. Mipmaps are not generated." ) ;
uploadBitmapAsCompressed ( target , 0 , bitmap ) ;
} else {
// Image is not compressed, mipmaps may or may not be desired.
logger . log ( Level . FINEST , " - Uploading bitmap directly.{0}" ,
( needMips ?
" Mipmaps will be generated in HARDWARE" :
" Mipmaps are not generated." ) ) ;
GLUtils . texImage2D ( target , 0 , bitmap , 0 ) ;
if ( needMips ) {
// No pregenerated mips available,
// generate from base level if required
GLES20 . glGenerateMipmap ( target ) ;
}
}
}
if ( recycleBitmap ) {
bitmap . recycle ( ) ;
}
}
public static void uploadTextureAny ( Image img , int target , int index , boolean needMips ) {
if ( img . getEfficentData ( ) instanceof AndroidImageInfo ) {
logger . log ( Level . FINEST , " === Uploading image {0}. Using BITMAP PATH === " , img ) ;
// If image was loaded from asset manager, use fast path
AndroidImageInfo imageInfo = ( AndroidImageInfo ) img . getEfficentData ( ) ;
uploadTextureBitmap ( target , imageInfo . getBitmap ( ) , needMips ) ;
} else {
} else {
GLUtils . texImage2D ( target , 0 , bitmap , 0 ) ;
logger . log ( Level . FINEST , " === Uploading image {0}. Using BUFFER PATH === " , img ) ;
//bitmap.recycle();
boolean wantGeneratedMips = needMips & & ! img . hasMipmaps ( ) ;
if ( wantGeneratedMips & & img . getFormat ( ) . isCompressed ( ) ) {
logger . log ( Level . WARNING , "Generating mipmaps is only"
+ " supported for Bitmap based or non-compressed images!" ) ;
}
// Upload using slower path
logger . log ( Level . FINEST , " - Uploading bitmap directly.{0}" ,
( wantGeneratedMips ?
" Mipmaps will be generated in HARDWARE" :
" Mipmaps are not generated." ) ) ;
uploadTexture ( img , target , index ) ;
// Image was uploaded using slower path, since it is not compressed,
// then compress it
if ( wantGeneratedMips ) {
// No pregenerated mips available,
// generate from base level if required
GLES20 . glGenerateMipmap ( target ) ;
}
}
}
}
}
private static void unsupportedFormat ( Format fmt ) {
throw new UnsupportedOperationException ( "The image format '" + fmt + "' is unsupported by the video hardware." ) ;
}
public static void uploadTexture ( Image img ,
private static void uploadTexture ( Image img ,
int target ,
int target ,
int index ,
int index ) {
int border ,
boolean tdc ,
boolean generateMips ,
boolean powerOf2 ) {
if ( img . getEfficentData ( ) instanceof AndroidImageInfo ) {
if ( img . getEfficentData ( ) instanceof AndroidImageInfo ) {
// If image was loaded from asset manager, use fast path
throw new RendererException ( "This image uses efficient data. "
AndroidImageInfo imageInfo = ( AndroidImageInfo ) img . getEfficentData ( ) ;
+ "Use uploadTextureBitmap instead." ) ;
uploadTextureBitmap ( target , imageInfo . getBitmap ( ) , generateMips , powerOf2 ) ;
return ;
}
}
// Otherwise upload image directly.
// Otherwise upload image directly.
@ -92,7 +249,16 @@ public class TextureUtil {
int width = img . getWidth ( ) ;
int width = img . getWidth ( ) ;
int height = img . getHeight ( ) ;
int height = img . getHeight ( ) ;
int depth = img . getDepth ( ) ;
int depth = img . getDepth ( ) ;
if ( ! NPOT ) {
// Check if texture is POT
if ( ! FastMath . isPowerOfTwo ( width ) | | width ! = height ) {
throw new RendererException ( "Non-power-of-2 textures "
+ "are not supported by the video hardware "
+ "and no scaling path available for image: " + img ) ;
}
}
boolean compress = false ;
boolean compress = false ;
int format = - 1 ;
int format = - 1 ;
int dataType = - 1 ;
int dataType = - 1 ;
@ -150,6 +316,22 @@ public class TextureUtil {
format = GLES20 . GL_DEPTH_COMPONENT ;
format = GLES20 . GL_DEPTH_COMPONENT ;
dataType = GLES20 . GL_UNSIGNED_BYTE ;
dataType = GLES20 . GL_UNSIGNED_BYTE ;
break ;
break ;
case DXT1 :
if ( ! DXT1 ) {
unsupportedFormat ( fmt ) ;
}
format = 0x83F0 ;
dataType = GLES20 . GL_UNSIGNED_BYTE ;
compress = true ;
break ;
case DXT1A :
if ( ! DXT1 ) {
unsupportedFormat ( fmt ) ;
}
format = 0x83F1 ;
dataType = GLES20 . GL_UNSIGNED_BYTE ;
compress = true ;
break ;
default :
default :
throw new UnsupportedOperationException ( "Unrecognized format: " + fmt ) ;
throw new UnsupportedOperationException ( "Unrecognized format: " + fmt ) ;
}
}
@ -170,18 +352,18 @@ public class TextureUtil {
// XXX: might want to change that when support
// XXX: might want to change that when support
// of more than paletted compressions is added..
// of more than paletted compressions is added..
/// NOTE: Doesn't support mipmaps
/// NOTE: Doesn't support mipmaps
if ( compress ) {
// if (compress){
data . clear ( ) ;
// data.clear();
GLES20 . glCompressedTexImage2D ( target ,
// GLES20.glCompressedTexImage2D(target,
1 - mipSizes . length ,
// 1 - mipSizes.length,
format ,
// format,
width ,
// width,
height ,
// height,
0 ,
// 0,
data . capacity ( ) ,
// data.capacity(),
data ) ;
// data);
return ;
// return;
}
// }
for ( int i = 0 ; i < mipSizes . length ; i + + ) {
for ( int i = 0 ; i < mipSizes . length ; i + + ) {
int mipWidth = Math . max ( 1 , width > > i ) ;
int mipWidth = Math . max ( 1 , width > > i ) ;