Home | History | Annotate | Download | only in opengl
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.opengl;
     18 
     19 import java.io.IOException;
     20 import java.io.InputStream;
     21 import java.io.OutputStream;
     22 import java.nio.Buffer;
     23 import java.nio.ByteBuffer;
     24 import java.nio.ByteOrder;
     25 
     26 /**
     27  * Utility methods for using ETC1 compressed textures.
     28  *
     29  */
     30 public class ETC1Util {
     31     /**
     32      * Convenience method to load an ETC1 texture whether or not the active OpenGL context
     33      * supports the ETC1 texture compression format.
     34      * @param target the texture target.
     35      * @param level the texture level
     36      * @param border the border size. Typically 0.
     37      * @param fallbackFormat the format to use if ETC1 texture compression is not supported.
     38      * Must be GL_RGB.
     39      * @param fallbackType the type to use if ETC1 texture compression is not supported.
     40      * Can be either GL_UNSIGNED_SHORT_5_6_5, which implies 16-bits-per-pixel,
     41      * or GL_UNSIGNED_BYTE, which implies 24-bits-per-pixel.
     42      * @param input the input stream containing an ETC1 texture in PKM format.
     43      * @throws IOException
     44      */
     45     public static void loadTexture(int target, int level, int border,
     46             int fallbackFormat, int fallbackType, InputStream input)
     47         throws IOException {
     48         loadTexture(target, level, border, fallbackFormat, fallbackType, createTexture(input));
     49     }
     50 
     51     /**
     52      * Convenience method to load an ETC1 texture whether or not the active OpenGL context
     53      * supports the ETC1 texture compression format.
     54      * @param target the texture target.
     55      * @param level the texture level
     56      * @param border the border size. Typically 0.
     57      * @param fallbackFormat the format to use if ETC1 texture compression is not supported.
     58      * Must be GL_RGB.
     59      * @param fallbackType the type to use if ETC1 texture compression is not supported.
     60      * Can be either GL_UNSIGNED_SHORT_5_6_5, which implies 16-bits-per-pixel,
     61      * or GL_UNSIGNED_BYTE, which implies 24-bits-per-pixel.
     62      * @param texture the ETC1 to load.
     63      */
     64     public static void loadTexture(int target, int level, int border,
     65             int fallbackFormat, int fallbackType, ETC1Texture texture) {
     66         if (fallbackFormat != GLES10.GL_RGB) {
     67             throw new IllegalArgumentException("fallbackFormat must be GL_RGB");
     68         }
     69         if (! (fallbackType == GLES10.GL_UNSIGNED_SHORT_5_6_5
     70                 || fallbackType == GLES10.GL_UNSIGNED_BYTE)) {
     71             throw new IllegalArgumentException("Unsupported fallbackType");
     72         }
     73 
     74         int width = texture.getWidth();
     75         int height = texture.getHeight();
     76         Buffer data = texture.getData();
     77         if (isETC1Supported()) {
     78             int imageSize = data.remaining();
     79             GLES10.glCompressedTexImage2D(target, level, ETC1.ETC1_RGB8_OES, width, height,
     80                     border, imageSize, data);
     81         } else {
     82             boolean useShorts = fallbackType != GLES10.GL_UNSIGNED_BYTE;
     83             int pixelSize = useShorts ? 2 : 3;
     84             int stride = pixelSize * width;
     85             ByteBuffer decodedData = ByteBuffer.allocateDirect(stride*height)
     86                 .order(ByteOrder.nativeOrder());
     87             ETC1.decodeImage(data, decodedData, width, height, pixelSize, stride);
     88             GLES10.glTexImage2D(target, level, fallbackFormat, width, height, border,
     89                     fallbackFormat, fallbackType, decodedData);
     90         }
     91     }
     92 
     93     /**
     94      * Check if ETC1 texture compression is supported by the active OpenGL ES context.
     95      * @return true if the active OpenGL ES context supports ETC1 texture compression.
     96      */
     97     public static boolean isETC1Supported() {
     98         int[] results = new int[20];
     99         GLES10.glGetIntegerv(GLES10.GL_NUM_COMPRESSED_TEXTURE_FORMATS, results, 0);
    100         int numFormats = results[0];
    101         if (numFormats > results.length) {
    102             results = new int[numFormats];
    103         }
    104         GLES10.glGetIntegerv(GLES10.GL_COMPRESSED_TEXTURE_FORMATS, results, 0);
    105         for (int i = 0; i < numFormats; i++) {
    106             if (results[i] == ETC1.ETC1_RGB8_OES) {
    107                 return true;
    108             }
    109         }
    110         return false;
    111     }
    112 
    113     /**
    114      * A utility class encapsulating a compressed ETC1 texture.
    115      */
    116     public static class ETC1Texture {
    117         public ETC1Texture(int width, int height, ByteBuffer data) {
    118             mWidth = width;
    119             mHeight = height;
    120             mData = data;
    121         }
    122 
    123         /**
    124          * Get the width of the texture in pixels.
    125          * @return the width of the texture in pixels.
    126          */
    127         public int getWidth() { return mWidth; }
    128 
    129         /**
    130          * Get the height of the texture in pixels.
    131          * @return the width of the texture in pixels.
    132          */
    133         public int getHeight() { return mHeight; }
    134 
    135         /**
    136          * Get the compressed data of the texture.
    137          * @return the texture data.
    138          */
    139         public ByteBuffer getData() { return mData; }
    140 
    141         private int mWidth;
    142         private int mHeight;
    143         private ByteBuffer mData;
    144     }
    145 
    146     /**
    147      * Create a new ETC1Texture from an input stream containing a PKM formatted compressed texture.
    148      * @param input an input stream containing a PKM formatted compressed texture.
    149      * @return an ETC1Texture read from the input stream.
    150      * @throws IOException
    151      */
    152     public static ETC1Texture createTexture(InputStream input) throws IOException {
    153         int width = 0;
    154         int height = 0;
    155         byte[] ioBuffer = new byte[4096];
    156         {
    157             if (input.read(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE) != ETC1.ETC_PKM_HEADER_SIZE) {
    158                 throw new IOException("Unable to read PKM file header.");
    159             }
    160             ByteBuffer headerBuffer = ByteBuffer.allocateDirect(ETC1.ETC_PKM_HEADER_SIZE)
    161                 .order(ByteOrder.nativeOrder());
    162             headerBuffer.put(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE).position(0);
    163             if (!ETC1.isValid(headerBuffer)) {
    164                 throw new IOException("Not a PKM file.");
    165             }
    166             width = ETC1.getWidth(headerBuffer);
    167             height = ETC1.getHeight(headerBuffer);
    168         }
    169         int encodedSize = ETC1.getEncodedDataSize(width, height);
    170         ByteBuffer dataBuffer = ByteBuffer.allocateDirect(encodedSize).order(ByteOrder.nativeOrder());
    171         for (int i = 0; i < encodedSize; ) {
    172             int chunkSize = Math.min(ioBuffer.length, encodedSize - i);
    173             if (input.read(ioBuffer, 0, chunkSize) != chunkSize) {
    174                 throw new IOException("Unable to read PKM file data.");
    175             }
    176             dataBuffer.put(ioBuffer, 0, chunkSize);
    177             i += chunkSize;
    178         }
    179         dataBuffer.position(0);
    180         return new ETC1Texture(width, height, dataBuffer);
    181     }
    182 
    183     /**
    184      * Helper function that compresses an image into an ETC1Texture.
    185      * @param input a native order direct buffer containing the image data
    186      * @param width the width of the image in pixels
    187      * @param height the height of the image in pixels
    188      * @param pixelSize the size of a pixel in bytes (2 or 3)
    189      * @param stride the width of a line of the image in bytes
    190      * @return the ETC1 texture.
    191      */
    192     public static ETC1Texture compressTexture(Buffer input, int width, int height, int pixelSize, int stride){
    193         int encodedImageSize = ETC1.getEncodedDataSize(width, height);
    194         ByteBuffer compressedImage = ByteBuffer.allocateDirect(encodedImageSize).
    195             order(ByteOrder.nativeOrder());
    196         ETC1.encodeImage(input, width, height, pixelSize, stride, compressedImage);
    197         return new ETC1Texture(width, height, compressedImage);
    198     }
    199 
    200     /**
    201      * Helper function that writes an ETC1Texture to an output stream formatted as a PKM file.
    202      * @param texture the input texture.
    203      * @param output the stream to write the formatted texture data to.
    204      * @throws IOException
    205      */
    206     public static void writeTexture(ETC1Texture texture, OutputStream output) throws IOException {
    207         ByteBuffer dataBuffer = texture.getData();
    208         int originalPosition = dataBuffer.position();
    209         try {
    210             int width = texture.getWidth();
    211             int height = texture.getHeight();
    212             ByteBuffer header = ByteBuffer.allocateDirect(ETC1.ETC_PKM_HEADER_SIZE).order(ByteOrder.nativeOrder());
    213             ETC1.formatHeader(header, width, height);
    214             byte[] ioBuffer = new byte[4096];
    215             header.get(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE);
    216             output.write(ioBuffer, 0, ETC1.ETC_PKM_HEADER_SIZE);
    217             int encodedSize = ETC1.getEncodedDataSize(width, height);
    218             for (int i = 0; i < encodedSize; ) {
    219                 int chunkSize = Math.min(ioBuffer.length, encodedSize - i);
    220                 dataBuffer.get(ioBuffer, 0, chunkSize);
    221                 output.write(ioBuffer, 0, chunkSize);
    222                 i += chunkSize;
    223             }
    224         } finally {
    225             dataBuffer.position(originalPosition);
    226         }
    227     }
    228 }
    229