1 /*
2  * Copyright (C) 2011 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 package android.opengl.cts;
17 
18 import java.io.InputStream;
19 import java.nio.ByteBuffer;
20 import java.nio.Buffer;
21 import java.nio.ByteOrder;
22 import java.util.HashMap;
23 
24 import com.android.cts.graphics.R;
25 
26 import android.app.Activity;
27 import android.content.res.AssetFileDescriptor;
28 import android.content.res.Resources;
29 import android.os.Bundle;
30 import android.util.Log;
31 
32 import android.opengl.ETC1;
33 import android.opengl.ETC1Util;
34 import android.opengl.GLES20;
35 
36 import android.graphics.Bitmap;
37 import android.graphics.Bitmap.Config;
38 import android.graphics.BitmapFactory;
39 
40 public class CompressedTextureLoader {
41     private static final String TAG = "CompressedTextureLoader";
42 
43     public static final String TEXTURE_UNCOMPRESSED = "UNCOMPRESSED";
44     public static final String TEXTURE_ETC1 = "ETC1";
45     public static final String TEXTURE_S3TC = "S3TC";
46     public static final String TEXTURE_ATC = "ATC";
47     public static final String TEXTURE_PVRTC = "PVRTC";
48 
49     public static class Texture {
Texture(int width, int height, int internalformat, ByteBuffer data, String formatName)50         public Texture(int width, int height, int internalformat, ByteBuffer data,
51                        String formatName) {
52             mWidth = width;
53             mHeight = height;
54             mInternalFormat = internalformat;
55             mData = data;
56             mFormatName = formatName;
57         }
58 
59         /**
60          * Get the width of the texture in pixels.
61          * @return the width of the texture in pixels.
62          */
getWidth()63         public int getWidth() { return mWidth; }
64 
65         /**
66          * Get the height of the texture in pixels.
67          * @return the width of the texture in pixels.
68          */
getHeight()69         public int getHeight() { return mHeight; }
70 
71         /**
72          * Get the compressed data of the texture.
73          * @return the texture data.
74          */
getData()75         public ByteBuffer getData() { return mData; }
76 
77         /**
78          * Get the format of the texture.
79          * @return the internal format.
80          */
getFormat()81         public int getFormat() { return mInternalFormat; }
82 
83         /**
84          * Get the format of the texture.
85          * @return the internal format.
86          */
isSupported()87         public boolean isSupported() { return isFormatSupported(mFormatName); }
88 
89         private int mWidth;
90         private int mHeight;
91         private int mInternalFormat;
92         private ByteBuffer mData;
93         private String mFormatName;
94     }
95 
96     /*  .pvr header is described by the following c struct
97         typedef struct PVR_TEXTURE_HEADER_TAG{
98             unsigned int  dwHeaderSize;   // size of the structure
99             unsigned int  dwHeight;    // height of surface to be created
100             unsigned int  dwWidth;    // width of input surface
101             unsigned int  dwMipMapCount;   // number of MIP-map levels requested
102             unsigned int  dwpfFlags;   // pixel format flags
103             unsigned int  dwDataSize;   // Size of the compress data
104             unsigned int  dwBitCount;   // number of bits per pixel
105             unsigned int  dwRBitMask;   // mask for red bit
106             unsigned int  dwGBitMask;   // mask for green bits
107             unsigned int  dwBBitMask;   // mask for blue bits
108             unsigned int  dwAlphaBitMask;   // mask for alpha channel
109             unsigned int  dwPVR;    // should be 'P' 'V' 'R' '!'
110             unsigned int  dwNumSurfs;   //number of slices for volume textures or skyboxes
111         } PVR_TEXTURE_HEADER;
112     */
113     static final int PVR_HEADER_SIZE = 13 * 4;
114     static final int PVR_2BPP = 24;
115     static final int PVR_4BPP = 25;
116     static final int PVR_MAGIC_NUMBER = 559044176;
117 
118     static final int GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00;
119     static final int GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01;
120     static final int GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02;
121     static final int GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03;
122 
123     static class PVRHeader {
124         int mHeaderSize;   // size of the structure
125         int mHeight;    // height of surface to be created
126         int mWidth;    // width of input surface
127         int mMipMapCount;   // number of MIP-map levels requested
128         int mpfFlags;   // pixel format flags
129         int mDataSize;   // Size of the compress data
130         int mBitCount;   // number of bits per pixel
131         int mRBitMask;   // mask for red bit
132         int mGBitMask;   // mask for green bits
133         int mBBitMask;   // mask for blue bits
134         int mAlphaBitMask;   // mask for alpha channel
135         int mPVR;    // should be 'P' 'V' 'R' '!'
136         int mNumSurfs;   //number of slices for volume textures or skyboxes
137     }
138 
readPVRHeader(InputStream is)139     protected static PVRHeader readPVRHeader(InputStream is) {
140 
141         byte[] headerData = new byte[PVR_HEADER_SIZE];
142         try {
143             is.read(headerData);
144         } catch (Exception e) {
145             throw new RuntimeException("Unable to read data");
146         }
147 
148         ByteBuffer headerBuffer = ByteBuffer.allocateDirect(PVR_HEADER_SIZE)
149                 .order(ByteOrder.nativeOrder());
150         headerBuffer.put(headerData, 0, PVR_HEADER_SIZE).position(0);
151 
152         PVRHeader header = new PVRHeader();
153 
154         header.mHeaderSize = headerBuffer.getInt();
155         header.mHeight = headerBuffer.getInt();
156         header.mWidth = headerBuffer.getInt();
157         header.mMipMapCount = headerBuffer.getInt();
158         header.mpfFlags = headerBuffer.getInt();
159         header.mDataSize = headerBuffer.getInt();
160         header.mBitCount = headerBuffer.getInt();
161         header.mRBitMask = headerBuffer.getInt();
162         header.mGBitMask = headerBuffer.getInt();
163         header.mBBitMask = headerBuffer.getInt();
164         header.mAlphaBitMask = headerBuffer.getInt();
165         header.mPVR = headerBuffer.getInt();
166         header.mNumSurfs = headerBuffer.getInt();
167 
168         if (header.mHeaderSize != PVR_HEADER_SIZE ||
169             header.mPVR != PVR_MAGIC_NUMBER) {
170             throw new RuntimeException("Invalid header data");
171         }
172 
173         return header;
174     }
175 
loadTextureATC(Resources res, int id)176     public static Texture loadTextureATC(Resources res, int id) {
177         Texture tex = new Texture(0, 0, 0, null, "Cts!");
178         return tex;
179     }
180 
compressTexture(Buffer input, int width, int height, int pixelSize, int stride)181     private static ETC1Util.ETC1Texture compressTexture(Buffer input,
182                                                         int width, int height,
183                                                         int pixelSize, int stride){
184         int encodedImageSize = ETC1.getEncodedDataSize(width, height);
185         ByteBuffer compressedImage = ByteBuffer.allocateDirect(encodedImageSize).
186             order(ByteOrder.nativeOrder());
187         ETC1.encodeImage(input, width, height, pixelSize, stride, compressedImage);
188         return new ETC1Util.ETC1Texture(width, height, compressedImage);
189     }
190 
createFromUncompressedETC1(Bitmap bitmap)191     public static Texture createFromUncompressedETC1(Bitmap bitmap) {
192         int dataSize = bitmap.getRowBytes() * bitmap.getHeight();
193 
194         ByteBuffer dataBuffer;
195         dataBuffer = ByteBuffer.allocateDirect(dataSize).order(ByteOrder.nativeOrder());
196         bitmap.copyPixelsToBuffer(dataBuffer);
197         dataBuffer.position(0);
198 
199         int bytesPerPixel = bitmap.getRowBytes() / bitmap.getWidth();
200         ETC1Util.ETC1Texture compressed = compressTexture(dataBuffer,
201                                                           bitmap.getWidth(),
202                                                           bitmap.getHeight(),
203                                                           bytesPerPixel,
204                                                           bitmap.getRowBytes());
205 
206         Texture tex = new Texture(compressed.getWidth(), compressed.getHeight(),
207                                   ETC1.ETC1_RGB8_OES, compressed.getData(), TEXTURE_ETC1);
208 
209         return tex;
210     }
211 
read(InputStream is, int dataSize)212     private static ByteBuffer read(InputStream is, int dataSize) {
213         ByteBuffer dataBuffer;
214         dataBuffer = ByteBuffer.allocateDirect(dataSize).order(ByteOrder.nativeOrder());
215         byte[] ioBuffer = new byte[4096];
216         for (int i = 0; i < dataSize; ) {
217             int chunkSize = Math.min(ioBuffer.length, dataSize - i);
218             try {
219                 is.read(ioBuffer, 0, chunkSize);
220             } catch (Exception e) {
221                 throw new RuntimeException("Unable to read data");
222             }
223             dataBuffer.put(ioBuffer, 0, chunkSize);
224             i += chunkSize;
225         }
226         dataBuffer.position(0);
227         return dataBuffer;
228     }
229 
loadTexturePVRTC(Resources res, int id)230     public static Texture loadTexturePVRTC(Resources res, int id) {
231         InputStream is = null;
232         try {
233             is = res.openRawResource(id);
234         } catch (Exception e) {
235             throw new RuntimeException("Unable to open resource " + id);
236         }
237 
238         PVRHeader header = readPVRHeader(is);
239 
240         int format = header.mpfFlags & 0xFF;
241         int internalFormat = 0;
242         if (format == PVR_2BPP && header.mAlphaBitMask == 1) {
243             internalFormat = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
244         } else if (format == PVR_2BPP && header.mAlphaBitMask == 0) {
245             internalFormat = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
246         } else if (format == PVR_4BPP && header.mAlphaBitMask == 1) {
247             internalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
248         } else if (format == PVR_4BPP && header.mAlphaBitMask == 0) {
249             internalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
250         }
251 
252         // only load the first mip level for now
253         int dataSize = (header.mWidth * header.mHeight * header.mBitCount) >> 3;
254         ByteBuffer dataBuffer = read(is, dataSize);
255         Texture tex = new Texture(header.mWidth, header.mHeight,
256                                   internalFormat, dataBuffer,
257                                   TEXTURE_PVRTC);
258         try {
259             is.close();
260         } catch (Exception e) {
261             throw new RuntimeException("Unable to close resource stream " + id);
262         }
263         return tex;
264     }
265 
266     /* DDS Header is described by the following structs
267        typedef struct {
268           DWORD           dwSize;
269           DWORD           dwFlags;
270           DWORD           dwHeight;
271           DWORD           dwWidth;
272           DWORD           dwPitchOrLinearSize;
273           DWORD           dwDepth;
274           DWORD           dwMipMapCount;
275           DWORD           dwReserved1[11];
276           DDS_PIXELFORMAT ddspf;
277           DWORD           dwCaps;
278           DWORD           dwCaps2;
279           DWORD           dwCaps3;
280           DWORD           dwCaps4;
281           DWORD           dwReserved2;
282         } DDS_HEADER;
283 
284         struct DDS_PIXELFORMAT {
285           DWORD dwSize;
286           DWORD dwFlags;
287           DWORD dwFourCC;
288           DWORD dwRGBBitCount;
289           DWORD dwRBitMask;
290           DWORD dwGBitMask;
291           DWORD dwBBitMask;
292           DWORD dwABitMask;
293         };
294 
295         In the file it looks like this
296         DWORD               dwMagic;
297         DDS_HEADER          header;
298         DDS_HEADER_DXT10    header10; // If the DDS_PIXELFORMAT dwFlags is set to DDPF_FOURCC
299                                       // and dwFourCC is DX10
300 
301     */
302 
303     static final int DDS_HEADER_STRUCT_SIZE = 124;
304     static final int DDS_PIXELFORMAT_STRUCT_SIZE = 32;
305     static final int DDS_HEADER_SIZE = 128;
306     static final int DDS_MAGIC_NUMBER = 0x20534444;
307     static final int DDS_DDPF_FOURCC = 0x4;
308     static final int DDS_DXT1 = 0x31545844;
309     static final int DDS_DXT5 = 0x35545844;
310 
311     static final int COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0;
312     static final int COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
313     static final int COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
314 
315     static class DDSHeader {
316         int mMagic;
317         int mSize;
318         int mFlags;
319         int mHeight;
320         int mWidth;
321         int mPitchOrLinearSize;
322         int mDepth;
323         int mMipMapCount;
324         int[] mReserved1;
325         // struct DDS_PIXELFORMAT {
326             int mPixelFormatSize;
327             int mPixelFormatFlags;
328             int mPixelFormatFourCC;
329             int mPixelFormatRGBBitCount;
330             int mPixelFormatRBitMask;
331             int mPixelFormatGBitMask;
332             int mPixelFormatBBitMask;
333             int mPixelFormatABitMask;
334         // };
335         int mCaps;
336         int mCaps2;
337         int mCaps3;
338         int mCaps4;
339         int mReserved2;
340 
DDSHeader()341         DDSHeader() {
342             mReserved1 = new int[11];
343         }
344     }
345 
readDDSHeader(InputStream is)346     protected static DDSHeader readDDSHeader(InputStream is) {
347 
348         byte[] headerData = new byte[DDS_HEADER_SIZE];
349         try {
350             is.read(headerData);
351         } catch (Exception e) {
352             throw new RuntimeException("Unable to read data");
353         }
354 
355         ByteBuffer headerBuffer = ByteBuffer.allocateDirect(DDS_HEADER_SIZE)
356                 .order(ByteOrder.nativeOrder());
357         headerBuffer.put(headerData, 0, DDS_HEADER_SIZE).position(0);
358 
359         DDSHeader header = new DDSHeader();
360 
361         header.mMagic = headerBuffer.getInt();
362         header.mSize = headerBuffer.getInt();
363         header.mFlags = headerBuffer.getInt();
364         header.mHeight = headerBuffer.getInt();
365         header.mWidth = headerBuffer.getInt();
366         header.mPitchOrLinearSize = headerBuffer.getInt();
367         header.mDepth = headerBuffer.getInt();
368         header.mMipMapCount = headerBuffer.getInt();
369         for (int i = 0; i < header.mReserved1.length; i ++) {
370             header.mReserved1[i] = headerBuffer.getInt();
371         }
372         // struct DDS_PIXELFORMAT {
373             header.mPixelFormatSize = headerBuffer.getInt();
374             header.mPixelFormatFlags = headerBuffer.getInt();
375             header.mPixelFormatFourCC = headerBuffer.getInt();
376             header.mPixelFormatRGBBitCount = headerBuffer.getInt();
377             header.mPixelFormatRBitMask = headerBuffer.getInt();
378             header.mPixelFormatGBitMask = headerBuffer.getInt();
379             header.mPixelFormatBBitMask = headerBuffer.getInt();
380             header.mPixelFormatABitMask = headerBuffer.getInt();
381         // };
382         header.mCaps = headerBuffer.getInt();
383         header.mCaps2 = headerBuffer.getInt();
384         header.mCaps3 = headerBuffer.getInt();
385         header.mCaps4 = headerBuffer.getInt();
386         header.mReserved2 = headerBuffer.getInt();
387 
388         if (header.mSize != DDS_HEADER_STRUCT_SIZE ||
389             header.mPixelFormatSize != DDS_PIXELFORMAT_STRUCT_SIZE ||
390             header.mMagic != DDS_MAGIC_NUMBER) {
391             throw new RuntimeException("Invalid header data");
392         }
393 
394         return header;
395     }
396 
397     // Very simple loader that only reads in the header and a DXT1 mip level 0
loadTextureDXT(Resources res, int id)398     public static Texture loadTextureDXT(Resources res, int id) {
399         InputStream is = null;
400         try {
401             is = res.openRawResource(id);
402         } catch (Exception e) {
403             throw new RuntimeException("Unable to open resource " + id);
404         }
405 
406         DDSHeader header = readDDSHeader(is);
407 
408         if (header.mPixelFormatFlags != DDS_DDPF_FOURCC) {
409             throw new RuntimeException("Unsupported DXT data");
410         }
411 
412         int internalFormat = 0;
413         int bpp = 0;
414         switch (header.mPixelFormatFourCC) {
415         case DDS_DXT1:
416             internalFormat = COMPRESSED_RGB_S3TC_DXT1_EXT;
417             bpp = 4;
418             break;
419         case DDS_DXT5:
420             internalFormat = COMPRESSED_RGBA_S3TC_DXT5_EXT;
421             bpp = 8;
422             break;
423         default:
424             throw new RuntimeException("Unsupported DXT data");
425         }
426 
427         // only load the first mip level for now
428         int dataSize = (header.mWidth * header.mHeight * bpp) >> 3;
429         if (dataSize != header.mPitchOrLinearSize) {
430             throw new RuntimeException("Expected data and header mismatch");
431         }
432         ByteBuffer dataBuffer = read(is, dataSize);
433 
434         Texture tex = new Texture(header.mWidth, header.mHeight, internalFormat,
435                                   dataBuffer, TEXTURE_S3TC);
436         return tex;
437     }
438 
439     static HashMap<String, Boolean> sExtensionMap;
440     static HashMap<String, Boolean> sFormatMap;
441 
updateSupportedFormats()442     private static synchronized void updateSupportedFormats() {
443         if (sExtensionMap != null) {
444             return;
445         }
446 
447         sExtensionMap = new HashMap<String, Boolean>();
448         sFormatMap = new HashMap<String, Boolean>();
449         String extensionList = GLES20.glGetString(GLES20.GL_EXTENSIONS);
450 
451         for (String extension : extensionList.split(" ")) {
452             sExtensionMap.put(extension, true);
453         }
454 
455         // Check ETC1
456         sFormatMap.put(TEXTURE_ETC1, ETC1Util.isETC1Supported());
457         // Check ATC
458         if (sExtensionMap.get("GL_AMD_compressed_ATC_texture") != null ||
459             sExtensionMap.get("GL_ATI_compressed_texture_atitc") != null ||
460             sExtensionMap.get("GL_ATI_texture_compression_atitc") != null) {
461             sFormatMap.put(TEXTURE_ATC, true);
462         }
463         // Check DXT
464         if (sExtensionMap.get("GL_EXT_texture_compression_dxt1") != null ||
465             sExtensionMap.get("GL_EXT_texture_compression_s3tc") != null ||
466             sExtensionMap.get("OES_texture_compression_S3TC") != null) {
467             sFormatMap.put(TEXTURE_S3TC, true);
468         }
469         // Check DXT
470         if (sExtensionMap.get("GL_IMG_texture_compression_pvrtc") != null) {
471             sFormatMap.put(TEXTURE_PVRTC, true);
472         }
473 
474         /*Log.i(TAG, "mIsSupportedETC1 " + sFormatMap.get(TEXTURE_ETC1));
475         Log.i(TAG, "mIsSupportedATC " + sFormatMap.get(TEXTURE_ATC));
476         Log.i(TAG, "mIsSupportedDXT " + sFormatMap.get(TEXTURE_S3TC));
477         Log.i(TAG, "mIsSupportedPVRTC " + sFormatMap.get(TEXTURE_PVRTC));*/
478     }
479 
isFormatSupported(String format)480     private static boolean isFormatSupported(String format) {
481         updateSupportedFormats();
482         Boolean supported = sFormatMap.get(format);
483         return supported != null ? supported : false;
484     }
485 }
486