Browse Source

read dds files

David Rose 17 years ago
parent
commit
c76a53486f

+ 4 - 10
panda/src/framework/windowFramework.cxx

@@ -598,7 +598,7 @@ load_model(const NodePath &parent, Filename filename) {
     if (model_type == (LoaderFileType *)NULL) {
     if (model_type == (LoaderFileType *)NULL) {
       // The extension isn't a known model file type; is it a known
       // The extension isn't a known model file type; is it a known
       // image file extension?
       // image file extension?
-      if (extension == "txo") {
+      if (extension == "txo" || downcase(extension) == "dds") {
         // A texture object.  Not exactly an image, but certainly a
         // A texture object.  Not exactly an image, but certainly a
         // texture.
         // texture.
         is_image = true;
         is_image = true;
@@ -1162,17 +1162,11 @@ load_image_as_model(const Filename &filename) {
     x_size = vtex->get_video_width();
     x_size = vtex->get_video_width();
     y_size = vtex->get_video_height();
     y_size = vtex->get_video_height();
     tex_scale = vtex->get_tex_scale();
     tex_scale = vtex->get_tex_scale();
-  } else if (tex->is_of_type(MovieTexture::get_class_type())) {
-    // Don't try to read a PNMImageHeader.
-  } else if (!tex->get_loaded_from_txo()) {
+  } else {
     // Get the size from the original image (the texture may have
     // Get the size from the original image (the texture may have
     // scaled it to make a power of 2).
     // scaled it to make a power of 2).
-    PNMImageHeader header;
-    if (header.read_header(filename)) {
-      x_size = header.get_x_size();
-      y_size = header.get_y_size();
-      has_alpha = header.has_alpha();
-    }
+    x_size = tex->get_orig_file_x_size();
+    y_size = tex->get_orig_file_y_size();
   }
   }
 
 
   // Yes, it is an image file; make a texture out of it.
   // Yes, it is an image file; make a texture out of it.

+ 17 - 0
panda/src/gobj/texture.I

@@ -1473,6 +1473,23 @@ is_txo_filename(const Filename &fullpath) {
   return (extension == "txo");
   return (extension == "txo");
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::is_dds_filename
+//       Access: Private, Static
+//  Description: Returns true if the indicated filename ends in .dds
+//               or .dds.pz, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool Texture::
+is_dds_filename(const Filename &fullpath) {
+  string extension = fullpath.get_extension();
+#ifdef HAVE_ZLIB
+  if (extension == "pz") {
+    extension = Filename(fullpath.get_basename_wo_extension()).get_extension();
+  }
+#endif  // HAVE_ZLIB
+  return (downcase(extension) == "dds");
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::set_textures_power_2
 //     Function: Texture::set_textures_power_2
 //       Access: Published, Static
 //       Access: Published, Static

+ 762 - 1
panda/src/gobj/texture.cxx

@@ -35,6 +35,8 @@
 #include "indent.h"
 #include "indent.h"
 #include "cmath.h"
 #include "cmath.h"
 #include "pStatTimer.h"
 #include "pStatTimer.h"
+#include "pbitops.h"
+#include "streamReader.h"
 
 
 #include <stddef.h>
 #include <stddef.h>
 
 
@@ -52,6 +54,168 @@ PStatCollector Texture::_texture_read_pcollector("*:Texture:Read");
 TypeHandle Texture::_type_handle;
 TypeHandle Texture::_type_handle;
 AutoTextureScale Texture::_textures_power_2 = ATS_UNSPECIFIED;
 AutoTextureScale Texture::_textures_power_2 = ATS_UNSPECIFIED;
 
 
+// Stuff to read and write DDS files.
+
+//  little-endian, of course
+#define DDS_MAGIC 0x20534444
+
+
+//  DDS_header.dwFlags
+#define DDSD_CAPS                   0x00000001 
+#define DDSD_HEIGHT                 0x00000002 
+#define DDSD_WIDTH                  0x00000004 
+#define DDSD_PITCH                  0x00000008 
+#define DDSD_PIXELFORMAT            0x00001000 
+#define DDSD_MIPMAPCOUNT            0x00020000 
+#define DDSD_LINEARSIZE             0x00080000 
+#define DDSD_DEPTH                  0x00800000 
+
+//  DDS_header.sPixelFormat.dwFlags
+#define DDPF_ALPHAPIXELS            0x00000001 
+#define DDPF_FOURCC                 0x00000004 
+#define DDPF_INDEXED                0x00000020 
+#define DDPF_RGB                    0x00000040 
+
+//  DDS_header.sCaps.dwCaps1
+#define DDSCAPS_COMPLEX             0x00000008 
+#define DDSCAPS_TEXTURE             0x00001000 
+#define DDSCAPS_MIPMAP              0x00400000 
+
+//  DDS_header.sCaps.dwCaps2
+#define DDSCAPS2_CUBEMAP            0x00000200 
+#define DDSCAPS2_CUBEMAP_POSITIVEX  0x00000400 
+#define DDSCAPS2_CUBEMAP_NEGATIVEX  0x00000800 
+#define DDSCAPS2_CUBEMAP_POSITIVEY  0x00001000 
+#define DDSCAPS2_CUBEMAP_NEGATIVEY  0x00002000 
+#define DDSCAPS2_CUBEMAP_POSITIVEZ  0x00004000 
+#define DDSCAPS2_CUBEMAP_NEGATIVEZ  0x00008000 
+#define DDSCAPS2_VOLUME             0x00200000 
+
+#define D3DFMT_DXT1     '1TXD'    //  DXT1 compression texture format 
+#define D3DFMT_DXT2     '2TXD'    //  DXT2 compression texture format 
+#define D3DFMT_DXT3     '3TXD'    //  DXT3 compression texture format 
+#define D3DFMT_DXT4     '4TXD'    //  DXT4 compression texture format 
+#define D3DFMT_DXT5     '5TXD'    //  DXT5 compression texture format 
+
+#define PF_IS_DXT1(pf) \
+  ((pf.dwFlags & DDPF_FOURCC) && \
+   (pf.dwFourCC == D3DFMT_DXT1))
+
+#define PF_IS_DXT3(pf) \
+  ((pf.dwFlags & DDPF_FOURCC) && \
+   (pf.dwFourCC == D3DFMT_DXT3))
+
+#define PF_IS_DXT5(pf) \
+  ((pf.dwFlags & DDPF_FOURCC) && \
+   (pf.dwFourCC == D3DFMT_DXT5))
+
+#define PF_IS_BGRA8(pf) \
+  ((pf.dwFlags & DDPF_RGB) && \
+   (pf.dwFlags & DDPF_ALPHAPIXELS) && \
+   (pf.dwRGBBitCount == 32) && \
+   (pf.dwRBitMask == 0xff0000) && \
+   (pf.dwGBitMask == 0xff00) && \
+   (pf.dwBBitMask == 0xff) && \
+   (pf.dwAlphaBitMask == 0xff000000U))
+
+#define PF_IS_BGR8(pf) \
+  ((pf.dwFlags & DDPF_ALPHAPIXELS) && \
+  !(pf.dwFlags & DDPF_ALPHAPIXELS) && \
+   (pf.dwRGBBitCount == 24) && \
+   (pf.dwRBitMask == 0xff0000) && \
+   (pf.dwGBitMask == 0xff00) && \
+   (pf.dwBBitMask == 0xff))
+
+#define PF_IS_BGR5A1(pf) \
+  ((pf.dwFlags & DDPF_RGB) && \
+   (pf.dwFlags & DDPF_ALPHAPIXELS) && \
+   (pf.dwRGBBitCount == 16) && \
+   (pf.dwRBitMask == 0x00007c00) && \
+   (pf.dwGBitMask == 0x000003e0) && \
+   (pf.dwBBitMask == 0x0000001f) && \
+   (pf.dwAlphaBitMask == 0x00008000))
+
+#define PF_IS_BGR565(pf) \
+  ((pf.dwFlags & DDPF_RGB) && \
+  !(pf.dwFlags & DDPF_ALPHAPIXELS) && \
+   (pf.dwRGBBitCount == 16) && \
+   (pf.dwRBitMask == 0x0000f800) && \
+   (pf.dwGBitMask == 0x000007e0) && \
+   (pf.dwBBitMask == 0x0000001f))
+
+#define PF_IS_INDEX8(pf) \
+  ((pf.dwFlags & DDPF_INDEXED) && \
+   (pf.dwRGBBitCount == 8))
+
+struct DDSPixelFormat {
+  unsigned int pf_size;
+  unsigned int pf_flags;
+  unsigned int four_cc;
+  unsigned int rgb_bitcount;
+  unsigned int r_mask;
+  unsigned int g_mask;
+  unsigned int b_mask;
+  unsigned int a_mask;
+};
+
+struct DDSCaps2 {
+  unsigned int caps1;
+  unsigned int caps2;
+  unsigned int ddsx;
+};
+
+struct DDSHeader {
+  unsigned int dds_magic;
+  unsigned int dds_size;
+  unsigned int dds_flags;
+  unsigned int height;
+  unsigned int width;
+  unsigned int pitch;
+  unsigned int depth;
+  unsigned int num_levels;
+
+  DDSPixelFormat pf;
+  DDSCaps2 caps;
+};
+
+struct DdsLoadInfo {
+  bool compressed;
+  bool swap;
+  bool palette;
+  unsigned int divSize;
+  unsigned int blockBytes;
+  /*
+  GLenum internalFormat;
+  GLenum externalFormat;
+  GLenum type;
+  */
+};
+
+DdsLoadInfo loadInfoDXT1 = {
+  true, false, false, 4, 8, // GL_COMPRESSED_RGBA_S3TC_DXT1
+};
+DdsLoadInfo loadInfoDXT3 = {
+  true, false, false, 4, 16, // GL_COMPRESSED_RGBA_S3TC_DXT3
+};
+DdsLoadInfo loadInfoDXT5 = {
+  true, false, false, 4, 16, // GL_COMPRESSED_RGBA_S3TC_DXT5
+};
+DdsLoadInfo loadInfoBGRA8 = {
+  false, false, false, 1, 4, // GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE
+};
+DdsLoadInfo loadInfoBGR8 = {
+  false, false, false, 1, 3, // GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE
+};
+DdsLoadInfo loadInfoBGR5A1 = {
+  false, true, false, 1, 2, // GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV
+};
+DdsLoadInfo loadInfoBGR565 = {
+  false, true, false, 1, 2, // GL_RGB5, GL_RGB, GL_UNSIGNED_SHORT_5_6_5
+};
+DdsLoadInfo loadInfoIndex8 = {
+  false, false, true, 1, 1, // GL_RGB8, GL_BGRA, GL_UNSIGNED_BYTE
+};
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::Constructor
 //     Function: Texture::Constructor
 //       Access: Published
 //       Access: Published
@@ -785,6 +949,155 @@ write_txo(ostream &out, const string &filename) const {
   return true;
   return true;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_dds
+//       Access: Published
+//  Description: Reads the texture from a DDS file object.  This is a
+//               Microsoft-defined file format; it is similar in
+//               principle to a txo object, in that it is designed to
+//               contain the texture image in a form as similar as
+//               possible to its runtime image, and it can contain
+//               mipmaps, pre-compressed textures, and so on.
+//
+//               As with read_txo, the filename is just for reference.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_dds(istream &in, const string &filename, bool header_only) {
+  ReMutexHolder holder(_lock);
+  
+  StreamReader dds(in);
+  
+  // DDS header
+  DDSHeader header;
+  header.dds_magic = dds.get_uint32();
+  header.dds_size = dds.get_uint32();
+  header.dds_flags = dds.get_uint32();
+  header.height = dds.get_uint32();
+  header.width = dds.get_uint32();
+  header.pitch = dds.get_uint32();
+  header.depth = dds.get_uint32();
+  header.num_levels = dds.get_uint32();
+  dds.skip_bytes(44);
+  
+  // Pixelformat
+  header.pf.pf_size = dds.get_uint32();
+  header.pf.pf_flags = dds.get_uint32();
+  header.pf.four_cc = dds.get_uint32();
+  header.pf.rgb_bitcount = dds.get_uint32();
+  header.pf.r_mask = dds.get_uint32();
+  header.pf.g_mask = dds.get_uint32();
+  header.pf.b_mask = dds.get_uint32();
+  header.pf.a_mask = dds.get_uint32();
+  
+  // Caps
+  header.caps.caps1 = dds.get_uint32();
+  header.caps.caps2 = dds.get_uint32();
+  header.caps.ddsx = dds.get_uint32();
+  dds.skip_bytes(4);
+  
+  if (header.dds_magic != DDS_MAGIC || (in.fail() || in.eof())) {
+    gobj_cat.error()
+      << filename << " is not a DDS file.\n";
+    return false;
+  }
+
+  if ((header.dds_flags & DDSD_MIPMAPCOUNT) == 0) {
+    // No bit set means only the base mipmap level.
+    header.num_levels = 1;
+  }
+
+  // Determine the function to use to read the DDS image.
+  typedef bool (*ReadDDSLevelFunc)(Texture *tex, const DDSHeader &header, 
+                                   int n, istream &in);
+  ReadDDSLevelFunc func = NULL;
+
+  Format format = F_rgb;
+
+  clear_ram_image();
+  CompressionMode compression = CM_off;
+
+  if (header.pf.pf_flags & DDPF_FOURCC) {
+    // Some compressed texture format.
+    if (header.pf.four_cc == 0x31545844) {   // 'DXT1', little-endian.
+      compression = CM_dxt1;
+    } else if (header.pf.four_cc == 0x33545844) {   // 'DXT3'
+      compression = CM_dxt3;
+    } else if (header.pf.four_cc == 0x35545844) {   // 'DXT5'
+      compression = CM_dxt5;
+    } else {
+      gobj_cat.error()
+        << filename << ": unsupported texture compression.\n";
+      return false;
+    }
+
+    func = read_dds_level_compressed;
+
+  } else {
+    // An uncompressed texture format.
+    func = read_dds_level_generic_uncompressed;
+
+    if (header.pf.pf_flags & DDPF_ALPHAPIXELS) {
+      // An uncompressed format that involves alpha.
+      format = F_rgba;
+      if (header.pf.rgb_bitcount == 32 &&
+          header.pf.r_mask == 0x000000ff &&
+          header.pf.g_mask == 0x0000ff00 &&
+          header.pf.b_mask == 0x00ff0000 &&
+          header.pf.a_mask == 0xff000000U) {
+        func = read_dds_level_abgr8;
+      } else if (header.pf.rgb_bitcount == 32 &&
+          header.pf.r_mask == 0x00ff0000 &&
+          header.pf.g_mask == 0x0000ff00 &&
+          header.pf.b_mask == 0x000000ff &&
+          header.pf.a_mask == 0xff000000U) {
+        func = read_dds_level_rgba8;
+      }
+    } else {
+      // An uncompressed format that doesn't involve alpha.
+      if (header.pf.rgb_bitcount == 24 &&
+          header.pf.r_mask == 0x00ff0000 &&
+          header.pf.g_mask == 0x0000ff00 &&
+          header.pf.b_mask == 0x000000ff) {
+        func = read_dds_level_rgb8;
+      } 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;
+      }
+    }
+  }
+
+  setup_2d_texture(header.width, header.height, T_unsigned_byte, format);
+  _orig_file_x_size = _x_size;
+  _orig_file_y_size = _y_size;
+  _compression = compression;
+  _ram_image_compression = compression;
+
+  if (!header_only) {
+    for (int n = 0; n < (int)header.num_levels; ++n) {
+      if (!func(this, header, n, in)) {
+        return false;
+      }
+    }
+    _has_read_pages = true;
+    _has_read_mipmaps = true;
+    _num_mipmap_levels_read = _ram_images.size();
+  }
+
+  if (in.fail() || in.eof()) {
+    gobj_cat.error()
+      << filename << ": truncated DDS file.\n";
+    return false;
+  }
+
+  ++_properties_modified;
+  _loaded_from_image = true;
+  _loaded_from_txo = true;
+
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::reload
 //     Function: Texture::reload
 //       Access: Published
 //       Access: Published
@@ -2193,6 +2506,13 @@ do_read(const Filename &fullpath, const Filename &alpha_fullpath,
     return read_txo_file(fullpath);
     return read_txo_file(fullpath);
   }
   }
 
 
+  if (is_dds_filename(fullpath)) {
+    if (record != (BamCacheRecord *)NULL) {
+      record->add_dependent_file(fullpath);
+    }
+    return read_dds_file(fullpath, header_only);
+  }
+
   // If read_pages or read_mipmaps is specified, then z and n actually
   // If read_pages or read_mipmaps is specified, then z and n actually
   // indicate z_size and n_size, respectively--the numerical limits on
   // indicate z_size and n_size, respectively--the numerical limits on
   // which to search for filenames.
   // which to search for filenames.
@@ -2865,7 +3185,6 @@ reload_ram_image() {
 
 
   gobj_cat.info()
   gobj_cat.info()
     << "Reloading texture " << get_name() << "\n";
     << "Reloading texture " << get_name() << "\n";
-  do_make_ram_image();
 
 
   int z = 0;
   int z = 0;
   int n = 0;
   int n = 0;
@@ -3396,6 +3715,410 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size,
   return true;
   return true;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_dds_level_bgr8
+//       Access: Private, Static
+//  Description: Called by read_dds for a DDS file in BGR8 format.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_dds_level_bgr8(Texture *tex, const DDSHeader &header, 
+                    int n, istream &in) {
+  // This is laid out 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);
+
+  size_t size = tex->get_expected_ram_mipmap_image_size(n);
+  size_t row_bytes = x_size * 3;
+  PTA_uchar image = PTA_uchar::empty_array(size);
+  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();
+
+      store_unscaled_byte(p, b);
+      store_unscaled_byte(p, g);
+      store_unscaled_byte(p, r);
+    }
+    nassertr(p <= image.p() + size, false);
+  }
+
+  tex->set_ram_mipmap_image(n, image);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_dds_level_rgb8
+//       Access: Private, Static
+//  Description: Called by read_dds for a DDS file in RGB8 format.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_dds_level_rgb8(Texture *tex, const DDSHeader &header, 
+                    int n, istream &in) {
+  // This is laid out 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);
+
+  size_t size = tex->get_expected_ram_mipmap_image_size(n);
+  size_t row_bytes = x_size * 3;
+  PTA_uchar image = PTA_uchar::empty_array(size);
+  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();
+
+      store_unscaled_byte(p, b);
+      store_unscaled_byte(p, g);
+      store_unscaled_byte(p, r);
+    }
+    nassertr(p <= image.p() + size, false);
+  }
+
+  tex->set_ram_mipmap_image(n, image);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_dds_level_abgr8
+//       Access: Private, Static
+//  Description: Called by read_dds for a DDS file in ABGR8 format.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_dds_level_abgr8(Texture *tex, const DDSHeader &header, 
+                     int n, istream &in) {
+  // This is laid out in order R, G, B, A.
+  int x_size = tex->get_expected_mipmap_x_size(n);
+  int y_size = tex->get_expected_mipmap_y_size(n);
+
+  size_t size = tex->get_expected_ram_mipmap_image_size(n);
+  size_t row_bytes = x_size * 4;
+  PTA_uchar image = PTA_uchar::empty_array(size);
+  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 a = (unsigned char)in.get();
+
+      store_unscaled_byte(p, b);
+      store_unscaled_byte(p, g);
+      store_unscaled_byte(p, r);
+      store_unscaled_byte(p, a);
+    }
+    nassertr(p <= image.p() + size, false);
+  }
+
+  tex->set_ram_mipmap_image(n, image);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_dds_level_rgba8
+//       Access: Private, Static
+//  Description: Called by read_dds for a DDS file in RGBA8 format.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_dds_level_rgba8(Texture *tex, const DDSHeader &header, 
+                     int n, istream &in) {
+  // This is actually laid out in order B, G, R, A.
+  int x_size = tex->get_expected_mipmap_x_size(n);
+  int y_size = tex->get_expected_mipmap_y_size(n);
+
+  size_t size = tex->get_expected_ram_mipmap_image_size(n);
+  size_t row_bytes = x_size * 4;
+  PTA_uchar image = PTA_uchar::empty_array(size);
+  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 a = (unsigned char)in.get();
+
+      store_unscaled_byte(p, b);
+      store_unscaled_byte(p, g);
+      store_unscaled_byte(p, r);
+      store_unscaled_byte(p, a);
+    }
+    nassertr(p <= image.p() + size, false);
+  }
+
+  tex->set_ram_mipmap_image(n, image);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_dds_level_generic_uncompressed
+//       Access: Private, Static
+//  Description: Called by read_dds for a DDS file whose format isn't
+//               one we've specifically optimized.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_dds_level_generic_uncompressed(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 pitch = (x_size * header.pf.rgb_bitcount) / 8;
+
+  // MS says the pitch can be supplied in the header file and must be
+  // DWORD aligned, but this appears to apply to level 0 mipmaps only
+  // (where it almost always will be anyway).  Other mipmap levels
+  // seem to be tightly packed, but there isn't a separate pitch for
+  // each mipmap level.  Weird.
+  if (n == 0) {
+    pitch = ((pitch + 3) / 4) * 4;
+    if (header.dds_flags & DDSD_PITCH) {
+      pitch = header.pitch;
+    }
+  }
+
+  int bpp = header.pf.rgb_bitcount / 8;
+  int skip_bytes = pitch - (bpp * x_size);
+  nassertr(skip_bytes >= 0, false);
+
+  unsigned int r_mask = header.pf.r_mask;
+  unsigned int g_mask = header.pf.g_mask;
+  unsigned int b_mask = header.pf.b_mask;
+  unsigned int a_mask = header.pf.a_mask;
+
+  // Determine the number of bits to shift each mask to the right so
+  // that the lowest on bit is at bit 0.
+  int r_shift = get_lowest_on_bit(r_mask);
+  int g_shift = get_lowest_on_bit(g_mask);
+  int b_shift = get_lowest_on_bit(b_mask);
+  int a_shift = get_lowest_on_bit(a_mask);
+
+  // Then determine the scale factor required to raise the highest
+  // color value to 0xff000000.
+  unsigned int r_scale = 0;
+  if (r_mask != 0) {
+    r_scale = 0xff000000 / (r_mask >> r_shift);
+  }
+  unsigned int g_scale = 0;
+  if (g_mask != 0) {
+    g_scale = 0xff000000 / (g_mask >> g_shift);
+  }
+  unsigned int b_scale = 0;
+  if (b_mask != 0) {
+    b_scale = 0xff000000 / (b_mask >> b_shift);
+  }
+  unsigned int a_scale = 0;
+  if (a_mask != 0) {
+    a_scale = 0xff000000 / (a_mask >> a_shift);
+  }
+
+  bool add_alpha = has_alpha(tex->_format);
+
+  size_t size = tex->get_expected_ram_mipmap_image_size(n);
+  size_t row_bytes = x_size * tex->get_num_components();
+  PTA_uchar image = PTA_uchar::empty_array(size);
+  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) {
+
+      // Read a little-endian numeric value of bpp bytes.
+      unsigned int pixel = 0;
+      int shift = 0;
+      for (int b = 0; b < bpp; ++b) {
+        unsigned int ch = (unsigned char)in.get();
+        pixel |= (ch << shift);
+        shift += 8;
+      }
+
+      // Then break apart that value into its R, G, B, and maybe A
+      // components.
+      unsigned int r = (((pixel & r_mask) >> r_shift) * r_scale) >> 24;
+      unsigned int g = (((pixel & g_mask) >> g_shift) * g_scale) >> 24;
+      unsigned int b = (((pixel & b_mask) >> b_shift) * b_scale) >> 24;
+
+      // Store the components in the Texture's image data.
+      store_unscaled_byte(p, b);
+      store_unscaled_byte(p, g);
+      store_unscaled_byte(p, r);
+      if (add_alpha) {
+        unsigned int a = (((pixel & a_mask) >> a_shift) * a_scale) >> 24;
+        store_unscaled_byte(p, a);
+      }
+    }
+    nassertr(p <= image.p() + size, false);
+    for (int b = 0; b < skip_bytes; ++b) {
+      in.get();
+    }
+  }
+
+  tex->set_ram_mipmap_image(n, image);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_dds_level_compressed
+//       Access: Private, Static
+//  Description: Called by read_dds for a compressed DDS file format.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_dds_level_compressed(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;
+
+  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;
+
+  if (n == 0) {
+    if (header.dds_flags & DDSD_LINEARSIZE) {
+      nassertr(linear_size == header.pitch, false);
+    }
+  }
+
+  PTA_uchar image = PTA_uchar::empty_array(linear_size);
+  in.read((char *)image.p(), linear_size);
+
+  tex->set_ram_mipmap_image(n, image);
+
+  return true;
+}
+      
+
+      /*
+  DdsLoadInfo * li;
+
+  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;
+  }
+
+  //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;
+    }
+    free( data );
+    free( unpacked );
+  }
+  else {
+    if( li->swap ) {
+      glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_TRUE );
+    }
+    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;
+    }
+    free( data );
+    glPixelStorei( GL_UNPACK_SWAP_BYTES, GL_FALSE );
+    gl->updateError();
+  }
+  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipMapCount-1 );
+  gl->updateError();
+
+  return true;
+
+ failure:
+  return false;
+
+      */
+
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::clear_prepared
 //     Function: Texture::clear_prepared
 //       Access: Private
 //       Access: Private
