| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include <expected> | ||
| 2 | #include <spanstream> | ||
| 3 | #include <algorithm> | ||
| 4 | |||
| 5 | #ifdef _MSC_VER | ||
| 6 | #define NOMINMAX | ||
| 7 | #endif | ||
| 8 | |||
| 9 | #include <spdlog/spdlog.h> | ||
| 10 | #include <cmp_compressonatorlib/compressonator.h> | ||
| 11 | #include <cmp_compressonatorlib/common.h> | ||
| 12 | #include <cmp_framework/common/cmp_boxfilter.h> | ||
| 13 | #include <_plugins/cimage/dds/dds_file.h> | ||
| 14 | #include <_plugins/cimage/dds/dds_helpers.h> | ||
| 15 | |||
| 16 | #include <blp/blp.hpp> | ||
| 17 | #include "assets_mpq_importer/blp.hpp" | ||
| 18 | #include "utils_blp.hpp" | ||
| 19 | |||
| 20 | namespace assmpq::blp { | ||
| 21 | |||
| 22 | // NOLINTBEGIN(clang-diagnostic-missing-designated-field-initializers, cppcoreguidelines-avoid-non-const-global-variables, cppcoreguidelines-pro-type-reinterpret-cast) | ||
| 23 | |||
| 24 | CMIPS g_CMIPS; | ||
| 25 | |||
| 26 | struct MipSetDeleter { | ||
| 27 | 14 | void operator()(CMP_MipSet* mipset_ptr) const { | |
| 28 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | if (mipset_ptr != nullptr) { |
| 29 | 14 | CMP_FreeMipSet(mipset_ptr); | |
| 30 |
1/2✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
|
14 | delete mipset_ptr; // NOLINT(cppcoreguidelines-owning-memory) |
| 31 | } | ||
| 32 | 14 | } | |
| 33 | }; | ||
| 34 | |||
| 35 | using MipSetPtr = std::unique_ptr<CMP_MipSet, MipSetDeleter>; | ||
| 36 | |||
| 37 | 7 | static auto get_dxgi_format(const MipSet& mipSet)-> DXGI_FORMAT | |
| 38 | { | ||
| 39 |
4/6✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
|
7 | switch (mipSet.m_format) { |
| 40 | 1 | case CMP_FORMAT_BC1: | |
| 41 | 1 | return DXGI_FORMAT_BC1_UNORM; | |
| 42 | 5 | case CMP_FORMAT_BC3: | |
| 43 | 5 | return DXGI_FORMAT_BC3_UNORM; | |
| 44 | 1 | case CMP_FORMAT_BC7: | |
| 45 | 1 | return DXGI_FORMAT_BC7_UNORM; | |
| 46 | ✗ | default: | |
| 47 | ✗ | return DXGI_FORMAT_UNKNOWN; | |
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | 7 | auto persist_dds_dx10(const MipSet& mipSet)-> std::vector<char> | |
| 52 | { | ||
| 53 | 7 | DDS_FILE_HEADER ddsd2 = {}; | |
| 54 | 7 | ddsd2.size = sizeof(DDS_FILE_HEADER); | |
| 55 | 7 | ddsd2.flags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_LINEARSIZE; | |
| 56 | 7 | ddsd2.height = static_cast<uint32_t>(mipSet.m_nHeight); | |
| 57 | 7 | ddsd2.width = static_cast<uint32_t>(mipSet.m_nWidth); | |
| 58 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | ddsd2.pitchOrLinearSize = g_CMIPS.GetMipLevel(&mipSet, 0)->m_dwLinearSize; |
| 59 | 7 | ddsd2.mipMapCount = static_cast<uint32_t>(mipSet.m_nMipLevels); | |
| 60 | 7 | ddsd2.ddspf.size = sizeof(DDPIXELFORMAT); | |
| 61 | 7 | ddsd2.ddspf.flags = DDPF_FOURCC; | |
| 62 | 7 | ddsd2.ddspf.fourCC = CMP_MAKEFOURCC('D', 'X', '1', '0'); | |
| 63 | 7 | ddsd2.caps = DDSCAPS_TEXTURE; | |
| 64 | |||
| 65 |
2/2✓ Branch 2 taken 5 times.
✓ Branch 3 taken 2 times.
|
7 | if (mipSet.m_nMipLevels > 1) { |
| 66 | 5 | ddsd2.flags |= DDSD_MIPMAPCOUNT; | |
| 67 | 5 | ddsd2.caps |= DDSCAPS_MIPMAP; | |
| 68 | } | ||
| 69 | |||
| 70 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | std::ostringstream output; |
| 71 | |||
| 72 | // Write the header | ||
| 73 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | output.write( reinterpret_cast<const char*>(&DDS_HEADER), static_cast<std::streamsize>(sizeof(DDS_HEADER))); |
| 74 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | output.write( reinterpret_cast<const char*>(&ddsd2), static_cast<std::streamsize>(sizeof(ddsd2))); |
| 75 | |||
| 76 | 7 | DDS_FILE_HEADER_DXT10 HeaderDDS10 = { | |
| 77 | 7 | .dxgiFormat = get_dxgi_format(mipSet), | |
| 78 | .resourceDimension = 3, // D3D10_RESOURCE_DIMENSION_TEXTURE2D | ||
| 79 | .miscFlag = 0, | ||
| 80 | .arraySize = 1, | ||
| 81 | .miscFlags2 = 0 | ||
| 82 | 7 | }; | |
| 83 | |||
| 84 | // Write DDS10 header | ||
| 85 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | output.write( reinterpret_cast<const char*>(&HeaderDDS10), static_cast<std::streamsize>(sizeof(HeaderDDS10))); |
| 86 | |||
| 87 | // Write the data | ||
| 88 |
2/2✓ Branch 2 taken 32 times.
✓ Branch 3 taken 7 times.
|
39 | for (int nMipLevel = 0; nMipLevel < mipSet.m_nMipLevels; nMipLevel++) { |
| 89 |
1/2✓ Branch 1 taken 32 times.
✗ Branch 2 not taken.
|
32 | const auto *mip_level_ptr = g_CMIPS.GetMipLevel(&mipSet, nMipLevel); |
| 90 | 32 | output.write( | |
| 91 |
1/2✓ Branch 2 taken 32 times.
✗ Branch 3 not taken.
|
32 | reinterpret_cast<const char*>(mip_level_ptr->m_pbData), // NOLINT(cppcoreguidelines-pro-type-union-access) |
| 92 | 32 | static_cast<std::streamsize>(mip_level_ptr->m_dwLinearSize)); | |
| 93 | } | ||
| 94 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | const auto& buffer_str = output.str(); |
| 95 |
1/2✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
|
28 | return { buffer_str.begin(), buffer_str.end() }; |
| 96 | 7 | } | |
| 97 | |||
| 98 | // Generate additional mipmaps up to 1x1 size | ||
| 99 | 2 | static auto generate_extra_mipmaps( | |
| 100 | MipSet& mipset_in, | ||
| 101 | const wc3lib::blp::Blp::MipMap& last_mipmap, | ||
| 102 | const size_t last_mipmap_idx | ||
| 103 | )-> bool | ||
| 104 | { | ||
| 105 | 2 | size_t mip_idx = last_mipmap_idx; | |
| 106 | 2 | int mip_width = static_cast<int>(last_mipmap.width()); | |
| 107 | 2 | int mip_height = static_cast<int>(last_mipmap.height()); | |
| 108 | |||
| 109 |
3/4✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
12 | while (mip_width > 1 || mip_height > 1) { |
| 110 | 10 | mip_width = std::max(1, mip_width / 2); | |
| 111 | 10 | mip_height = std::max(1, mip_height / 2); | |
| 112 | 10 | mip_idx++; | |
| 113 | |||
| 114 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | CMP_MipLevel* this_mip_level = g_CMIPS.GetMipLevel( |
| 115 | &mipset_in, | ||
| 116 | static_cast<CMP_INT>(mip_idx), | ||
| 117 | 0); | ||
| 118 | |||
| 119 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (this_mip_level == nullptr) { |
| 120 | ✗ | spdlog::error("generate_extra_mipmaps: Error obtaining new mipmap level."); | |
| 121 | ✗ | return false; | |
| 122 | } | ||
| 123 | |||
| 124 |
4/8✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
✓ Branch 7 taken 10 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 10 times.
|
10 | if (!g_CMIPS.AllocateMipLevelData( |
| 125 | this_mip_level, | ||
| 126 | mip_width, | ||
| 127 | mip_height, | ||
| 128 | mipset_in.m_ChannelFormat, | ||
| 129 | mipset_in.m_TextureDataType)) | ||
| 130 | { | ||
| 131 | ✗ | spdlog::error("generate_extra_mipmaps: Error allocating mipmap level data."); | |
| 132 | ✗ | return false; | |
| 133 | } | ||
| 134 | |||
| 135 | 10 | CMP_MipLevel* prev_mip_level = g_CMIPS.GetMipLevel( // NOLINT wrong const result suggestion | |
| 136 | &mipset_in, | ||
| 137 |
1/2✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
|
10 | static_cast<CMP_INT>(mip_idx - 1), |
| 138 | 10 | 0); | |
| 139 | |||
| 140 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (this_mip_level == nullptr) { |
| 141 | ✗ | spdlog::error("generate_extra_mipmaps: Error obtaining base mipmap level."); | |
| 142 | ✗ | return false; | |
| 143 | } | ||
| 144 | |||
| 145 |
2/4✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
|
10 | GenerateMipmapLevel( |
| 146 | this_mip_level, | ||
| 147 | &prev_mip_level, | ||
| 148 | 1, | ||
| 149 | mipset_in.m_format); | ||
| 150 | } | ||
| 151 | 2 | return true; | |
| 152 | } | ||
| 153 | |||
| 154 | |||
| 155 | // NOLINTEND(clang-diagnostic-missing-designated-field-initializers, cppcoreguidelines-avoid-non-const-global-variables, cppcoreguidelines-pro-type-reinterpret-cast) | ||
| 156 | |||
| 157 | 8 | auto convert_blp_to_dds_texture_amdc( // NOLINT | |
| 158 | const FileData& blp_file, | ||
| 159 | const Compression& compression, | ||
| 160 | bool regen_mipmaps | ||
| 161 | )-> std::expected<FileData, ErrorMessage> | ||
| 162 | try { | ||
| 163 |
1/2✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | wc3lib::blp::Blp texture; |
| 164 | |||
| 165 | static const std::unordered_map<Compression, CMP_FORMAT> format_map = { | ||
| 166 | { Compression::DDS_BC1, CMP_FORMAT_BC1 }, | ||
| 167 | { Compression::DDS_BC3, CMP_FORMAT_BC3 }, | ||
| 168 | { Compression::DDS_BC7, CMP_FORMAT_BC7 }, | ||
| 169 |
3/8✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 9 taken 8 times.
✗ Branch 10 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
|
24 | }; |
| 170 | |||
| 171 |
1/2✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
|
8 | std::ispanstream input(blp_file); |
| 172 |
2/2✓ Branch 2 taken 7 times.
✓ Branch 3 taken 1 times.
|
8 | texture.read(input); |
| 173 | |||
| 174 |
1/2✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
|
7 | CMP_InitFramework(); |
| 175 | |||
| 176 | 7 | const bool has_mipmaps = texture.mipMaps().size() > 1; | |
| 177 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
|
7 | const size_t mipmap_count = regen_mipmaps ? 1 : texture.mipMaps().size(); |
| 178 |
1/2✓ Branch 5 taken 7 times.
✗ Branch 6 not taken.
|
7 | const int blp_width = static_cast<int>(texture.mipMaps()[0].width()); |
| 179 |
1/2✓ Branch 5 taken 7 times.
✗ Branch 6 not taken.
|
7 | const int blp_height = static_cast<int>(texture.mipMaps()[0].height()); |
| 180 | |||
| 181 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | const MipSetPtr mipset_in(new CMP_MipSet{}); |
| 182 | |||
| 183 |
2/4✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
|
7 | if (!g_CMIPS.AllocateMipSet(mipset_in.get(), CF_8bit, TDT_ARGB, TT_2D, blp_width, blp_height, 1)) { |
| 184 | ✗ | return std::unexpected("Compressionator: Error allocating Compressionator::MipSet"); | |
| 185 | } | ||
| 186 | |||
| 187 | 7 | const auto max_mipmaps = static_cast<size_t>(mipset_in->m_nMaxMipLevels); | |
| 188 |
4/4✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 2 times.
|
7 | const auto extra_mipmaps = regen_mipmaps || has_mipmaps ? max_mipmaps - mipmap_count : 0; |
| 189 | |||
| 190 | 7 | mipset_in->m_nMipLevels = static_cast<CMP_INT>(mipmap_count + extra_mipmaps); | |
| 191 | 7 | mipset_in->m_format = CMP_FORMAT_RGBA_8888; | |
| 192 | |||
| 193 |
2/2✓ Branch 0 taken 22 times.
✓ Branch 1 taken 7 times.
|
29 | for (size_t mip_idx = 0; mip_idx < mipmap_count; ++mip_idx) { |
| 194 |
1/2✓ Branch 5 taken 22 times.
✗ Branch 6 not taken.
|
22 | const auto& mipmap = texture.mipMaps()[mip_idx]; |
| 195 | 22 | const int mip_width = static_cast<int>(mipmap.width()); | |
| 196 | 22 | const int mip_height = static_cast<int>(mipmap.height()); | |
| 197 | |||
| 198 |
1/2✓ Branch 2 taken 22 times.
✗ Branch 3 not taken.
|
22 | CMP_MipLevel* mip_level_ptr = g_CMIPS.GetMipLevel(mipset_in.get(), static_cast<CMP_INT>(mip_idx)); |
| 199 |
2/4✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 22 times.
|
22 | if (!g_CMIPS.AllocateMipLevelData(mip_level_ptr, mip_width, mip_height, CF_8bit, TDT_ARGB)) { |
| 200 | ✗ | return std::unexpected("Compressionator: Error allocating MipLevelData"); | |
| 201 | } | ||
| 202 | |||
| 203 | 22 | std::vector<uint32_t> mipmap_color_buffer; | |
| 204 |
2/2✓ Branch 2 taken 1 times.
✓ Branch 3 taken 21 times.
|
22 | if (texture.compression() == wc3lib::blp::Blp::Compression::Paletted) { |
| 205 |
1/2✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | const auto& palette = texture.palette(); |
| 206 |
1/2✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
|
1 | mipmap_color_buffer = get_paletted_mipmap_buffer_rgba(mipmap, palette); |
| 207 | } else { | ||
| 208 |
1/2✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
|
21 | mipmap_color_buffer = get_mipmap_buffer_rgba(mipmap); |
| 209 | } | ||
| 210 | |||
| 211 | 22 | CMP_BYTE* data_ptr = mip_level_ptr->m_pbData; // NOLINT(cppcoreguidelines-pro-type-union-access) | |
| 212 |
2/4✗ Branch 2 not taken.
✓ Branch 3 taken 22 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 22 times.
|
22 | memcpy(data_ptr, mipmap_color_buffer.data(), mipmap_color_buffer.size()); |
| 213 | |||
| 214 | // Assign miplevel 0 to MipSetin pData ref | ||
| 215 |
2/2✓ Branch 3 taken 7 times.
✓ Branch 4 taken 15 times.
|
22 | if (mipset_in->pData == nullptr) { |
| 216 | 7 | mipset_in->pData = data_ptr; | |
| 217 | 7 | mipset_in->dwDataSize = static_cast<CMP_DWORD>(mipmap_color_buffer.size()); | |
| 218 | 7 | mipset_in->dwWidth = static_cast<CMP_DWORD>(mip_width); | |
| 219 | 7 | mipset_in->dwHeight = static_cast<CMP_DWORD>(mip_height); | |
| 220 | } | ||
| 221 | 22 | } | |
| 222 | |||
| 223 | // auto generate extra mipmaps up to 1x1 dimesion | ||
| 224 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
|
7 | if (extra_mipmaps > 0) { |
| 225 |
1/2✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | const bool result = generate_extra_mipmaps( |
| 226 | 2 | *mipset_in, | |
| 227 |
1/2✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
|
2 | texture.mipMaps()[mipmap_count - 1], |
| 228 | mipmap_count - 1); | ||
| 229 | |||
| 230 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (!result) { |
| 231 | ✗ | return std::unexpected("Compressionator: Error generating extra mipmaps."); | |
| 232 | } | ||
| 233 | } | ||
| 234 | |||
| 235 | |||
| 236 | // Do compression | ||
| 237 | 7 | KernelOptions options = {}; | |
| 238 | 7 | options.encodeWith = CMP_Compute_type::CMP_CPU; | |
| 239 |
2/4✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
|
7 | options.format = format_map.at(compression); // Destination format (e.g., BC1, BC3, BC7) |
| 240 | 7 | options.fquality = 1.0F; // Quality level (0.0 to 1.0) | |
| 241 | 7 | options.threads = 0; | |
| 242 | |||
| 243 |
3/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 6 times.
|
7 | if (options.format == CMP_FORMAT_BC1) { |
| 244 | 1 | options.bc15.useAlphaThreshold = true; // NOLINT(cppcoreguidelines-pro-type-union-access) | |
| 245 | 1 | options.bc15.alphaThreshold = 1; // NOLINT(cppcoreguidelines-pro-type-union-access) | |
| 246 | } | ||
| 247 | |||
| 248 |
1/2✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | const MipSetPtr mipset_out(new CMP_MipSet{}); |
| 249 | |||
| 250 | 7 | auto dont_stop_callback = [] (CMP_FLOAT /*fProgress*/, CMP_DWORD_PTR /*pUser1*/, CMP_DWORD_PTR /*pUser2*/) -> bool { | |
| 251 | 94 | return false; | |
| 252 | }; | ||
| 253 | |||
| 254 | // Perform compression CMP_CalculateBufferSize() | ||
| 255 |
2/4✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 7 times.
|
7 | if (CMP_ProcessTexture(mipset_in.get(), mipset_out.get(), options, dont_stop_callback) != CMP_OK) { |
| 256 | ✗ | return std::unexpected("Compressionator: Error processing texture."); | |
| 257 | } | ||
| 258 | |||
| 259 |
1/2✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
|
7 | return persist_dds_dx10(*mipset_out); |
| 260 |
1/2✗ Branch 12 not taken.
✓ Branch 13 taken 1 times.
|
10 | } catch (std::exception &e) { |
| 261 |
1/2✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
|
1 | return std::unexpected(e.what()); |
| 262 | 1 | } | |
| 263 | |||
| 264 | |||
| 265 | } // namespace assmpq::blp | ||
| 266 | |||
| 267 | |||
| 268 | |||
| 269 |