GCC Code Coverage Report


Directory: ./
File: src/blp_library/converter_dds_amdc.cpp
Date: 2026-04-01 15:09:43
Exec Total Coverage
Lines: 116 128 90.6%
Functions: 6 6 100.0%
Branches: 82 154 53.2%

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