Explorar o código

Android texture util now supports uploading a sub texture to the GPU, even as a bitmap.
This makes Nifty batch rendering work on android.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10488 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

rem..om %!s(int64=12) %!d(string=hai) anos
pai
achega
4d91089b3a
Modificáronse 1 ficheiros con 258 adicións e 185 borrados
  1. 258 185
      engine/src/android/com/jme3/renderer/android/TextureUtil.java

+ 258 - 185
engine/src/android/com/jme3/renderer/android/TextureUtil.java

@@ -18,14 +18,13 @@ import java.util.logging.Logger;
 public class TextureUtil {
 
     private static final Logger logger = Logger.getLogger(TextureUtil.class.getName());
-    
     //TODO Make this configurable through appSettings
     public 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");
@@ -36,21 +35,21 @@ public class TextureUtil {
         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 height = bitmap.getHeight();
         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) {
             //First of all, generate the texture from our bitmap and set it to the according level
             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);
+                uploadBitmapAsCompressed(GLES20.GL_TEXTURE_2D, level, bitmap, false, 0, 0);
             } 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);
@@ -67,20 +66,26 @@ public class TextureUtil {
 
             // Recycle any bitmaps created as a result of scaling the bitmap.
             // Do not recycle the original image (mipmap level 0)
-            if (level != 0){
+            if (level != 0) {
                 bitmap.recycle();
             }
-            
+
             bitmap = bitmap2;
-            
+
             level++;
         }
     }
 
-    private static void uploadBitmapAsCompressed(int target, int level, Bitmap bitmap) {
+    private static void uploadBitmapAsCompressed(int target, int level, Bitmap bitmap, boolean subTexture, int x, int y) {
         if (bitmap.hasAlpha()) {
             logger.log(Level.FINEST, " - Uploading bitmap directly. Cannot compress as alpha present.");
-            GLUtils.texImage2D(target, level, bitmap, 0);
+            if (subTexture) {
+                GLUtils.texSubImage2D(target, level, x, y, bitmap);
+                checkGLError();
+            } else {
+                GLUtils.texImage2D(target, level, bitmap, 0);
+                checkGLError();
+            }
         } else {
             // Convert to RGB565
             int bytesPerPixel = 2;
@@ -97,15 +102,15 @@ public class TextureUtil {
             // 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);
-            
+            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);
 
@@ -113,55 +118,53 @@ public class TextureUtil {
             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());
-                
+                logger.log(Level.FINEST, " - Uploading compressed image to GL, oldSize = {0}, newSize = {1}, ratio = {2}", new Object[]{oldSize, newSize, (float) oldSize / newSize});
+                if (subTexture) {
+                    GLES20.glCompressedTexSubImage2D(target,
+                            level,
+                            x, y,
+                            bitmap.getWidth(),
+                            bitmap.getHeight(),
+                            ETC1.ETC1_RGB8_OES,
+                            etc1tex.getData().capacity(),
+                            etc1tex.getData());
+                    checkGLError();
+                } else {
+                    GLES20.glCompressedTexImage2D(target,
+                            level,
+                            ETC1.ETC1_RGB8_OES,
+                            bitmap.getWidth(),
+                            bitmap.getHeight(),
+                            0,
+                            etc1tex.getData().capacity(),
+                            etc1tex.getData());
+                    checkGLError();
+                }
+
 //                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
      */
     public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips) {
+        uploadTextureBitmap(target, bitmap, needMips, false, 0, 0);
+    }
+
+    /**
+     * <code>uploadTextureBitmap</code> uploads a native android bitmap
+     */
+    public static void uploadTextureBitmap(final int target, Bitmap bitmap, boolean needMips, boolean subTexture, int x, int y) {
         boolean recycleBitmap = false;
-        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 height = bitmap.getHeight();
-            
-            // If the image is not power of 2, rescale it
-            if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(height)) {
-                // Scale to power of two.
-                width  = FastMath.nearestPowerOfTwo(width);
-                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;
-                
-                // Flag to indicate that bitmap
-                // should be recycled at the end.
-                recycleBitmap = true; 
-            }
-        }
+        //TODO, maybe this should raise an exception when NPOT is not supported
 
         boolean willCompress = ENABLE_COMPRESSION && ETC1support && !bitmap.hasAlpha();
         if (needMips && willCompress) {
@@ -172,29 +175,39 @@ public class TextureUtil {
             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);
+                uploadBitmapAsCompressed(target, 0, bitmap, subTexture, x, y);
+
             } 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);