@@ -3546,6 +4269,44 @@ read_txo_file(const Filename &fullpath) {
   return success;
   return success;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Texture::read_dds_file
+//       Access: Private
+//  Description: Called internally when read() detects a DDS file.
+////////////////////////////////////////////////////////////////////
+bool Texture::
+read_dds_file(const Filename &fullpath, bool header_only) {
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+
+  Filename filename = Filename::binary_filename(fullpath);
+  PT(VirtualFile) file = vfs->get_file(filename);
+  if (file == (VirtualFile *)NULL) {
+    // No such file.
+    gobj_cat.error()
+      << "Could not find " << fullpath << "\n";
+    return false;
+  }
+  
+  if (gobj_cat.is_debug()) {
+    gobj_cat.debug()
+      << "Reading DDS file " << filename << "\n";
+  }
+  
+  istream *in = file->open_read_file(true);
+  bool success = read_dds(*in, fullpath, header_only);
+  vfs->close_read_file(in);
+
+  if (!has_name()) {
+    set_name(fullpath.get_basename_wo_extension());
+  }
+  
+  set_fullpath(fullpath);
+  clear_alpha_fullpath();
+  _keep_ram_image = false;
+  
+  return success;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Texture::write_txo_file
 //     Function: Texture::write_txo_file
 //       Access: Private
 //       Access: Private

+ 21 - 0
panda/src/gobj/texture.h

@@ -29,6 +29,7 @@
 #include "reMutex.h"
 #include "reMutex.h"
 #include "reMutexHolder.h"
 #include "reMutexHolder.h"
 #include "loaderOptions.h"
 #include "loaderOptions.h"
+#include "string_utils.h"
 
 
 class PNMImage;
 class PNMImage;
 class TextureContext;
 class TextureContext;
@@ -37,6 +38,7 @@ class PreparedGraphicsObjects;
 class CullTraverser;
 class CullTraverser;
 class CullTraverserData;
 class CullTraverserData;
 class BamCacheRecord;
 class BamCacheRecord;
+struct DDSHeader;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : Texture
 //       Class : Texture
@@ -231,6 +233,7 @@ PUBLISHED:
 
 
   bool read_txo(istream &in, const string &filename = "stream");
   bool read_txo(istream &in, const string &filename = "stream");
   bool write_txo(ostream &out, const string &filename = "stream") const;
   bool write_txo(ostream &out, const string &filename = "stream") const;
+  bool read_dds(istream &in, const string &filename = "stream", bool header_only = false);
 
 
   INLINE bool load(const PNMImage &pnmimage);
   INLINE bool load(const PNMImage &pnmimage);
   INLINE bool load(const PNMImage &pnmimage, int z, int n);
   INLINE bool load(const PNMImage &pnmimage, int z, int n);
@@ -463,6 +466,21 @@ private:
                                   int num_components, int component_width,
                                   int num_components, int component_width,
                                   CPTA_uchar image, size_t page_size, 
                                   CPTA_uchar image, size_t page_size, 
                                   int z);
                                   int z);
