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.stub.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, "Stub!"); 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