+                logger.log(Level.FINEST, " - Uploading bitmap directly.{0}",
+                        (needMips
+                        ? " Mipmaps will be generated in HARDWARE"
+                        : " Mipmaps are not generated."));
+                if (subTexture) {
+                    System.err.println("x : " + x + " y :" + y + " , " + bitmap.getWidth() + "/" + bitmap.getHeight());
+                    GLUtils.texSubImage2D(target, 0, x, y, bitmap);
+                    checkGLError();
+                } else {
+                    GLUtils.texImage2D(target, 0, bitmap, 0);
+                    checkGLError();
+                }
+
                 if (needMips) {
                     // No pregenerated mips available,
                     // generate from base level if required
                     GLES20.glGenerateMipmap(target);
+                    checkGLError();
                 }
             }
         }
-        
+
         if (recycleBitmap) {
             bitmap.recycle();
         }
     }
-    
+
     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
             AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData();
@@ -206,14 +219,14 @@ public class TextureUtil {
                 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."));
+            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) {
@@ -223,48 +236,14 @@ public class TextureUtil {
             }
         }
     }
-    
+
     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. 
-        // Prefer to only use power of 2 textures here to avoid errors.
-        Image.Format fmt = img.getFormat();
-        ByteBuffer data;
-        if (index >= 0 || img.getData() != null && img.getData().size() > 0){
-            data = img.getData(index);
-        }else{
-            data = null;
-        }
-
-        int width = img.getWidth();
-        int height = img.getHeight();
-        int depth = img.getDepth();
-        
-        if (!NPOT) {
-            // Check if texture is POT
-            if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(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;
-        int format = -1;
-        int dataType = -1;
-
-        switch (fmt){
+    private static AndroidGLImageFormat getImageFormat(Format fmt) throws UnsupportedOperationException {
+        AndroidGLImageFormat imageFormat = new AndroidGLImageFormat();
+        switch (fmt) {
             case RGBA16:
             case RGB16:
             case RGB10:
@@ -273,69 +252,110 @@ public class TextureUtil {
             case Alpha16:
             case Depth32:
             case Depth32F:
-                throw new UnsupportedOperationException("The image format '" 
+                throw new UnsupportedOperationException("The image format '"
                         + fmt + "' is not supported by OpenGL ES 2.0 specification.");
             case Alpha8:
-                format = GLES20.GL_ALPHA;
-                dataType = GLES20.GL_UNSIGNED_BYTE;                
+                imageFormat.format = GLES20.GL_ALPHA;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
                 break;
             case Luminance8:
-                format = GLES20.GL_LUMINANCE;
-                dataType = GLES20.GL_UNSIGNED_BYTE;
+                imageFormat.format = GLES20.GL_LUMINANCE;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
                 break;
             case Luminance8Alpha8:
-                format = GLES20.GL_LUMINANCE_ALPHA;
-                dataType = GLES20.GL_UNSIGNED_BYTE;
+                imageFormat.format = GLES20.GL_LUMINANCE_ALPHA;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
                 break;
             case RGB565:
-                format = GLES20.GL_RGB;
-                dataType = GLES20.GL_UNSIGNED_SHORT_5_6_5;
+                imageFormat.format = GLES20.GL_RGB;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT_5_6_5;
                 break;
             case ARGB4444:
-                format = GLES20.GL_RGBA4;
-                dataType = GLES20.GL_UNSIGNED_SHORT_4_4_4_4;
+                imageFormat.format = GLES20.GL_RGBA4;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT_4_4_4_4;
                 break;
             case RGB5A1:
-                format = GLES20.GL_RGBA;
-                dataType = GLES20.GL_UNSIGNED_SHORT_5_5_5_1;
+                imageFormat.format = GLES20.GL_RGBA;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_SHORT_5_5_5_1;
                 break;
             case RGB8:
-                format = GLES20.GL_RGB;
-                dataType = GLES20.GL_UNSIGNED_BYTE;
+                imageFormat.format = GLES20.GL_RGB;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
                 break;
             case BGR8:
-                format = GLES20.GL_RGB;
-                dataType = GLES20.GL_UNSIGNED_BYTE;
+                imageFormat.format = GLES20.GL_RGB;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
                 break;
             case RGBA8:
-                format = GLES20.GL_RGBA;                
-                dataType = GLES20.GL_UNSIGNED_BYTE;
+                imageFormat.format = GLES20.GL_RGBA;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
                 break;
             case Depth:
             case Depth16:
             case Depth24:
-                format = GLES20.GL_DEPTH_COMPONENT;
-                dataType = GLES20.GL_UNSIGNED_BYTE;
+                imageFormat.format = GLES20.GL_DEPTH_COMPONENT;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
                 break;
             case DXT1:
                 if (!DXT1) {
                     unsupportedFormat(fmt);
                 }
-                format = 0x83F0;
-                dataType = GLES20.GL_UNSIGNED_BYTE;
-                compress = true;
+                imageFormat.format = 0x83F0;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
+                imageFormat.compress = true;
                 break;
             case DXT1A:
                 if (!DXT1) {
                     unsupportedFormat(fmt);
                 }
-                format = 0x83F1;
-                dataType = GLES20.GL_UNSIGNED_BYTE;
-                compress = true;
+                imageFormat.format = 0x83F1;
+                imageFormat.dataType = GLES20.GL_UNSIGNED_BYTE;
+                imageFormat.compress = true;
                 break;
             default:
                 throw new UnsupportedOperationException("Unrecognized format: " + fmt);
         }
+        return imageFormat;
+    }
+
+    private static class AndroidGLImageFormat {
+
+        boolean compress = false;
+        int format = -1;
+        int dataType = -1;
+    }
+
+    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. 
+        // Prefer to only use power of 2 textures here to avoid errors.
+        Image.Format fmt = img.getFormat();
+        ByteBuffer data;
+        if (index >= 0 || img.getData() != null && img.getData().size() > 0) {
+            data = img.getData(index);
+        } else {
+            data = null;
+        }
+
+        int width = img.getWidth();
+        int height = img.getHeight();
+
+        if (!NPOT) {
+            // Check if texture is POT
+            if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(height)) {
+                throw new RendererException("Non-power-of-2 textures "
+                        + "are not supported by the video hardware "
+                        + "and no scaling path available for image: " + img);
+            }
+        }
+        AndroidGLImageFormat imageFormat = getImageFormat(fmt);
 
         if (data != null) {
             GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
@@ -343,81 +363,134 @@ public class TextureUtil {
 
         int[] mipSizes = img.getMipMapSizes();
         int pos = 0;
-        if (mipSizes == null){
-            if (data != null)
-                mipSizes = new int[]{ data.capacity() };
-            else
-                mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 };
+        if (mipSizes == null) {
+            if (data != null) {
+                mipSizes = new int[]{data.capacity()};
+            } else {
+                mipSizes = new int[]{width * height * fmt.getBitsPerPixel() / 8};
+            }
         }
 
-        // XXX: might want to change that when support
-        // of more than paletted compressions is added..
-        /// NOTE: Doesn't support mipmaps
-//        if (compress){
-//            data.clear();
-//            GLES20.glCompressedTexImage2D(target,
-//                                      1 - mipSizes.length,
-//                                      format,
-//                                      width,
-//                                      height,
-//                                      0,
-//                                      data.capacity(),
-//                                      data);
-//            return;
-//        }
-
-        for (int i = 0; i < mipSizes.length; i++){
-            int mipWidth =  Math.max(1, width  >> i);
+        for (int i = 0; i < mipSizes.length; i++) {
+            int mipWidth = Math.max(1, width >> i);
             int mipHeight = Math.max(1, height >> i);
-//            int mipDepth =  Math.max(1, depth  >> i);
 
-            if (data != null){
+            if (data != null) {
                 data.position(pos);
                 data.limit(pos + mipSizes[i]);
             }
 
-            if (compress && data != null){
+            if (imageFormat.compress && data != null) {
                 GLES20.glCompressedTexImage2D(target,
-                                          i,
-                                          format,
-                                          mipWidth,
-                                          mipHeight,
-                                          0,
-                                          data.remaining(),
-                                          data);
-            }else{
+                        i,
+                        imageFormat.format,
+                        mipWidth,
+                        mipHeight,
+                        0,
+                        data.remaining(),
+                        data);
+            } else {
                 GLES20.glTexImage2D(target,
-                                i,
-                                format,
-                                mipWidth,
-                                mipHeight,
-                                0,
-                                format,
-                                dataType,
-                                data);
+                        i,
+                        imageFormat.format,
+                        mipWidth,
+                        mipHeight,
+                        0,
+                        imageFormat.format,
+                        imageFormat.dataType,
+                        data);
             }
 
             pos += mipSizes[i];
         }
     }
 
+    private static void checkGLError() {
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            throw new RendererException("OpenGL Error " + error);
+        }
+    }
+
     /**
-     * Update the texture currently bound to target at with data from the given Image at position x and y. The parameter
-     * index is used as the zoffset in case a 3d texture or texture 2d array is being updated.
+     * Update the texture currently bound to target at with data from the given
+     * Image at position x and y. The parameter index is used as the zoffset in
+     * case a 3d texture or texture 2d array is being updated.
      *
-     * @param image Image with the source data (this data will be put into the texture)
+     * @param image Image with the source data (this data will be put into the
+     * texture)
      * @param target the target texture
      * @param index the mipmap level to update
      * @param x the x position where to put the image in the texture
      * @param y the y position where to put the image in the texture
      */
     public static void uploadSubTexture(
-        Image image,
-        int target,
-        int index,
-        int x,
-        int y) {
-      // FIXME and implement this!
-    }
+            Image img,
+            int target,
+            int index,
+            int x,
+            int y) {
+        if (img.getEfficentData() instanceof AndroidImageInfo) {
+            AndroidImageInfo imageInfo = (AndroidImageInfo) img.getEfficentData();
+            uploadTextureBitmap(target, imageInfo.getBitmap(), true, true, x, y);
+            return;
+        }
+
+        // Otherwise upload image directly. 
+        // Prefer to only use power of 2 textures here to avoid errors.
+        Image.Format fmt = img.getFormat();
+        ByteBuffer data;
+        if (index >= 0 || img.getData() != null && img.getData().size() > 0) {
+            data = img.getData(index);
+        } else {
+            data = null;
+        }
+
+        int width = img.getWidth();
+        int height = img.getHeight();
+
+        if (!NPOT) {
+            // Check if texture is POT
+            if (!FastMath.isPowerOfTwo(width) || !FastMath.isPowerOfTwo(height)) {
+                throw new RendererException("Non-power-of-2 textures "
+                        + "are not supported by the video hardware "
+                        + "and no scaling path available for image: " + img);
+            }
+        }
+        AndroidGLImageFormat imageFormat = getImageFormat(fmt);
+
+        if (data != null) {
+            GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
+        }
 
+        int[] mipSizes = img.getMipMapSizes();
+        int pos = 0;
+        if (mipSizes == null) {
+            if (data != null) {
+                mipSizes = new int[]{data.capacity()};
+            } else {
+                mipSizes = new int[]{width * height * fmt.getBitsPerPixel() / 8};
+            }
+        }
+
+        for (int i = 0; i < mipSizes.length; i++) {
+            int mipWidth = Math.max(1, width >> i);
+            int mipHeight = Math.max(1, height >> i);
+
+            if (data != null) {
+                data.position(pos);
+                data.limit(pos + mipSizes[i]);
+            }
+
+            if (imageFormat.compress && data != null) {
+                GLES20.glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, data.remaining(), data);
+                checkGLError();
+            } else {
+                GLES20.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, imageFormat.format, imageFormat.dataType, data);
+                checkGLError();
+            }
+
+            pos += mipSizes[i];
+        }
+    }
 }