+  static bool read_dds_level_rgb8(Texture *tex, const DDSHeader &header, 
+                                  int n, istream &in);
+  static bool read_dds_level_bgr8(Texture *tex, const DDSHeader &header, 
+                                  int n, istream &in);
+  static bool read_dds_level_abgr8(Texture *tex, const DDSHeader &header, 
+                                   int n, istream &in);
+  static bool read_dds_level_rgba8(Texture *tex, const DDSHeader &header, 
+                                   int n, istream &in);
+  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);
+
   void clear_prepared(PreparedGraphicsObjects *prepared_objects);
   void clear_prepared(PreparedGraphicsObjects *prepared_objects);
 
 
   void consider_rescale(PNMImage &pnmimage, const string &name);
   void consider_rescale(PNMImage &pnmimage, const string &name);
@@ -481,6 +499,9 @@ private:
   bool read_txo_file(const Filename &fullpath);
   bool read_txo_file(const Filename &fullpath);
   bool write_txo_file(const Filename &fullpath) const;
   bool write_txo_file(const Filename &fullpath) const;
 
 
+  INLINE static bool is_dds_filename(const Filename &fullpath);
+  bool read_dds_file(const Filename &fullpath, bool header_only);
+
   void filter_2d_mipmap_pages(RamImage &to, const RamImage &from,
   void filter_2d_mipmap_pages(RamImage &to, const RamImage &from,
                               int x_size, int y_size);
                               int x_size, int y_size);
 
 

