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