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