GCC Code Coverage Report


Directory: ./
File: src/blp_library/converter_dds_nvtt.cpp
Date: 2026-04-01 15:09:43
Exec Total Coverage
Lines: 68 71 95.8%
Functions: 6 6 100.0%
Branches: 60 108 55.6%

Line Branch Exec Source
1 #include <expected>
2 #include <spanstream>
3
4 #include <spdlog/spdlog.h>
5 #include <nvtt/nvtt.h>
6 #include <nvtt/Surface.h>
7
8 #include <blp/blp.hpp>
9 #include "assets_mpq_importer/blp.hpp"
10 #include "utils_blp.hpp"
11
12 namespace assmpq::blp {
13
14 class MemoryOutputHandler : public nvtt::OutputHandler
15 {
16 static constexpr size_t kDDSHeadetSize = 148;
17 public:
18 7 explicit MemoryOutputHandler(size_t estimated_size = 0) {
19
1/2
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 dds_data.reserve(estimated_size + kDDSHeadetSize);
20 7 }
21
22 // This vector will store the DDS data
23 std::vector<char> dds_data;
24
25 // The begin method is called at the start of the compression process.
26 // It can be used to prepare the output buffer and optionally write the DDS header.
27 32 void beginImage(int /*size*/, int /*width*/, int /*height*/, int /*depth*/, int /*faceCount*/, int /*mipmapCount*/) override {}
28
29 // The writeData method is called to write compressed data chunks to the output.
30 // We append the data to our vector.
31 39 bool writeData(const void * data, int size) override
32 {
33 39 const auto* bytes = static_cast<const unsigned char*>(data);
34 39 const std::span<const unsigned char> data_span(bytes, static_cast<size_t>(size));
35
1/2
✓ Branch 14 taken 39 times.
✗ Branch 15 not taken.
39 dds_data.insert(dds_data.end(), data_span.begin(), data_span.end());
36 39 return true;
37 }
38
39 // The end method is called when the compression is finished.
40 32 void endImage() override {}
41 };
42
43 // Generate additional mipmaps up to 1x1 size
44 2 static auto generate_extra_mipmaps(
45 const nvtt::Context& context,
46 const nvtt::CompressionOptions& compression_options,
47 const nvtt::OutputOptions& output_options,
48 const wc3lib::blp::Blp& texture,
49 const wc3lib::blp::Blp::MipMap& last_mipmap,
50 const size_t last_mipmap_idx
51 )-> bool
52 {
53
1/2
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 nvtt::Surface surface;
54 2 size_t mip_idx = last_mipmap_idx;
55
56 2 std::vector<uint32_t> mipmap_color_buffer;
57
2/2
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
2 if (texture.compression() == wc3lib::blp::Blp::Compression::Paletted) {
58
1/2
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 const auto& palette = texture.palette();
59
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 mipmap_color_buffer = get_paletted_mipmap_buffer_rgba(last_mipmap, palette);
60 } else {
61
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 mipmap_color_buffer = get_mipmap_buffer_rgba(last_mipmap);
62 }
63
64 2 const int mip_width = static_cast<int>(last_mipmap.width());
65 2 const int mip_height = static_cast<int>(last_mipmap.height());
66
67
2/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
2 if (!surface.setImage(nvtt::InputFormat_BGRA_8UB, mip_width, mip_height, 1, mipmap_color_buffer.data())) {
68 spdlog::error("Error setting image data to nvtt::Surface.");
69 return false;
70 }
71
72
3/4
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 2 times.
12 while (surface.buildNextMipmap(nvtt::MipmapFilter_Triangle, 1)) {
73
1/2
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
10 context.compress(
74 surface,
75 0,
76 static_cast<int>(++mip_idx),
77 compression_options,
78 output_options);
79 }
80
81 2 return true;
82 2 }
83
84 8 auto convert_blp_to_dds_texture_nvtt(
85 const FileData& blp_file,
86 const Compression& compression,
87 bool regen_mipmaps
88 )-> std::expected<FileData, ErrorMessage>
89 try {
90
1/2
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 wc3lib::blp::Blp texture;
91
92 static const std::unordered_map<Compression, nvtt::Format> format_map = {
93 { Compression::DDS_BC1, nvtt::Format_BC1 },
94 { Compression::DDS_BC3, nvtt::Format_BC3 },
95 { Compression::DDS_BC7, nvtt::Format_BC7 },
96
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 };
97
98
1/2
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
8 std::ispanstream input(blp_file);
99
2/2
✓ Branch 2 taken 7 times.
✓ Branch 3 taken 1 times.
8 texture.read(input);
100
101
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 nvtt::CompressionOptions compression_options;
102 // Set the desired compression format, e.g., BC1, BC3, or BC7
103
3/6
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✓ Branch 8 taken 7 times.
✗ Branch 9 not taken.
7 compression_options.setFormat(format_map.at(compression));
104
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 compression_options.setQuality(nvtt::Quality_Normal);
105
106
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 const nvtt::Context context;
107 // context.enableCudaAcceleration(!nocuda);
108
109 7 const bool has_mipmaps = texture.mipMaps().size() > 1;
110
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
7 const size_t mipmap_count = regen_mipmaps ? 1 : texture.mipMaps().size();
111
112
1/2
✓ Branch 5 taken 7 times.
✗ Branch 6 not taken.
7 const int blp_width = static_cast<int>(texture.mipMaps()[0].width());
113
1/2
✓ Branch 5 taken 7 times.
✗ Branch 6 not taken.
7 const int blp_height = static_cast<int>(texture.mipMaps()[0].height());
114
115
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 const auto max_mipmaps = nv::countMipmaps(
116 static_cast<unsigned>(blp_width),
117 static_cast<unsigned>(blp_height), 0);
118
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;
119
120 14 const int estimated_size = context.estimateSize(
121 blp_width,
122 blp_height,
123 1,
124
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 static_cast<int>(mipmap_count + extra_mipmaps),
125 compression_options);
126
127
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 MemoryOutputHandler output_handler(static_cast<size_t>(estimated_size));
128
129
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 nvtt::OutputOptions output_options;
130
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 output_options.setContainer(nvtt::Container_DDS10);
131
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 output_options.setOutputHandler(&output_handler);
132
133 // Compress the texture
134 // For NVTT 3, you typically call outputHeader() and then compress().
135
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (context.outputHeader(
136 nvtt::TextureType_2D,
137 blp_width,
138 blp_height,
139 1,
140 1,
141
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 static_cast<int>(mipmap_count + extra_mipmaps),
142 false,
143 compression_options,
144 output_options)) {
145
146 // Conver and add each custom mipmap level
147
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) {
148
1/2
✓ Branch 5 taken 22 times.
✗ Branch 6 not taken.
22 const auto& mipmap = texture.mipMaps()[mip_idx];
149
150 22 const int mip_width = static_cast<int>(mipmap.width());
151 22 const int mip_height = static_cast<int>(mipmap.height());
152
153 22 std::vector<float> mipmap_color_buffer;
154
2/2
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 21 times.
22 if (texture.compression() == wc3lib::blp::Blp::Compression::Paletted) {
155
1/2
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 const auto& palette = texture.palette();
156
1/2
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
1 mipmap_color_buffer = get_paletted_mipmap_buffer_float(mipmap, palette);
157 } else {
158
1/2
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
21 mipmap_color_buffer = get_mipmap_buffer_float(mipmap);
159 }
160
161 // Feed the custom data for the current mip level
162 // The library will compress this data and write the compressed blocks to the output handler
163
1/2
✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
22 context.compress(
164 mip_width,
165 mip_height,
166 1,
167 0,
168 static_cast<int>(mip_idx),
169 22 mipmap_color_buffer.data(),
170 compression_options,
171 output_options
172 );
173 22 }
174
175 // auto generate extra mipmaps up to 1x1 dimesion
176
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 5 times.
7 if (extra_mipmaps > 0) {
177
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 const bool result = generate_extra_mipmaps(
178 context,
179 compression_options,
180 output_options,
181 texture,
182
1/2
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
2 texture.mipMaps()[mipmap_count - 1],
183 mipmap_count - 1
184 );
185
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!result) {
187 return std::unexpected("Error generating extra mipmaps.");
188 }
189 }
190 }
191
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 return output_handler.dds_data;
192
1/2
✗ Branch 18 not taken.
✓ Branch 19 taken 1 times.
10 } catch (std::exception &e) {
193
1/2
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
1 return std::unexpected(e.what());
194 1 }
195
196
197 } // namespace assmpq::blp
198
199
200
201