From 45901c935cb68d6103f292baa6aa6e41f5cd6462 Mon Sep 17 00:00:00 2001 From: "richgel99@gmail.com" Date: Mon, 16 Apr 2012 00:58:38 +0000 Subject: [PATCH] - Fixing DDS reader so it doesn't require the source pitch/linear size field to be divisible by 4 - Fixing DDS writer so it writes a non-zero pitch/linearsize field (a few DDS readers in the wild require this field to be set) - Fixing example2.cpp and example3.cpp so they write non-zero pitch/linearsize fields. - Misc merges from the ddsexport branch - Adding -usesourceformat command line option, useful when reprocessing a lot of existing DDS files. --- crnlib/crn_core.cpp | 2 +- crnlib/crn_dds_texture.cpp | 23 ++++++- crnlib/crn_pixel_format.h | 3 + crnlib/crn_rect.h | 104 ++++++++++++++++++++++++++++++ crnlib/crn_texture_conversion.cpp | 14 ++-- crnlib/crn_texture_conversion.h | 4 +- crunch/crunch.cpp | 31 +++++++-- example2/example2.cpp | 9 ++- example3/example3.cpp | 5 ++ 9 files changed, 179 insertions(+), 16 deletions(-) diff --git a/crnlib/crn_core.cpp b/crnlib/crn_core.cpp index 665d3bb..7305aa8 100644 --- a/crnlib/crn_core.cpp +++ b/crnlib/crn_core.cpp @@ -3,5 +3,5 @@ #include "crn_core.h" #include "crn_winhdr.h" -char *g_copyright_str = "Copyright (c) 2010-2011 Tenacious Software LLC"; +char *g_copyright_str = "Copyright (c) 2010-2012 Tenacious Software LLC"; char *g_sig_str = "C8cfRlaorj0wLtnMSxrBJxTC85rho2L9hUZKHcBL"; diff --git a/crnlib/crn_dds_texture.cpp b/crnlib/crn_dds_texture.cpp index 2b92bf1..1e7a7e5 100644 --- a/crnlib/crn_dds_texture.cpp +++ b/crnlib/crn_dds_texture.cpp @@ -620,7 +620,17 @@ namespace crnlib uint pitch = desc.lPitch; if (!pitch) pitch = default_pitch; - else if ((pitch > default_pitch * 8) || (pitch & 3)) +#if 0 + else if (pitch & 3) + { + // MS's DDS docs say the pitch must be DWORD aligned - but this isn't always the case. + // ATI Compressonator writes images with non-DWORD aligned pitches, and the DDSWithoutD3DX sample from MS doesn't compute the proper DWORD aligned pitch when reading DDS + // files, so the docs must be wrong/outdated. + console::warning(L"DDS file's pitch is not divisible by 4 - trying to load anyway."); + } +#endif + // Check for obviously wacky source pitches (probably a corrupted/invalid file). + else if (pitch > default_pitch * 8) { set_last_error(L"Invalid pitch"); return false; @@ -946,6 +956,10 @@ namespace crnlib break; } } + + uint bits_per_pixel = pixel_format_helpers::get_bpp(m_format); + desc.lPitch = (((desc.dwWidth + 3) & ~3) * ((desc.dwHeight + 3) & ~3) * bits_per_pixel) >> 3; + desc.dwFlags |= DDSD_LINEARSIZE; } else { @@ -998,8 +1012,12 @@ namespace crnlib return false; } } - } + uint bits_per_pixel = desc.ddpfPixelFormat.dwRGBBitCount; + desc.lPitch = (desc.dwWidth * bits_per_pixel) >> 3; + desc.dwFlags |= DDSD_LINEARSIZE; + } + if (!c_crnlib_little_endian_platform) utils::endian_switch_dwords(reinterpret_cast(&desc), sizeof(desc) / sizeof(uint32)); @@ -1272,6 +1290,7 @@ namespace crnlib else { image_u8* p = crnlib_new(mip_width, mip_height); + p->set_comp_flags(m_comp_flags); m_faces[f][l]->assign(p, m_format); } } diff --git a/crnlib/crn_pixel_format.h b/crnlib/crn_pixel_format.h index c2f6457..e7e0ae6 100644 --- a/crnlib/crn_pixel_format.h +++ b/crnlib/crn_pixel_format.h @@ -35,6 +35,8 @@ namespace crnlib return (fmt == PIXEL_FMT_DXT1) || (fmt == PIXEL_FMT_DXT1A); } + // has_alpha() should probably be called "has_opacity()" - it indicates if the format encodes opacity + // because some swizzled DXT5 formats do not encode opacity. inline bool has_alpha(pixel_format fmt) { switch (fmt) @@ -48,6 +50,7 @@ namespace crnlib case PIXEL_FMT_A8R8G8B8: case PIXEL_FMT_A8: case PIXEL_FMT_A8L8: + case PIXEL_FMT_DXT5_AGBR: return true; default: break; } diff --git a/crnlib/crn_rect.h b/crnlib/crn_rect.h index 8482763..a7d4a0e 100644 --- a/crnlib/crn_rect.h +++ b/crnlib/crn_rect.h @@ -34,6 +34,24 @@ namespace crnlib m_corner[0] = point; m_corner[1].set(point[0] + 1, point[1] + 1); } + + inline bool operator== (const rect& r) const + { + return (m_corner[0] == r.m_corner[0]) && (m_corner[1] == r.m_corner[1]); + } + + inline bool operator< (const rect& r) const + { + for (uint i = 0; i < 2; i++) + { + if (m_corner[i] < r.m_corner[i]) + return true; + else if (!(m_corner[i] == r.m_corner[i])) + return false; + } + + return false; + } inline void clear() { @@ -70,10 +88,96 @@ namespace crnlib inline bool is_empty() const { return (m_corner[1][0] <= m_corner[0][0]) || (m_corner[1][1] <= m_corner[0][1]); } inline uint get_dimension(uint axis) const { return m_corner[1][axis] - m_corner[0][axis]; } + inline uint get_area() const { return get_dimension(0) * get_dimension(1); } inline const vec2I& operator[] (uint i) const { CRNLIB_ASSERT(i < 2); return m_corner[i]; } inline vec2I& operator[] (uint i) { CRNLIB_ASSERT(i < 2); return m_corner[i]; } + inline rect& translate(int x_ofs, int y_ofs) + { + m_corner[0][0] += x_ofs; + m_corner[0][1] += y_ofs; + m_corner[1][0] += x_ofs; + m_corner[1][1] += y_ofs; + return *this; + } + + inline rect& init_expand() + { + m_corner[0].set(INT_MAX); + m_corner[1].set(INT_MIN); + return *this; + } + + inline rect& expand(int x, int y) + { + m_corner[0][0] = math::minimum(m_corner[0][0], x); + m_corner[0][1] = math::minimum(m_corner[0][1], y); + m_corner[1][0] = math::maximum(m_corner[1][0], x + 1); + m_corner[1][1] = math::maximum(m_corner[1][1], y + 1); + return *this; + } + + inline rect& expand(const rect& r) + { + m_corner[0][0] = math::minimum(m_corner[0][0], r[0][0]); + m_corner[0][1] = math::minimum(m_corner[0][1], r[0][1]); + m_corner[1][0] = math::maximum(m_corner[1][0], r[1][0]); + m_corner[1][1] = math::maximum(m_corner[1][1], r[1][1]); + return *this; + } + + inline bool touches(const rect& r) const + { + for (uint i = 0; i < 2; i++) + { + if (r[1][i] <= m_corner[0][i]) + return false; + else if (r[0][i] >= m_corner[1][i]) + return false; + } + + return true; + } + + inline bool within(const rect& r) const + { + for (uint i = 0; i < 2; i++) + { + if (m_corner[0][i] < r[0][i]) + return false; + else if (m_corner[1][i] > r[1][i]) + return false; + } + + return true; + } + + inline bool intersect(const rect& r) + { + if (!touches(r)) + { + clear(); + return false; + } + + for (uint i = 0; i < 2; i++) + { + m_corner[0][i] = math::maximum(m_corner[0][i], r[0][i]); + m_corner[1][i] = math::minimum(m_corner[1][i], r[1][i]); + } + + return true; + } + + inline bool contains(int x, int y) const + { + return (x >= m_corner[0][0]) && (x < m_corner[1][0]) && + (y >= m_corner[0][1]) && (y < m_corner[1][1]); + } + + inline bool contains(const vec2I& p) const { return contains(p[0], p[1]); } + private: vec2I m_corner[2]; }; diff --git a/crnlib/crn_texture_conversion.cpp b/crnlib/crn_texture_conversion.cpp index 409566c..ef976f7 100644 --- a/crnlib/crn_texture_conversion.cpp +++ b/crnlib/crn_texture_conversion.cpp @@ -295,6 +295,9 @@ namespace crnlib static pixel_format choose_pixel_format(convert_params& params, const crn_comp_params &comp_params, const dds_texture& src_tex, texture_type tex_type) { + if (params.m_use_source_format) + return src_tex.get_format(); + const bool is_normal_map = (tex_type == cTextureTypeNormalMap); if (params.m_dst_file_type == texture_file_types::cFormatCRN) @@ -409,11 +412,12 @@ namespace crnlib m_pInput_texture->get_num_levels(), pixel_format_helpers::get_pixel_format_string(m_pInput_texture->get_format())); - console::debug(L" texture_type: %s", get_texture_type_desc(m_texture_type)); - console::debug(L" dst_filename: %s", m_dst_filename.get_ptr()); - console::debug(L"dst_file_type: %s", texture_file_types::get_extension(m_dst_file_type)); - console::debug(L" dst_format: %s", pixel_format_helpers::get_pixel_format_string(m_dst_format)); - console::debug(L" quick: %u", m_quick); + console::debug(L" texture_type: %s", get_texture_type_desc(m_texture_type)); + console::debug(L" dst_filename: %s", m_dst_filename.get_ptr()); + console::debug(L" dst_file_type: %s", texture_file_types::get_extension(m_dst_file_type)); + console::debug(L" dst_format: %s", pixel_format_helpers::get_pixel_format_string(m_dst_format)); + console::debug(L" quick: %u", m_quick); + console::debug(L" use_source_format: %u", m_use_source_format); } static bool write_compressed_texture( diff --git a/crnlib/crn_texture_conversion.h b/crnlib/crn_texture_conversion.h index 442321e..9957d21 100644 --- a/crnlib/crn_texture_conversion.h +++ b/crnlib/crn_texture_conversion.h @@ -60,7 +60,8 @@ namespace crnlib m_no_stats(false), m_lzma_stats(false), m_status(false), - m_canceled(false) + m_canceled(false), + m_use_source_format(false) { } @@ -96,6 +97,7 @@ namespace crnlib bool m_debugging; bool m_param_debugging; bool m_no_stats; + bool m_use_source_format; bool m_lzma_stats; mutable bool m_status; diff --git a/crunch/crunch.cpp b/crunch/crunch.cpp index 2aea6dc..fc4c793 100644 --- a/crunch/crunch.cpp +++ b/crunch/crunch.cpp @@ -65,6 +65,7 @@ public: static void print_usage() { + // ------------------------------------------------------------------------------- console::message(L"\nCommand line usage:"); console::printf(L"crunch [options] -file filename"); console::printf(L"-file filename - Required input filename, wildcards, multiple /file params OK."); @@ -104,7 +105,7 @@ public: console::printf(L"/mipstats - Print statistics for each mipmap, not just the top mip"); console::printf(L"/lzmastats - Print size of output file compressed with LZMA codec"); console::printf(L"/split - Write faces/mip levels to multiple separate output files"); - + console::message(L"\nImage rescaling (mutually exclusive options)"); console::printf(L"/rescale - Rescale image to specified resolution"); console::printf(L"/relscale - Rescale image to specified relative resolution"); @@ -125,9 +126,14 @@ public: console::printf(L"/ca # - Alpha endpoint palette size, 32-8192, default=3072"); console::printf(L"/sa # - Alpha selector palette size, 32-8192, default=3072"); + // ------------------------------------------------------------------------------- console::message(L"\nMipmap filtering options:"); console::printf(L"/mipMode [UseSourceOrGenerate,UseSource,Generate,None]"); console::printf(L" Default mipMode is UseSourceOrGenerate"); + console::printf(L" UseSourceOrGenerate: Use source mipmaps if possible, or create new mipmaps."); + console::printf(L" UseSource: Always use source mipmaps, if any (never generate new mipmaps)"); + console::printf(L" Generate: Always generate a new mipmap chain (ignore source mipmaps)"); + console::printf(L" None: Do not output any mipmaps"); console::printf(L"/mipFilter [box,tent,lanczos4,mitchell,kaiser], default=kaiser"); console::printf(L"/gamma # - Mipmap gamma correction value, default=2.2, use 1.0 for linear"); console::printf(L"/blurriness # - Scale filter kernel, >1=blur, <1=sharpen, .01-8, default=.9"); @@ -150,12 +156,16 @@ public: console::printf(L"/forceprimaryencoding - Only use DXT1 color4 and DXT5 alpha8 block encodings."); console::printf(L"/usetransparentindicesforblack - Try DXT1 transparent indices for dark pixels."); + console::message(L"\nOuptut pixel format options:"); + console::printf(L"/usesourceformat - Use input file's format for output format (when possible)."); console::message(L"\nAll supported texture formats (Note: .CRN only supports DXTn pixel formats):"); for (uint i = 0; i < pixel_format_helpers::get_num_formats(); i++) { pixel_format fmt = pixel_format_helpers::get_pixel_format_by_index(i); console::printf(L"/%s", pixel_format_helpers::get_pixel_format_string(fmt)); } + + console::printf(L"\nFor bugs, support, or feedback: richgel99@gmail.com"); } bool convert(const wchar_t* pCommand_line) @@ -221,6 +231,7 @@ public: { L"info" }, { L"forceprimaryencoding" }, { L"usetransparentindicesforblack" }, + { L"usesourceformat" }, { L"rescalemode", 1 }, { L"rescale", 2 }, @@ -475,10 +486,17 @@ private: if (!m_params.has_key(L"fileformat")) { - texture_file_types::format input_file_type = texture_file_types::determine_file_format(in_filename.get_ptr()); - if (input_file_type == texture_file_types::cFormatCRN) + if (m_params.has_key(L"split")) { - out_file_type = texture_file_types::cFormatDDS; + out_file_type = texture_file_types::cFormatTGA; + } + else + { + texture_file_types::format input_file_type = texture_file_types::determine_file_format(in_filename.get_ptr()); + if (input_file_type == texture_file_types::cFormatCRN) + { + out_file_type = texture_file_types::cFormatDDS; + } } } @@ -1106,6 +1124,7 @@ private: params.m_dst_file_type = out_file_type; params.m_lzma_stats = m_params.has_key(L"lzmastats"); params.m_write_mipmaps_to_multiple_files = m_params.has_key(L"split"); + params.m_use_source_format = m_params.has_key(L"usesourceformat"); if ((!m_params.get_value_as_bool(L"noprogress")) && (!m_params.get_value_as_bool(L"quiet"))) params.m_pProgress_func = progress_callback_func; @@ -1203,8 +1222,8 @@ static bool check_for_option(int argc, wchar_t *argv[], const wchar_t *pOption) static void print_title() { - console::printf(L"crunch: Advanced DXTn Texture Compressor"); - console::printf(L"Copyright (c) 2010-2011 Tenacious Software LLC"); + console::printf(L"crunch: Advanced DXTn Texture Compressor - http://code.google.com/p/crunch"); + console::printf(L"Copyright (c) 2010-2012 Rich Geldreich and Tenacious Software LLC"); console::printf(L"crnlib version v%u.%02u %s Built %s, %s", CRNLIB_VERSION / 100U, CRNLIB_VERSION % 100U, crnlib_is_x64() ? L"x64" : L"x86", U(__DATE__), U(__TIME__)); console::printf(L""); } diff --git a/example2/example2.cpp b/example2/example2.cpp index e4b4a61..c17baae 100644 --- a/example2/example2.cpp +++ b/example2/example2.cpp @@ -3,7 +3,8 @@ // This tool does NOT depend on the crnlib library at all. It only needs the low-level // decompression/transcoding functionality defined in inc/crn_decomp.h. // This is the basic functionality a game engine would need to employ at runtime to utilize -// .CRN textures. +// .CRN textures (excluding writing the output DDS file - instead you would provide the DXTn +// bits directly to OpenGL/D3D). // See Copyright Notice and license at the end of inc/crnlib.h #include #include @@ -170,6 +171,7 @@ int main(int argc, char *argv[]) dds_desc.ddpfPixelFormat.dwFourCC = crnd::crnd_crn_format_to_fourcc(fundamental_fmt); if (fundamental_fmt != tex_info.m_format) { + // It's a funky swizzled DXTn format - write its FOURCC to dwRGBBitCount. dds_desc.ddpfPixelFormat.dwRGBBitCount = crnd::crnd_crn_format_to_fourcc(tex_info.m_format); } @@ -186,6 +188,11 @@ int main(int argc, char *argv[]) DDSCAPS2_CUBEMAP_NEGATIVEY | DDSCAPS2_CUBEMAP_POSITIVEZ | DDSCAPS2_CUBEMAP_NEGATIVEZ; } + // Set pitch/linearsize field (some DDS readers require this field to be non-zero). + int bits_per_pixel = crnd::crnd_get_crn_format_bits_per_texel(tex_info.m_format); + dds_desc.lPitch = (((dds_desc.dwWidth + 3) & ~3) * ((dds_desc.dwHeight + 3) & ~3) * bits_per_pixel) >> 3; + dds_desc.dwFlags |= DDSD_LINEARSIZE; + // Write the DDS header to the output file. fwrite(&dds_desc, sizeof(dds_desc), 1, pDDS_file); diff --git a/example3/example3.cpp b/example3/example3.cpp index e8800ce..42d5570 100644 --- a/example3/example3.cpp +++ b/example3/example3.cpp @@ -260,6 +260,11 @@ int main(int argc, char *argv[]) dds_desc.ddpfPixelFormat.dwFourCC = crn_get_format_fourcc(fmt); dds_desc.ddsCaps.dwCaps = DDSCAPS_TEXTURE; + // Set pitch/linearsize field (some DDS readers require this field to be non-zero). + uint bits_per_pixel = crn_get_format_bits_per_texel(fmt); + dds_desc.lPitch = (((dds_desc.dwWidth + 3) & ~3) * ((dds_desc.dwHeight + 3) & ~3) * bits_per_pixel) >> 3; + dds_desc.dwFlags |= DDSD_LINEARSIZE; + // Write the DDS header to the output file. fwrite(&dds_desc, sizeof(dds_desc), 1, pDDS_file);