Browse Source

properly invert dxt textures

David Rose 17 years ago
parent
commit
790e3f5f97
2 changed files with 268 additions and 136 deletions
  1. 259 133
      panda/src/gobj/texture.cxx
  2. 9 3
      panda/src/gobj/texture.h

+ 259 - 133
panda/src/gobj/texture.cxx

@@ -929,17 +929,28 @@ read_dds(istream &in, const string &filename, bool header_only) {
     // Some compressed texture format.
     if (header.pf.four_cc == 0x31545844) {   // 'DXT1', little-endian.
       compression = CM_dxt1;
+      func = read_dds_level_dxt1;
+    } else if (header.pf.four_cc == 0x32545844) {   // 'DXT2'
+      compression = CM_dxt2;
+      func = read_dds_level_dxt23;
     } else if (header.pf.four_cc == 0x33545844) {   // 'DXT3'
       compression = CM_dxt3;
+      func = read_dds_level_dxt23;
+    } else if (header.pf.four_cc == 0x34545844) {   // 'DXT4'
+      compression = CM_dxt4;
+      func = read_dds_level_dxt45;
     } else if (header.pf.four_cc == 0x35545844) {   // 'DXT5'
       compression = CM_dxt5;
+      func = read_dds_level_dxt45;
     } else {
       gobj_cat.error()
         << filename << ": unsupported texture compression.\n";
       return false;
     }
 
-    func = read_dds_level_compressed;
+    if (header.pf.pf_flags & DDPF_ALPHAPIXELS) {
+      format = F_rgba;
+    }
 
   } else {
     // An uncompressed texture format.
@@ -967,12 +978,12 @@ read_dds(istream &in, const string &filename, bool header_only) {
           header.pf.r_mask == 0x00ff0000 &&
           header.pf.g_mask == 0x0000ff00 &&
           header.pf.b_mask == 0x000000ff) {
-        func = read_dds_level_rgb8;
+        func = read_dds_level_bgr8;
       } else if (header.pf.rgb_bitcount == 24 &&
                  header.pf.r_mask == 0x000000ff &&
                  header.pf.g_mask == 0x0000ff00 &&
                  header.pf.b_mask == 0x00ff0000) {
-        func = read_dds_level_bgr8;
+        func = read_dds_level_rgb8;
       }
     }
   }
