* Removed all "verbose logging" parts of OGLESShaderRenderer, as it made the code hard to read. This sort of logging functionality should be done in a "GL Wrapper" sort of class instead.

* The AndroidImageInfo.notifyBitmapUploaded() method is now called at the end of the upload as expected 
 * Huge overhaul of TextureUtil. It automatically compresses textures to ETC1 if selected by user. In addition, it will use hardware mipmap generation if possible, and fallback to software if not (e.g. for compressed textures)
 * Add support for DXT1 format (usually supported on NVIDIA Tegra devices). The BrightSky.dds skybox now works on Android if the device supports it.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9410 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
Sha..rd 13 years ago
parent 4be1d2be1e
commit c34189dfe5
  1. 744
      engine/src/android/com/jme3/renderer/android/OGLESShaderRenderer.java
  2. 248
      engine/src/android/com/jme3/renderer/android/TextureUtil.java

@ -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);
// 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.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;
if (!NPOT || needMips) {
// Power of 2 images are not supported by this GPU. // 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 { } 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); GLUtils.texImage2D(target, 0, bitmap, 0);
//bitmap.recycle(); if (needMips) {
// No pregenerated mips available,
// generate from base level if required
GLES20.glGenerateMipmap(target);
}
} }
} }
public static void uploadTexture(Image img, if (recycleBitmap) {
int target, bitmap.recycle();
int index, }
int border, }
boolean tdc,
boolean generateMips,
boolean powerOf2){
public static void uploadTextureAny(Image img, int target, int index, boolean needMips) {
if (img.getEfficentData() instanceof AndroidImageInfo){ 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 // If image was loaded from asset manager, use fast path
AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData(); AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData();
uploadTextureBitmap(target, imageInfo.getBitmap(), generateMips, powerOf2); uploadTextureBitmap(target, imageInfo.getBitmap(), needMips);
return; } else {
logger.log(Level.FINEST, " === Uploading image {0}. Using BUFFER PATH === ", img);
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.");
}
private static void uploadTexture(Image img,
int target,
int index){
if (img.getEfficentData() instanceof AndroidImageInfo){
throw new RendererException("This image uses efficient data. "
+ "Use uploadTextureBitmap instead.");
} }
// Otherwise upload image directly. // Otherwise upload image directly.
@ -93,6 +250,15 @@ public class TextureUtil {
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);

Loading…
Cancel
Save