diff --git a/panda/src/framework/windowFramework.cxx b/panda/src/framework/windowFramework.cxx index b64c9c84d3..794aa11bca 100644 --- a/panda/src/framework/windowFramework.cxx +++ b/panda/src/framework/windowFramework.cxx @@ -598,7 +598,7 @@ load_model(const NodePath &parent, Filename filename) { if (model_type == (LoaderFileType *)NULL) { // The extension isn't a known model file type; is it a known // image file extension? - if (extension == "txo") { + if (extension == "txo" || downcase(extension) == "dds") { // A texture object. Not exactly an image, but certainly a // texture. is_image = true; @@ -1162,17 +1162,11 @@ load_image_as_model(const Filename &filename) { x_size = vtex->get_video_width(); y_size = vtex->get_video_height(); 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 // 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. diff --git a/panda/src/gobj/texture.I b/panda/src/gobj/texture.I index 702e9675a7..e7cee19abb 100644 --- a/panda/src/gobj/texture.I +++ b/panda/src/gobj/texture.I @@ -1473,6 +1473,23 @@ is_txo_filename(const Filename &fullpath) { 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 // Access: Published, Static diff --git a/panda/src/gobj/texture.cxx b/panda/src/gobj/texture.cxx index 3823e31a30..9cb56864b5 100644 --- a/panda/src/gobj/texture.cxx +++ b/panda/src/gobj/texture.cxx @@ -35,6 +35,8 @@ #include "indent.h" #include "cmath.h" #include "pStatTimer.h" +#include "pbitops.h" +#include "streamReader.h" #include @@ -52,6 +54,168 @@ PStatCollector Texture::_texture_read_pcollector("*:Texture:Read"); TypeHandle Texture::_type_handle; 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 // Access: Published @@ -785,6 +949,155 @@ write_txo(ostream &out, const string &filename) const { 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 // Access: Published @@ -2193,6 +2506,13 @@ do_read(const Filename &fullpath, const Filename &alpha_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 // indicate z_size and n_size, respectively--the numerical limits on // which to search for filenames. @@ -2865,7 +3185,6 @@ reload_ram_image() { gobj_cat.info() << "Reloading texture " << get_name() << "\n"; - do_make_ram_image(); int z = 0; int n = 0; @@ -3396,6 +3715,410 @@ convert_to_pnmimage(PNMImage &pnmimage, int x_size, int y_size, 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 // Access: Private @@ -3546,6 +4269,44 @@ read_txo_file(const Filename &fullpath) { 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 // Access: Private diff --git a/panda/src/gobj/texture.h b/panda/src/gobj/texture.h index f3f0d09148..f6efb30cf9 100644 --- a/panda/src/gobj/texture.h +++ b/panda/src/gobj/texture.h @@ -29,6 +29,7 @@ #include "reMutex.h" #include "reMutexHolder.h" #include "loaderOptions.h" +#include "string_utils.h" class PNMImage; class TextureContext; @@ -37,6 +38,7 @@ class PreparedGraphicsObjects; class CullTraverser; class CullTraverserData; class BamCacheRecord; +struct DDSHeader; //////////////////////////////////////////////////////////////////// // Class : Texture @@ -231,6 +233,7 @@ PUBLISHED: bool read_txo(istream &in, const string &filename = "stream"); 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, int z, int n); @@ -463,6 +466,21 @@ private: int num_components, int component_width, CPTA_uchar image, size_t page_size, 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 consider_rescale(PNMImage &pnmimage, const string &name); @@ -481,6 +499,9 @@ private: bool read_txo_file(const Filename &fullpath); 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, int x_size, int y_size); diff --git a/panda/src/putil/bitMask.I b/panda/src/putil/bitMask.I index 3e7147181f..ffaf45052d 100644 --- a/panda/src/putil/bitMask.I +++ b/panda/src/putil/bitMask.I @@ -429,12 +429,7 @@ get_num_off_bits() const { template INLINE int BitMask:: 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); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/putil/pbitops.I b/panda/src/putil/pbitops.I index 943f6b671a..36487ba082 100644 --- a/panda/src/putil/pbitops.I +++ b/panda/src/putil/pbitops.I @@ -93,6 +93,36 @@ flood_bits_up(PN_uint64 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 // Description: Returns the index of the highest 1 bit in the word. diff --git a/panda/src/putil/pbitops.h b/panda/src/putil/pbitops.h index a09152f42e..d85a68f3f9 100644 --- a/panda/src/putil/pbitops.h +++ b/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_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_uint64 x);