| // basisu_dds_export.cpp |
| // Copyright (C) 2019-2025 Binomial LLC. All Rights Reserved. |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| // See basisu_dds_export.h for an overview. |
| #include "basisu_dds_export.h" |
| |
| #include "basisu_comp.h" |
| #include "basisu_bc7e_scalar.h" |
| #include "../transcoder/basisu_transcoder_internal.h" |
| #include "../transcoder/basisu_transcoder_uastc.h" |
| |
| namespace basisu |
| { |
| // --- DXGI format codes (only the ones we emit). --- |
| enum |
| { |
| DXGI_FORMAT_R8G8B8A8_UNORM = 28, |
| DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29, |
| DXGI_FORMAT_R8G8_UNORM = 49, |
| DXGI_FORMAT_R8_UNORM = 61, |
| DXGI_FORMAT_BC1_UNORM = 71, |
| DXGI_FORMAT_BC1_UNORM_SRGB = 72, |
| DXGI_FORMAT_BC2_UNORM = 74, |
| DXGI_FORMAT_BC2_UNORM_SRGB = 75, |
| DXGI_FORMAT_BC3_UNORM = 77, |
| DXGI_FORMAT_BC3_UNORM_SRGB = 78, |
| DXGI_FORMAT_BC4_UNORM = 80, |
| DXGI_FORMAT_BC5_UNORM = 83, |
| DXGI_FORMAT_B5G6R5_UNORM = 85, |
| DXGI_FORMAT_B5G5R5A1_UNORM = 86, |
| DXGI_FORMAT_B8G8R8A8_UNORM = 87, |
| DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91, |
| DXGI_FORMAT_BC7_UNORM = 98, |
| DXGI_FORMAT_BC7_UNORM_SRGB = 99, |
| DXGI_FORMAT_B4G4R4A4_UNORM = 115 |
| }; |
| |
| // --- DDS header flag/cap constants. --- |
| enum |
| { |
| DDSD_CAPS = 0x1, DDSD_HEIGHT = 0x2, DDSD_WIDTH = 0x4, DDSD_PITCH = 0x8, |
| DDSD_PIXELFORMAT = 0x1000, DDSD_MIPMAPCOUNT = 0x20000, DDSD_LINEARSIZE = 0x80000, |
| |
| DDPF_FOURCC = 0x4, |
| |
| DDSCAPS_COMPLEX = 0x8, DDSCAPS_TEXTURE = 0x1000, DDSCAPS_MIPMAP = 0x400000, |
| |
| DDSCAPS2_CUBEMAP = 0x200, DDSCAPS2_CUBEMAP_ALLFACES = 0xFC00, |
| |
| DDS_MAGIC = 0x20534444, // "DDS " |
| DDS_DX10_FOURCC = 0x30315844, // "DX10" |
| |
| DDS_DIMENSION_TEXTURE2D = 3, |
| DDS_RESOURCE_MISC_TEXTURECUBE = 0x4 |
| }; |
| |
| // Per-format static info. |
| struct dds_format_info |
| { |
| const char* m_pToken; |
| bool m_block_compressed; |
| uint32_t m_bytes; // bytes per 4x4 block (compressed) or bytes per pixel (uncompressed) |
| uint32_t m_dxgi_unorm; |
| uint32_t m_dxgi_srgb; // 0 if no sRGB variant exists |
| }; |
| |
| static const dds_format_info g_dds_format_info[cDDSFmtTotal] = |
| { |
| // token block bytes unorm srgb |
| { "bc1", true, 8, DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM_SRGB }, |
| { "bc2", true, 16, DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC2_UNORM_SRGB }, |
| { "bc3", true, 16, DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_BC3_UNORM_SRGB }, |
| { "bc4", true, 8, DXGI_FORMAT_BC4_UNORM, 0 }, |
| { "bc5", true, 16, DXGI_FORMAT_BC5_UNORM, 0 }, |
| { "bc7", true, 16, DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM_SRGB }, |
| { "a8r8g8b8", false, 4, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM_SRGB }, |
| { "a8b8g8r8", false, 4, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB }, |
| { "r8", false, 1, DXGI_FORMAT_R8_UNORM, 0 }, |
| { "r8g8", false, 2, DXGI_FORMAT_R8G8_UNORM, 0 }, |
| { "r5g6b5", false, 2, DXGI_FORMAT_B5G6R5_UNORM, 0 }, |
| { "a1r5g5b5", false, 2, DXGI_FORMAT_B5G5R5A1_UNORM, 0 }, |
| { "a4r4g4b4", false, 2, DXGI_FORMAT_B4G4R4A4_UNORM, 0 } |
| }; |
| |
| bool parse_dds_output_format(const char* pToken, dds_output_format& fmt) |
| { |
| fmt = cDDSFmtInvalid; |
| if (!pToken) |
| return false; |
| |
| for (int i = 0; i < (int)cDDSFmtTotal; i++) |
| { |
| if (strcasecmp(pToken, g_dds_format_info[i].m_pToken) == 0) |
| { |
| fmt = (dds_output_format)i; |
| return true; |
| } |
| } |
| |
| // Tolerate the (geometrically impossible) "a1r5g6b5" spelling as an alias for a1r5g5b5. |
| if (strcasecmp(pToken, "a1r5g6b5") == 0) |
| { |
| fmt = cDDSFmtA1R5G5B5; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| const char* get_dds_output_format_string(dds_output_format fmt) |
| { |
| if ((fmt < 0) || (fmt >= cDDSFmtTotal)) |
| return "?"; |
| return g_dds_format_info[fmt].m_pToken; |
| } |
| |
| bool dds_output_format_has_srgb_variant(dds_output_format fmt) |
| { |
| if ((fmt < 0) || (fmt >= cDDSFmtTotal)) |
| return false; |
| return g_dds_format_info[fmt].m_dxgi_srgb != 0; |
| } |
| |
| // --- Little-endian append helpers. --- |
| static inline void append_u16(uint8_vec& v, uint32_t x) |
| { |
| v.push_back((uint8_t)(x & 0xFF)); |
| v.push_back((uint8_t)((x >> 8) & 0xFF)); |
| } |
| static inline void append_u32(uint8_vec& v, uint32_t x) |
| { |
| v.push_back((uint8_t)(x & 0xFF)); |
| v.push_back((uint8_t)((x >> 8) & 0xFF)); |
| v.push_back((uint8_t)((x >> 16) & 0xFF)); |
| v.push_back((uint8_t)((x >> 24) & 0xFF)); |
| } |
| |
| // Packs a single RGBA8 texel into the uncompressed output bytes for fmt (truncating 8->N bits, which is |
| // the exact inverse of the reader's bit-replication expansion). Channel byte orders match the DXGI formats. |
| static void pack_uncompressed_pixel(uint8_vec& out, const color_rgba& c, dds_output_format fmt) |
| { |
| switch (fmt) |
| { |
| case cDDSFmtA8R8G8B8: // DXGI B8G8R8A8 -> memory order B,G,R,A |
| out.push_back(c.b); out.push_back(c.g); out.push_back(c.r); out.push_back(c.a); |
| break; |
| case cDDSFmtA8B8G8R8: // DXGI R8G8B8A8 -> memory order R,G,B,A |
| out.push_back(c.r); out.push_back(c.g); out.push_back(c.b); out.push_back(c.a); |
| break; |
| case cDDSFmtR8: // DXGI R8 -> just R |
| out.push_back(c.r); |
| break; |
| case cDDSFmtR8G8: // DXGI R8G8 -> source R into R, source G into G (matches BC5; swizzle input for other mappings) |
| out.push_back(c.r); out.push_back(c.g); |
| break; |
| case cDDSFmtR5G6B5: |
| append_u16(out, (uint32_t)(((c.r >> 3) << 11) | ((c.g >> 2) << 5) | (c.b >> 3))); |
| break; |
| case cDDSFmtA1R5G5B5: |
| append_u16(out, (uint32_t)((c.a >= 128 ? 0x8000 : 0) | ((c.r >> 3) << 10) | ((c.g >> 3) << 5) | (c.b >> 3))); |
| break; |
| case cDDSFmtA4R4G4B4: |
| append_u16(out, (uint32_t)(((c.a >> 4) << 12) | ((c.r >> 4) << 8) | ((c.g >> 4) << 4) | (c.b >> 4))); |
| break; |
| default: |
| assert(0); |
| break; |
| } |
| } |
| |
| // Prebuilt BC7 packing context (built once per build_dds, shared read-only across slices). |
| struct bc7_pack_context |
| { |
| dds_bc7_encoder m_encoder; |
| uint32_t m_bc7f_flags; // bc7f packer flags (when m_encoder == bc7f) |
| bc7e_scalar::bc7e_compress_block_params m_bc7e_params; // initialized only when m_encoder == bc7e_scalar |
| }; |
| |
| // Packs one prepared slice image into the bytes for fmt, using logical dims (orig_width/orig_height). |
| // For block formats this iterates whole 4x4 blocks (the slice image is already block-padded); for |
| // uncompressed it emits tight rows of exactly orig_width*orig_height pixels. |
| static void pack_slice(uint8_vec& out, const image& img, uint32_t orig_width, uint32_t orig_height, dds_output_format fmt, const bc7_pack_context& bc7ctx) |
| { |
| const dds_format_info& info = g_dds_format_info[fmt]; |
| |
| if (info.m_block_compressed) |
| { |
| const uint32_t blocks_x = (orig_width + 3) / 4; |
| const uint32_t blocks_y = (orig_height + 3) / 4; |
| |
| for (uint32_t by = 0; by < blocks_y; by++) |
| { |
| for (uint32_t bx = 0; bx < blocks_x; bx++) |
| { |
| color_rgba blk[16]; |
| img.extract_block_clamped(blk, bx * 4, by * 4, 4, 4); |
| |
| uint8_t dst[16]; |
| switch (fmt) |
| { |
| case cDDSFmtBC1: |
| basist::encode_bc1(dst, (const uint8_t*)blk, basist::cEncodeBC1HighQuality); |
| break; |
| case cDDSFmtBC2: |
| // 8 bytes explicit 4-bit alpha (one LE 16-bit word per row, texel x at nibble x), then a |
| // BC1 color block (encode_bc1 emits 4-color blocks, which BC2's always-4-color decode needs). |
| for (uint32_t ry = 0; ry < 4; ry++) |
| { |
| uint32_t row = 0; |
| for (uint32_t rx = 0; rx < 4; rx++) |
| row |= ((uint32_t)(blk[ry * 4 + rx].a >> 4)) << (rx * 4); |
| dst[ry * 2] = (uint8_t)(row & 0xFF); |
| dst[ry * 2 + 1] = (uint8_t)(row >> 8); |
| } |
| basist::encode_bc1(dst + 8, (const uint8_t*)blk, basist::cEncodeBC1HighQuality); |
| break; |
| case cDDSFmtBC3: |
| basist::encode_bc4(dst, &blk[0].a, sizeof(color_rgba)); |
| basist::encode_bc1(dst + 8, (const uint8_t*)blk, basist::cEncodeBC1HighQuality); |
| break; |
| case cDDSFmtBC4: |
| basist::encode_bc4(dst, &blk[0].r, sizeof(color_rgba)); |
| break; |
| case cDDSFmtBC5: |
| basist::encode_bc4(dst, &blk[0].r, sizeof(color_rgba)); |
| basist::encode_bc4(dst + 8, &blk[0].g, sizeof(color_rgba)); |
| break; |
| case cDDSFmtBC7: |
| // basist::color_rgba and basisu::color_rgba have identical layout (r,g,b,a uint8_t). |
| if (bc7ctx.m_encoder == cDDSBC7Encoder_BC7E_Scalar) |
| { |
| uint64_t blk64[2]; |
| bc7e_scalar::bc7e_compress_blocks(1, blk64, reinterpret_cast<const uint32_t*>(blk), &bc7ctx.m_bc7e_params, nullptr); |
| memcpy(dst, blk64, 16); |
| } |
| else |
| { |
| basist::bc7f::fast_pack_bc7_auto_rgba(dst, reinterpret_cast<const basist::color_rgba*>(blk), bc7ctx.m_bc7f_flags); |
| } |
| break; |
| default: |
| assert(0); |
| break; |
| } |
| |
| out.append(dst, info.m_bytes); |
| } |
| } |
| } |
| else |
| { |
| for (uint32_t y = 0; y < orig_height; y++) |
| for (uint32_t x = 0; x < orig_width; x++) |
| pack_uncompressed_pixel(out, img(x, y), fmt); |
| } |
| } |
| |
| // Builds the DDS magic + DDS_HEADER + DDS_HEADER_DXT10 (148 bytes total). |
| static void build_dx10_header(uint8_vec& out, uint32_t width, uint32_t height, uint32_t mip_count, |
| uint32_t array_size, bool is_cubemap, uint32_t dxgi_format, const dds_format_info& info) |
| { |
| // dwPitchOrLinearSize (informational; readers generally recompute). |
| uint32_t pitch_or_linear_size; |
| uint32_t flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; |
| if (info.m_block_compressed) |
| { |
| pitch_or_linear_size = ((width + 3) / 4) * ((height + 3) / 4) * info.m_bytes; |
| flags |= DDSD_LINEARSIZE; |
| } |
| else |
| { |
| pitch_or_linear_size = width * info.m_bytes; |
| flags |= DDSD_PITCH; |
| } |
| if (mip_count > 1) |
| flags |= DDSD_MIPMAPCOUNT; |
| |
| uint32_t caps = DDSCAPS_TEXTURE; |
| if ((mip_count > 1) || is_cubemap || (array_size > 1)) |
| caps |= DDSCAPS_COMPLEX; |
| if (mip_count > 1) |
| caps |= DDSCAPS_MIPMAP; |
| |
| const uint32_t caps2 = is_cubemap ? (DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALLFACES) : 0; |
| |
| append_u32(out, DDS_MAGIC); |
| |
| // DDS_HEADER (124 bytes). |
| append_u32(out, 124); // dwSize |
| append_u32(out, flags); // dwFlags |
| append_u32(out, height); // dwHeight |
| append_u32(out, width); // dwWidth |
| append_u32(out, pitch_or_linear_size); |
| append_u32(out, 0); // dwDepth |
| append_u32(out, mip_count); // dwMipMapCount |
| for (uint32_t i = 0; i < 11; i++) // dwReserved1[11] |
| append_u32(out, 0); |
| |
| // DDS_PIXELFORMAT (32 bytes) - DX10 indirection. |
| append_u32(out, 32); // ddspf.dwSize |
| append_u32(out, DDPF_FOURCC); // ddspf.dwFlags |
| append_u32(out, DDS_DX10_FOURCC); // ddspf.dwFourCC = "DX10" |
| append_u32(out, 0); // dwRGBBitCount |
| append_u32(out, 0); // dwRBitMask |
| append_u32(out, 0); // dwGBitMask |
| append_u32(out, 0); // dwBBitMask |
| append_u32(out, 0); // dwABitMask |
| |
| append_u32(out, caps); // dwCaps |
| append_u32(out, caps2); // dwCaps2 |
| append_u32(out, 0); // dwCaps3 |
| append_u32(out, 0); // dwCaps4 |
| append_u32(out, 0); // dwReserved2 |
| |
| // DDS_HEADER_DXT10 (20 bytes). |
| append_u32(out, dxgi_format); // dxgiFormat |
| append_u32(out, DDS_DIMENSION_TEXTURE2D); |
| append_u32(out, is_cubemap ? DDS_RESOURCE_MISC_TEXTURECUBE : 0); // miscFlag |
| append_u32(out, array_size); // arraySize (number of array elements; cubes for a cubemap) |
| append_u32(out, 0); // miscFlags2 |
| } |
| |
| bool build_dds(uint8_vec& dds_data, const basis_compressor& comp, const dds_export_params& params, |
| std::string& error_msg, |
| uint32_t* pOut_width, uint32_t* pOut_height, |
| uint32_t* pOut_levels, uint32_t* pOut_layers, uint32_t* pOut_faces) |
| { |
| error_msg.clear(); |
| dds_data.resize(0); |
| |
| const dds_output_format fmt = params.m_format; |
| |
| if ((fmt < 0) || (fmt >= cDDSFmtTotal)) |
| { |
| error_msg = "invalid output format"; |
| return false; |
| } |
| |
| const dds_format_info& info = g_dds_format_info[fmt]; |
| |
| const basisu::vector<image>& slices = comp.get_slice_images(); |
| const basisu_backend_slice_desc_vec& descs = comp.get_slice_descs(); |
| |
| if (!slices.size() || (slices.size() != descs.size())) |
| { |
| error_msg = "no prepared slices (was process_source_images() run successfully?)"; |
| return false; |
| } |
| |
| const bool is_cubemap = (comp.get_params().m_tex_type == basist::cBASISTexTypeCubemapArray); |
| |
| // Determine base dims, layer count, mip level count, and face count - mirrors create_ktx2_file(). |
| uint32_t base_width = 0, base_height = 0, total_layers = 0, total_levels = 0, total_faces = 1; |
| for (uint32_t i = 0; i < descs.size(); i++) |
| { |
| if ((descs[i].m_mip_index == 0) && (!base_width)) |
| { |
| base_width = descs[i].m_orig_width; |
| base_height = descs[i].m_orig_height; |
| } |
| |
| total_layers = maximum<uint32_t>(total_layers, descs[i].m_source_file_index + 1); |
| |
| if (!descs[i].m_source_file_index) |
| total_levels = maximum<uint32_t>(total_levels, descs[i].m_mip_index + 1); |
| } |
| |
| if (is_cubemap) |
| { |
| if ((total_layers % 6) != 0) |
| { |
| error_msg = "cubemap source image count is not a multiple of 6"; |
| return false; |
| } |
| total_layers /= 6; |
| total_faces = 6; |
| } |
| |
| if (!base_width || !base_height || !total_layers || !total_levels) |
| { |
| error_msg = "could not determine texture dimensions from the prepared slices"; |
| return false; |
| } |
| |
| // Build a (layer, face, level) -> slice index map using the same decomposition as create_ktx2_file(). |
| const uint32_t total_subresources = total_layers * total_faces * total_levels; |
| basisu::vector<int> slice_map(total_subresources); |
| for (uint32_t i = 0; i < total_subresources; i++) |
| slice_map[i] = -1; |
| |
| for (uint32_t i = 0; i < descs.size(); i++) |
| { |
| // Note: descs[i].m_alpha just flags that the slice contains alpha; in the XUBC7 (m_uastc) path each |
| // slice holds full RGBA, so it is NOT a separate alpha-only slice (that only happens for ETC1S). |
| |
| const uint32_t level_index = descs[i].m_mip_index; |
| uint32_t layer_index = descs[i].m_source_file_index; |
| uint32_t face_index = 0; |
| if (is_cubemap) |
| { |
| face_index = layer_index % 6; |
| layer_index /= 6; |
| } |
| |
| if ((layer_index >= total_layers) || (face_index >= total_faces) || (level_index >= total_levels)) |
| { |
| error_msg = "slice descriptor out of range (internal error)"; |
| return false; |
| } |
| |
| const uint32_t map_index = (layer_index * total_faces + face_index) * total_levels + level_index; |
| |
| // Two slices mapping to the same subresource would indicate RGB/alpha slice splitting (ETC1S), which |
| // we don't expect here since we force the XUBC7 path. |
| if (slice_map[map_index] >= 0) |
| { |
| error_msg = "multiple slices map to the same (layer, face, level) - RGB/alpha slice splitting is not supported"; |
| return false; |
| } |
| |
| slice_map[map_index] = (int)i; |
| } |
| |
| for (uint32_t i = 0; i < total_subresources; i++) |
| { |
| if (slice_map[i] < 0) |
| { |
| error_msg = "missing slice for a (layer, face, level) - source images are not all the same size / mip count"; |
| return false; |
| } |
| } |
| |
| // Select the DXGI variant (UNORM vs sRGB). |
| const bool want_srgb = comp.get_params().m_ktx2_and_basis_srgb_transfer_function; |
| const uint32_t dxgi_format = (want_srgb && info.m_dxgi_srgb) ? info.m_dxgi_srgb : info.m_dxgi_unorm; |
| if (want_srgb && !info.m_dxgi_srgb && comp.get_params().m_status_output) |
| printf("Note: format %s has no sRGB DXGI variant; writing UNORM (texel data is unchanged regardless).\n", info.m_pToken); |
| |
| // Build the BC7 packing context once (only relevant for the bc7 format). |
| bc7_pack_context bc7ctx; |
| memset(&bc7ctx, 0, sizeof(bc7ctx)); |
| bc7ctx.m_encoder = params.m_bc7_encoder; |
| switch (clamp<int>(params.m_bc7f_level, cDDSBC7FLevel_Analytical, cDDSBC7FLevel_NonAnalytical)) |
| { |
| case cDDSBC7FLevel_Analytical: bc7ctx.m_bc7f_flags = basist::bc7f::cPackBC7FlagDefault; break; |
| case cDDSBC7FLevel_NonAnalytical: bc7ctx.m_bc7f_flags = basist::bc7f::cPackBC7FlagDefaultNonAnalytical; break; |
| default: bc7ctx.m_bc7f_flags = basist::bc7f::cPackBC7FlagDefaultPartiallyAnalytical; break; |
| } |
| if ((fmt == cDDSFmtBC7) && (params.m_bc7_encoder == cDDSBC7Encoder_BC7E_Scalar)) |
| { |
| bc7e_scalar::bc7e_compress_block_init(); |
| |
| typedef void (*bc7e_init_func)(bc7e_scalar::bc7e_compress_block_params*, bool); |
| static const bc7e_init_func s_bc7e_level_init[7] = |
| { |
| &bc7e_scalar::bc7e_compress_block_params_init_ultrafast, // 0 |
| &bc7e_scalar::bc7e_compress_block_params_init_veryfast, // 1 |
| &bc7e_scalar::bc7e_compress_block_params_init_fast, // 2 |
| &bc7e_scalar::bc7e_compress_block_params_init_basic, // 3 |
| &bc7e_scalar::bc7e_compress_block_params_init_slow, // 4 |
| &bc7e_scalar::bc7e_compress_block_params_init_veryslow, // 5 |
| &bc7e_scalar::bc7e_compress_block_params_init_slowest, // 6 |
| }; |
| const int lvl = clamp<int>(params.m_bc7e_scalar_level, 0, 6); |
| |
| // Mirrors the XUBC7 path: for perceptual (sRGB) sources run bc7e in its built-in perceptual error |
| // mode (it ignores m_weights then); for linear sources run linear and hand it the same RGBA channel |
| // weights XUASTC LDR / XUBC7 use. |
| const bool perceptual = comp.get_params().m_perceptual; |
| s_bc7e_level_init[lvl](&bc7ctx.m_bc7e_params, perceptual); |
| |
| if (!perceptual) |
| { |
| for (uint32_t i = 0; i < 4; i++) |
| bc7ctx.m_bc7e_params.m_weights[i] = comp.get_params().m_xuastc_ldr_channel_weights[i]; |
| } |
| } |
| |
| // Optional debug logging (gated by the compressor's m_debug param, same flag -debug/-verbose set). |
| const bool debug = comp.get_params().m_debug; |
| |
| if (debug) |
| { |
| const bool wrote_srgb_dbg = want_srgb && (info.m_dxgi_srgb != 0); |
| debug_printf("DDS export: format \"%s\" (%s), DXGI=%u, %ux%u, %u level(s), %u layer(s), %u face(s) (%s), %u subresource(s)\n", |
| info.m_pToken, wrote_srgb_dbg ? "sRGB" : "UNORM", dxgi_format, base_width, base_height, |
| total_levels, total_layers, total_faces, is_cubemap ? "cubemap" : "2D", total_subresources); |
| debug_printf("DDS export: %s, %u byte(s) per %s\n", |
| info.m_block_compressed ? "block-compressed" : "uncompressed", info.m_bytes, |
| info.m_block_compressed ? "block" : "pixel"); |
| |
| if (fmt == cDDSFmtBC7) |
| { |
| // Print the FULL BC7 configuration (both encoders' settings) regardless of which encoder |
| // is active, so the actual values used can be verified at a glance. The "encoder=" line |
| // states which one is in effect; the line for the other encoder is informational. |
| const bool using_bc7e = (params.m_bc7_encoder == cDDSBC7Encoder_BC7E_Scalar); |
| |
| const int bc7f_lvl = clamp<int>(params.m_bc7f_level, cDDSBC7FLevel_Analytical, cDDSBC7FLevel_NonAnalytical); |
| const char* pBc7fLvl = (bc7f_lvl == cDDSBC7FLevel_Analytical) ? "analytical" : |
| (bc7f_lvl == cDDSBC7FLevel_NonAnalytical) ? "non-analytical" : "partially-analytical"; |
| |
| const int bc7e_lvl = clamp<int>(params.m_bc7e_scalar_level, 0, 6); |
| const bool perceptual = comp.get_params().m_perceptual; |
| const uint32_t* pW = comp.get_params().m_xuastc_ldr_channel_weights; |
| |
| debug_printf("DDS export: BC7 encoder=%s\n", using_bc7e ? "bc7e_scalar" : "bc7f"); |
| debug_printf("DDS export: bc7f level=%d (%s), pack flags=0x%X%s\n", |
| bc7f_lvl, pBc7fLvl, bc7ctx.m_bc7f_flags, using_bc7e ? " (inactive)" : " (active)"); |
| debug_printf("DDS export: bc7e_scalar level=%d, perceptual=%u, weights=[%u %u %u %u]%s%s\n", |
| bc7e_lvl, (uint32_t)perceptual, pW[0], pW[1], pW[2], pW[3], |
| perceptual ? " (weights ignored in perceptual mode)" : "", |
| using_bc7e ? " (active)" : " (inactive)"); |
| } |
| } |
| |
| // Assemble the file into the caller's buffer: header then subresources in DDS order (layer, face, mip). |
| build_dx10_header(dds_data, base_width, base_height, total_levels, total_layers, is_cubemap, dxgi_format, info); |
| |
| if (comp.get_params().m_status_output) |
| printf("Writing DDS (format \"%s\", %ux%u): %u subresource(s) [%u level(s), %u layer(s), %u face(s)]\n", |
| info.m_pToken, base_width, base_height, total_subresources, total_levels, total_layers, total_faces); |
| |
| for (uint32_t layer = 0; layer < total_layers; layer++) |
| { |
| for (uint32_t face = 0; face < total_faces; face++) |
| { |
| for (uint32_t level = 0; level < total_levels; level++) |
| { |
| const uint32_t map_index = (layer * total_faces + face) * total_levels + level; |
| const int slice_index = slice_map[map_index]; |
| |
| const basisu_backend_slice_desc& desc = descs[slice_index]; |
| |
| if (comp.get_params().m_status_output) |
| printf("DDS: packing subresource %u/%u (layer %u, face %u, level %u): %ux%u\n", |
| map_index + 1, total_subresources, layer, face, level, desc.m_orig_width, desc.m_orig_height); |
| |
| const size_t before_size = dds_data.size(); |
| pack_slice(dds_data, slices[slice_index], desc.m_orig_width, desc.m_orig_height, fmt, bc7ctx); |
| |
| if (debug) |
| debug_printf(" subresource [layer %u, face %u, level %u]: %ux%u -> %u byte(s)\n", |
| layer, face, level, desc.m_orig_width, desc.m_orig_height, (uint32_t)(dds_data.size() - before_size)); |
| } |
| } |
| } |
| |
| if (pOut_width) *pOut_width = base_width; |
| if (pOut_height) *pOut_height = base_height; |
| if (pOut_levels) *pOut_levels = total_levels; |
| if (pOut_layers) *pOut_layers = total_layers; |
| if (pOut_faces) *pOut_faces = total_faces; |
| |
| return true; |
| } |
| |
| } // namespace basisu |