+ 1 - 6
panda/src/putil/bitMask.I

@@ -429,12 +429,7 @@ get_num_off_bits() const {
 template<class WType, int nbits>
 template<class WType, int nbits>
 INLINE int BitMask<WType, nbits>::
 INLINE int BitMask<WType, nbits>::
 get_lowest_on_bit() const {
 get_lowest_on_bit() const {
-  if (_word == 0) {
-    return -1;
-  }
-
-  WordType w = (_word & (~_word + 1));
-  return count_bits_in_word(w - 1);
+  return ::get_lowest_on_bit(_word);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 30 - 0
panda/src/putil/pbitops.I

@@ -93,6 +93,36 @@ flood_bits_up(PN_uint64 x) {
   return x;
   return x;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: get_lowest_on_bit
+//  Description: Returns the index of the lowest 1 bit in the word.
+//               Returns -1 if there are no 1 bits.
+////////////////////////////////////////////////////////////////////
+INLINE int
+get_lowest_on_bit(PN_uint32 x) {
+  if (x == 0) {
+    return -1;
+  }
+
+  PN_uint32 w = (x & (~x + 1));
+  return count_bits_in_word(w - 1);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: get_lowest_on_bit
+//  Description: Returns the index of the lowest 1 bit in the word.
+//               Returns -1 if there are no 1 bits.
+////////////////////////////////////////////////////////////////////
+INLINE int
+get_lowest_on_bit(PN_uint64 x) {
+  if (x == 0) {
+    return -1;
+  }
+
+  PN_uint64 w = (x & (~x + 1));
+  return count_bits_in_word(w - 1);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: get_highest_on_bit
 //     Function: get_highest_on_bit
 //  Description: Returns the index of the highest 1 bit in the word.
 //  Description: Returns the index of the highest 1 bit in the word.

+ 2 - 0
panda/src/putil/pbitops.h

@@ -31,6 +31,8 @@ INLINE PN_uint64 flood_bits_down(PN_uint64 x);
 INLINE PN_uint32 flood_bits_up(PN_uint32 x);
 INLINE PN_uint32 flood_bits_up(PN_uint32 x);
 INLINE PN_uint64 flood_bits_up(PN_uint64 x);
 INLINE PN_uint64 flood_bits_up(PN_uint64 x);
 
 
+INLINE int get_lowest_on_bit(PN_uint32 x);
+INLINE int get_lowest_on_bit(PN_uint64 x);
 INLINE int get_highest_on_bit(PN_uint32 x);
 INLINE int get_highest_on_bit(PN_uint32 x);
 INLINE int get_highest_on_bit(PN_uint64 x);
 INLINE int get_highest_on_bit(PN_uint64 x);