@@ -3632,7 +3643,7 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
 bool Texture::
 read_dds_level_bgr8(Texture *tex, const DDSHeader &header, 
                     int n, istream &in) { 
-  // This appears to be laid out in order R, G, B (?)
+  // This is in order B, G, R.
   int x_size = tex->get_expected_mipmap_x_size(n);
   int y_size = tex->get_expected_mipmap_y_size(n);
 
@@ -3642,9 +3653,9 @@ read_dds_level_bgr8(Texture *tex, const DDSHeader &header,
   for (int y = y_size - 1; y >= 0; --y) {
     unsigned char *p = image.p() + y * row_bytes;
     for (int x = 0; x < x_size; ++x) {
-      unsigned int r = (unsigned char)in.get();
-      unsigned int g = (unsigned char)in.get();
       unsigned int b = (unsigned char)in.get();
+      unsigned int g = (unsigned char)in.get();
+      unsigned int r = (unsigned char)in.get();
 
       store_unscaled_byte(p, b);
       store_unscaled_byte(p, g);
@@ -3666,7 +3677,7 @@ read_dds_level_bgr8(Texture *tex, const DDSHeader &header,
 bool Texture::
 read_dds_level_rgb8(Texture *tex, const DDSHeader &header, 
                     int n, istream &in) {
-  // This appears to be laid out in order B, G, R (?)
+  // This is in order R, G, B.
   int x_size = tex->get_expected_mipmap_x_size(n);
   int y_size = tex->get_expected_mipmap_y_size(n);
 
@@ -3676,9 +3687,9 @@ read_dds_level_rgb8(Texture *tex, const DDSHeader &header,
   for (int y = y_size - 1; y >= 0; --y) {
     unsigned char *p = image.p() + y * row_bytes;
     for (int x = 0; x < x_size; ++x) {
-      unsigned int b = (unsigned char)in.get();
-      unsigned int g = (unsigned char)in.get();
       unsigned int r = (unsigned char)in.get();
+      unsigned int g = (unsigned char)in.get();
+      unsigned int b = (unsigned char)in.get();
 
       store_unscaled_byte(p, b);
       store_unscaled_byte(p, g);
@@ -3870,27 +3881,25 @@ read_dds_level_generic_uncompressed(Texture *tex, const DDSHeader &header,
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: Texture::read_dds_level_compressed
+//     Function: Texture::read_dds_level_dxt1
 //       Access: Private, Static
-//  Description: Called by read_dds for a compressed DDS file format.
+//  Description: Called by read_dds for DXT1 file format.
 ////////////////////////////////////////////////////////////////////
 bool Texture::
-read_dds_level_compressed(Texture *tex, const DDSHeader &header, 
-                          int n, istream &in) {
+read_dds_level_dxt1(Texture *tex, const DDSHeader &header, 
+                    int n, istream &in) {
   int x_size = tex->get_expected_mipmap_x_size(n);
   int y_size = tex->get_expected_mipmap_y_size(n);
 
-  int div = 4;
-  int block_bytes = 8;
+  static const int div = 4;
+  static const int block_bytes = 8;
 
-  switch (tex->_ram_image_compression) {
-  case CM_dxt3:
-  case CM_dxt5:
-    block_bytes = 16;
-    break;
-  }
-
-  int linear_size = max(div, x_size) / div * max(div, y_size) / div * block_bytes;
+  // The DXT1 image is divided into num_rows x num_cols blocks, where
+  // each block represents 4x4 pixels.
+  int num_cols = max(div, x_size) / div;
+  int num_rows = max(div, y_size) / div;
+  int row_length = num_cols * block_bytes;
+  int linear_size = row_length * num_rows;
 
   if (n == 0) {
     if (header.dds_flags & DDSD_LINEARSIZE) {
@@ -3899,134 +3908,251 @@ read_dds_level_compressed(Texture *tex, const DDSHeader &header,
   }
 
   PTA_uchar image = PTA_uchar::empty_array(linear_size);
-  in.read((char *)image.p(), linear_size);
+
+  if (y_size >= 4) {
+    // We have to flip the image as we read it, because of DirectX's
+    // inverted sense of up.  That means we (a) reverse the order of the
+    // rows of blocks . . .
+    for (int ri = num_rows - 1; ri >= 0; --ri) {
+      unsigned char *p = image.p() + row_length * ri;
+      in.read((char *)p, row_length);
+      
+      for (int ci = 0; ci < num_cols; ++ci) {
+        // . . . and (b) within each block, we reverse the 4 individual
+        // rows of 4 pixels.
+        PN_uint32 *cells = (PN_uint32 *)p;
+        PN_uint32 w = cells[1];
+        w = ((w & 0xff) << 24) | ((w & 0xff00) << 8) | ((w & 0xff0000) >> 8) | ((w & 0xff000000U) >> 24);
+        cells[1] = w;
+        
+        p += block_bytes;
+      }
+    }
+
+  } else if (y_size >= 2) {
+    // To invert a two-pixel high image, we just flip two rows within a cell.
+    unsigned char *p = image.p();
+    in.read((char *)p, row_length);
+    
+    for (int ci = 0; ci < num_cols; ++ci) {
+      PN_uint32 *cells = (PN_uint32 *)p;
+      PN_uint32 w = cells[1];
+      w = ((w & 0xff) << 8) | ((w & 0xff00) >> 8);
+      cells[1] = w;
+      
+      p += block_bytes;
+    }
+
+  } else if (y_size >= 1) {
+    // No need to invert a one-pixel-high image.
+    unsigned char *p = image.p();
+    in.read((char *)p, row_length);
+  }
 
   tex->set_ram_mipmap_image(n, image);
 
   return true;
 }
-      
 
-      /*
-  DdsLoadInfo * li;
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_dds_level_dxt23
+//       Access: Private, Static
+//  Description: Called by read_dds for DXT2 or DXT3 file format.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_dds_level_dxt23(Texture *tex, const DDSHeader &header, 
+                     int n, istream &in) {
+  int x_size = tex->get_expected_mipmap_x_size(n);
+  int y_size = tex->get_expected_mipmap_y_size(n);
 
-  if( PF_IS_DXT1( hdr.sPixelFormat ) ) {
-    li = &loadInfoDXT1;
-  }
-  else if( PF_IS_DXT3( hdr.sPixelFormat ) ) {
-    li = &loadInfoDXT3;
-  }
-  else if( PF_IS_DXT5( hdr.sPixelFormat ) ) {
-    li = &loadInfoDXT5;
-  }
-  else if( PF_IS_BGRA8( hdr.sPixelFormat ) ) {
-    li = &loadInfoBGRA8;
-  }
-  else if( PF_IS_BGR8( hdr.sPixelFormat ) ) {
-    li = &loadInfoBGR8;
-  }
-  else if( PF_IS_BGR5A1( hdr.sPixelFormat ) ) {
-    li = &loadInfoBGR5A1;
-  }
-  else if( PF_IS_BGR565( hdr.sPixelFormat ) ) {
-    li = &loadInfoBGR565;
-  }
-  else if( PF_IS_INDEX8( hdr.sPixelFormat ) ) {
-    li = &loadInfoIndex8;
-  }
-  else {
-    goto failure;
-  }
+  static const int div = 4;
+  static const int block_bytes = 16;
 
-  //fixme: do cube maps later
-  //fixme: do 3d later
-  x = xSize;
-  y = ySize;
-  glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE );
-  mipMapCount = (hdr.dwFlags & DDSD_MIPMAPCOUNT) ? hdr.dwMipMapCount : 1;
-  if( mipMapCount > 1 ) {
-    hasMipmaps_ = true;
-  }
-  if( li->compressed ) {
-    size_t size = max( li->divSize, x )/li->divSize * max( li->divSize, y )/li->divSize * li->blockBytes;
-    assert( size == hdr.dwPitchOrLinearSize );
-    assert( hdr.dwFlags & DDSD_LINEARSIZE );
-    unsigned char * data = (unsigned char *)malloc( size );
-    if( !data ) {
-      goto failure;
-    }
-    format = cFormat = li->internalFormat;
-    for( unsigned int ix = 0; ix < mipMapCount; ++ix ) {
-      fread( data, 1, size, f );
-      glCompressedTexImage2D( GL_TEXTURE_2D, ix, li->internalFormat, x, y, 0, size, data );
-      gl->updateError();
-      x = (x+1)>>1;
-      y = (y+1)>>1;
-      size = max( li->divSize, x )/li->divSize * max( li->divSize, y )/li->divSize * li->blockBytes;
-    }
-    free( data );
-  }
-  else if( li->palette ) {
-    //  currently, we unpack palette into BGRA
-    //  I'm not sure we always get pitch...
-    assert( hdr.dwFlags & DDSD_PITCH );
-    assert( hdr.sPixelFormat.dwRGBBitCount == 8 );
-    size_t size = hdr.dwPitchOrLinearSize * ySize;
-    //  And I'm even less sure we don't get padding on the smaller MIP levels...
-    assert( size == x * y * li->blockBytes );
-    format = li->externalFormat;
-    cFormat = li->internalFormat;
-    unsigned char * data = (unsigned char *)malloc( size );
-    unsigned int palette[ 256 ];
-    unsigned int * unpacked = (unsigned int *)malloc( size*sizeof( unsigned int ) );
-    fread( palette, 4, 256, f );
-    for( unsigned int ix = 0; ix < mipMapCount; ++ix ) {
-      fread( data, 1, size, f );
-      for( unsigned int zz = 0; zz < size; ++zz ) {
-        unpacked[ zz ] = palette[ data[ zz ] ];
-      }
-      glPixelStorei( GL_UNPACK_ROW_LENGTH, y );
-      glTexImage2D( GL_TEXTURE_2D, ix, li->internalFormat, x, y, 0, li->externalFormat, li->type, unpacked );
-      gl->updateError();
-      x = (x+1)>>1;
-      y = (y+1)>>1;
-      size = x * y * li->blockBytes;
+  // The DXT3 image is divided into num_rows x num_cols blocks, where
+  // each block represents 4x4 pixels.  Unlike DXT1, each block
+  // consists of two 8-byte chunks, representing the alpha and color
+  // separately.
+  int num_cols = max(div, x_size) / div;
+  int num_rows = max(div, y_size) / div;
+  int row_length = num_cols * block_bytes;
+  int linear_size = row_length * num_rows;
+
+  if (n == 0) {
+    if (header.dds_flags & DDSD_LINEARSIZE) {
+      nassertr(linear_size == header.pitch, false);
     }
-    free( data );
-    free( unpacked );
   }
-  else {
-    if( li->swap ) {
-      glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_TRUE );
+
+  PTA_uchar image = PTA_uchar::empty_array(linear_size);
+
+  if (y_size >= 4) {
+    // We have to flip the image as we read it, because of DirectX's
+    // inverted sense of up.  That means we (a) reverse the order of the
+    // rows of blocks . . .
+    for (int ri = num_rows - 1; ri >= 0; --ri) {
+      unsigned char *p = image.p() + row_length * ri;
+      in.read((char *)p, row_length);
+      
+      for (int ci = 0; ci < num_cols; ++ci) {
+        // . . . and (b) within each block, we reverse the 4 individual
+        // rows of 4 pixels.
+        PN_uint32 *cells = (PN_uint32 *)p;
+
+        // Alpha.  The block is four 16-bit words of pixel data.
+        PN_uint32 w0 = cells[0];
+        PN_uint32 w1 = cells[1];
+        w0 = ((w0 & 0xffff) << 16) | ((w0 & 0xffff0000U) >> 16);
+        w1 = ((w1 & 0xffff) << 16) | ((w1 & 0xffff0000U) >> 16);
+        cells[0] = w1;
+        cells[1] = w0;
+
+        // Color.  Only the second 32-bit dword of the color block
+        // represents the pixel data.
+        PN_uint32 w = cells[3];
+        w = ((w & 0xff) << 24) | ((w & 0xff00) << 8) | ((w & 0xff0000) >> 8) | ((w & 0xff000000U) >> 24);
+        cells[3] = w;
+        
+        p += block_bytes;
+      }
     }
-    size = x * y * li->blockBytes;
-    format = li->externalFormat;
-    cFormat = li->internalFormat;
-    unsigned char * data = (unsigned char *)malloc( size );
-    //fixme: how are MIP maps stored for 24-bit if pitch != ySize*3 ?
-    for( unsigned int ix = 0; ix < mipMapCount; ++ix ) {
-      fread( data, 1, size, f );
-      glPixelStorei( GL_UNPACK_ROW_LENGTH, y );
-      glTexImage2D( GL_TEXTURE_2D, ix, li->internalFormat, x, y, 0, li->externalFormat, li->type, data );
-      gl->updateError();
-      x = (x+1)>>1;
-      y = (y+1)>>1;
-      size = x * y * li->blockBytes;
+
+  } else if (y_size >= 2) {
+    // To invert a two-pixel high image, we just flip two rows within a cell.
+    unsigned char *p = image.p();
+    in.read((char *)p, row_length);
+    
+    for (int ci = 0; ci < num_cols; ++ci) {
+      PN_uint32 *cells = (PN_uint32 *)p;
+
+      PN_uint32 w0 = cells[0];
+      w0 = ((w0 & 0xffff) << 16) | ((w0 & 0xffff0000U) >> 16);
+      cells[0] = w0;
+
+      PN_uint32 w = cells[3];
+      w = ((w & 0xff) << 8) | ((w & 0xff00) >> 8);
+      cells[3] = w;
+      
+      p += block_bytes;
     }
-    free( data );
-    glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE );
-    gl->updateError();
+
+  } else if (y_size >= 1) {
+    // No need to invert a one-pixel-high image.
+    unsigned char *p = image.p();
+    in.read((char *)p, row_length);
   }
-  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipMapCount-1 );
-  gl->updateError();
+
+  tex->set_ram_mipmap_image(n, image);
 
   return true;
+}
 
- failure:
-  return false;
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_dds_level_dxt45
+//       Access: Private, Static
+//  Description: Called by read_dds for DXT4 or DXT5 file format.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_dds_level_dxt45(Texture *tex, const DDSHeader &header, 
+                     int n, istream &in) {
+  int x_size = tex->get_expected_mipmap_x_size(n);
+  int y_size = tex->get_expected_mipmap_y_size(n);
+
+  static const int div = 4;
+  static const int block_bytes = 16;
 
-      */
+  // The DXT5 image is similar to DXT3, in that there each 4x4 block
+  // of pixels consists of an alpha block and a color block, but the
+  // layout of the alpha block is different.
+  int num_cols = max(div, x_size) / div;
+  int num_rows = max(div, y_size) / div;
+  int row_length = num_cols * block_bytes;
+  int linear_size = row_length * num_rows;
 
+  if (n == 0) {
+    if (header.dds_flags & DDSD_LINEARSIZE) {
+      nassertr(linear_size == header.pitch, false);
+    }
+  }
+
+  PTA_uchar image = PTA_uchar::empty_array(linear_size);
+
+  if (y_size >= 4) {
+    // We have to flip the image as we read it, because of DirectX's
+    // inverted sense of up.  That means we (a) reverse the order of the
+    // rows of blocks . . .
+    for (int ri = num_rows - 1; ri >= 0; --ri) {
+      unsigned char *p = image.p() + row_length * ri;
+      in.read((char *)p, row_length);
+      
+      for (int ci = 0; ci < num_cols; ++ci) {
+        // . . . and (b) within each block, we reverse the 4 individual
+        // rows of 4 pixels.
+        PN_uint32 *cells = (PN_uint32 *)p;
+
+        // Alpha.  The block is one 16-bit word of reference values,
+        // followed by six words of pixel values, in 12-bit rows.
+        // Tricky to invert.
+        unsigned char p2 = p[2];
+        unsigned char p3 = p[3];
+        unsigned char p4 = p[4];
+        unsigned char p5 = p[5];
+        unsigned char p6 = p[6];
+        unsigned char p7 = p[7];
+
+        p[2] = ((p7 & 0xf) << 4) | ((p6 & 0xf0) >> 4);
+        p[3] = ((p5 & 0xf) << 4) | ((p7 & 0xf0) >> 4);
+        p[4] = ((p6 & 0xf) << 4) | ((p5 & 0xf0) >> 4);
+        p[5] = ((p4 & 0xf) << 4) | ((p3 & 0xf0) >> 4);
+        p[6] = ((p2 & 0xf) << 4) | ((p4 & 0xf0) >> 4);
+        p[7] = ((p3 & 0xf) << 4) | ((p2 & 0xf0) >> 4);
+
+        // Color.  Only the second 32-bit dword of the color block
+        // represents the pixel data.
+        PN_uint32 w = cells[3];
+        w = ((w & 0xff) << 24) | ((w & 0xff00) << 8) | ((w & 0xff0000) >> 8) | ((w & 0xff000000U) >> 24);
+        cells[3] = w;
+        
+        p += block_bytes;
+      }
+    }
+
+  } else if (y_size >= 2) {
+    // To invert a two-pixel high image, we just flip two rows within a cell.
+    unsigned char *p = image.p();
+    in.read((char *)p, row_length);
+    
+    for (int ci = 0; ci < num_cols; ++ci) {
+      PN_uint32 *cells = (PN_uint32 *)p;
+
+      unsigned char p2 = p[2];
+      unsigned char p3 = p[3];
+      unsigned char p4 = p[4];
+      
+      p[2] = ((p4 & 0xf) << 4) | ((p3 & 0xf0) >> 4);
+      p[3] = ((p2 & 0xf) << 4) | ((p4 & 0xf0) >> 4);
+      p[4] = ((p3 & 0xf) << 4) | ((p2 & 0xf0) >> 4);
+
+      PN_uint32 w0 = cells[0];
+      w0 = ((w0 & 0xffff) << 16) | ((w0 & 0xffff0000U) >> 16);
+      cells[0] = w0;
+
+      PN_uint32 w = cells[3];
+      w = ((w & 0xff) << 8) | ((w & 0xff00) >> 8);
+      cells[3] = w;
+      
+      p += block_bytes;
+    }
+
+  } else if (y_size >= 1) {
+    // No need to invert a one-pixel-high image.
+    unsigned char *p = image.p();
+    in.read((char *)p, row_length);
+  }
+
+  tex->set_ram_mipmap_image(n, image);
+
+  return true;
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::clear_prepared

+ 9 - 3
panda/src/gobj/texture.h

@@ -477,9 +477,15 @@ private:
   static bool read_dds_level_generic_uncompressed(Texture *tex, 
                                                   const DDSHeader &header, 
                                                   int n, istream &in);
-  static bool read_dds_level_compressed(Texture *tex, 
-                                        const DDSHeader &header, 
-                                        int n, istream &in);
+  static bool read_dds_level_dxt1(Texture *tex, 
+                                  const DDSHeader &header, 
+                                  int n, istream &in);
+  static bool read_dds_level_dxt23(Texture *tex, 
+                                   const DDSHeader &header, 
+                                   int n, istream &in);
+  static bool read_dds_level_dxt45(Texture *tex, 
+                                   const DDSHeader &header, 
+                                   int n, istream &in);
 
   void clear_prepared(PreparedGraphicsObjects *prepared_objects);