Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/modules/etcpak/image_compress_etcpak.cpp
10277 views
1
/**************************************************************************/
2
/* image_compress_etcpak.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "image_compress_etcpak.h"
32
33
#ifdef TOOLS_ENABLED
34
35
#include "core/os/os.h"
36
#include "core/string/print_string.h"
37
38
#include <ProcessDxtc.hpp>
39
#include <ProcessRGB.hpp>
40
41
EtcpakType _determine_etc_type(Image::UsedChannels p_channels) {
42
switch (p_channels) {
43
case Image::USED_CHANNELS_L:
44
return EtcpakType::ETCPAK_TYPE_ETC2;
45
case Image::USED_CHANNELS_LA:
46
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
47
case Image::USED_CHANNELS_R:
48
return EtcpakType::ETCPAK_TYPE_ETC2_R;
49
case Image::USED_CHANNELS_RG:
50
return EtcpakType::ETCPAK_TYPE_ETC2_RG;
51
case Image::USED_CHANNELS_RGB:
52
return EtcpakType::ETCPAK_TYPE_ETC2;
53
case Image::USED_CHANNELS_RGBA:
54
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
55
56
default:
57
return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA;
58
}
59
}
60
61
EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) {
62
switch (p_channels) {
63
case Image::USED_CHANNELS_L:
64
return EtcpakType::ETCPAK_TYPE_DXT1;
65
case Image::USED_CHANNELS_LA:
66
return EtcpakType::ETCPAK_TYPE_DXT5;
67
case Image::USED_CHANNELS_R:
68
return EtcpakType::ETCPAK_TYPE_RGTC_R;
69
case Image::USED_CHANNELS_RG:
70
return EtcpakType::ETCPAK_TYPE_RGTC_RG;
71
case Image::USED_CHANNELS_RGB:
72
return EtcpakType::ETCPAK_TYPE_DXT1;
73
case Image::USED_CHANNELS_RGBA:
74
return EtcpakType::ETCPAK_TYPE_DXT5;
75
76
default:
77
return EtcpakType::ETCPAK_TYPE_DXT5;
78
}
79
}
80
81
void _compress_etc1(Image *r_img) {
82
_compress_etcpak(EtcpakType::ETCPAK_TYPE_ETC1, r_img);
83
}
84
85
void _compress_etc2(Image *r_img, Image::UsedChannels p_channels) {
86
_compress_etcpak(_determine_etc_type(p_channels), r_img);
87
}
88
89
void _compress_bc(Image *r_img, Image::UsedChannels p_channels) {
90
_compress_etcpak(_determine_dxt_type(p_channels), r_img);
91
}
92
93
void _compress_etcpak(EtcpakType p_compress_type, Image *r_img) {
94
uint64_t start_time = OS::get_singleton()->get_ticks_msec();
95
96
// The image is already compressed, return.
97
if (r_img->is_compressed()) {
98
return;
99
}
100
101
// Convert to RGBA8 for compression.
102
r_img->convert(Image::FORMAT_RGBA8);
103
104
// Determine output format based on Etcpak type.
105
Image::Format target_format = Image::FORMAT_RGBA8;
106
107
switch (p_compress_type) {
108
case EtcpakType::ETCPAK_TYPE_ETC1:
109
target_format = Image::FORMAT_ETC;
110
break;
111
112
case EtcpakType::ETCPAK_TYPE_ETC2:
113
target_format = Image::FORMAT_ETC2_RGB8;
114
break;
115
116
case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
117
target_format = Image::FORMAT_ETC2_RGBA8;
118
break;
119
120
case EtcpakType::ETCPAK_TYPE_ETC2_R:
121
target_format = Image::FORMAT_ETC2_R11;
122
break;
123
124
case EtcpakType::ETCPAK_TYPE_ETC2_RG:
125
target_format = Image::FORMAT_ETC2_RG11;
126
break;
127
128
case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
129
target_format = Image::FORMAT_ETC2_RA_AS_RG;
130
r_img->convert_rg_to_ra_rgba8();
131
break;
132
133
case EtcpakType::ETCPAK_TYPE_DXT1:
134
target_format = Image::FORMAT_DXT1;
135
break;
136
137
case EtcpakType::ETCPAK_TYPE_DXT5:
138
target_format = Image::FORMAT_DXT5;
139
break;
140
141
case EtcpakType::ETCPAK_TYPE_RGTC_R:
142
target_format = Image::FORMAT_RGTC_R;
143
break;
144
145
case EtcpakType::ETCPAK_TYPE_RGTC_RG:
146
target_format = Image::FORMAT_RGTC_RG;
147
break;
148
149
case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG:
150
target_format = Image::FORMAT_DXT5_RA_AS_RG;
151
r_img->convert_rg_to_ra_rgba8();
152
break;
153
154
default:
155
ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT.");
156
break;
157
}
158
159
// It's badly documented but ETCPAK seems to expect BGRA8 for ETC formats.
160
if (p_compress_type < EtcpakType::ETCPAK_TYPE_DXT1) {
161
r_img->convert_rgba8_to_bgra8();
162
}
163
164
// Compress image data and (if required) mipmaps.
165
const bool has_mipmaps = r_img->has_mipmaps();
166
int width = r_img->get_width();
167
int height = r_img->get_height();
168
169
/*
170
The first mipmap level of a compressed texture must be a multiple of 4. Quote from D3D11.3 spec:
171
172
BC format surfaces are always multiples of full blocks, each block representing 4x4 pixels.
173
For mipmaps, the top level map is required to be a multiple of 4 size in all dimensions.
174
The sizes for the lower level maps are computed as they are for all mipmapped surfaces,
175
and thus may not be a multiple of 4, for example a top level map of 20 results in a second level
176
map size of 10. For these cases, there is a differing 'physical' size and a 'virtual' size.
177
The virtual size is that computed for each mip level without adjustment, which is 10 for the example.
178
The physical size is the virtual size rounded up to the next multiple of 4, which is 12 for the example,
179
and this represents the actual memory size. The sampling hardware will apply texture address
180
processing based on the virtual size (using, for example, border color if specified for accesses
181
beyond 10), and thus for the example case will not access the 11th and 12th row of the resource.
182
So for mipmap chains when an axis becomes < 4 in size, only texels 'a','b','e','f'
183
are used for a 2x2 map, and texel 'a' is used for 1x1. Note that this is similar to, but distinct from,
184
the surface pitch, which can encompass additional padding beyond the physical surface size.
185
*/
186
187
if (width % 4 != 0 || height % 4 != 0) {
188
width = width <= 2 ? width : (width + 3) & ~3;
189
height = height <= 2 ? height : (height + 3) & ~3;
190
}
191
192
// Multiple-of-4 should be guaranteed by above.
193
// However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels,
194
// which are individually compressed Image objects that violate the above rule.
195
// Hence, we allow Nx1 and Nx2 images through without forcing to multiple-of-4.
196
197
// Create the buffer for compressed image data.
198
Vector<uint8_t> dest_data;
199
dest_data.resize(Image::get_image_data_size(width, height, target_format, has_mipmaps));
200
uint8_t *dest_write = dest_data.ptrw();
201
202
const uint8_t *src_read = r_img->get_data().ptr();
203
204
const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0;
205
Vector<uint32_t> padded_src;
206
207
for (int i = 0; i < mip_count + 1; i++) {
208
// Get write mip metrics for target image.
209
int dest_mip_w, dest_mip_h;
210
int64_t dest_mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dest_mip_w, dest_mip_h);
211
212
// Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer).
213
ERR_FAIL_COND(dest_mip_ofs % 8 != 0);
214
uint64_t *dest_mip_write = reinterpret_cast<uint64_t *>(dest_write + dest_mip_ofs);
215
216
// Block size.
217
dest_mip_w = (dest_mip_w + 3) & ~3;
218
dest_mip_h = (dest_mip_h + 3) & ~3;
219
const uint32_t blocks = dest_mip_w * dest_mip_h / 16;
220
221
// Get mip data from source image for reading.
222
int64_t src_mip_ofs, src_mip_size;
223
int src_mip_w, src_mip_h;
224
225
r_img->get_mipmap_offset_size_and_dimensions(i, src_mip_ofs, src_mip_size, src_mip_w, src_mip_h);
226
227
const uint32_t *src_mip_read = reinterpret_cast<const uint32_t *>(src_read + src_mip_ofs);
228
229
// Pad textures to nearest block by smearing.
230
if (dest_mip_w != src_mip_w || dest_mip_h != src_mip_h) {
231
// Reserve the buffer for padded image data.
232
padded_src.resize(dest_mip_w * dest_mip_h);
233
uint32_t *ptrw = padded_src.ptrw();
234
235
int x = 0, y = 0;
236
for (y = 0; y < src_mip_h; y++) {
237
for (x = 0; x < src_mip_w; x++) {
238
ptrw[dest_mip_w * y + x] = src_mip_read[src_mip_w * y + x];
239
}
240
241
// First, smear in x.
242
for (; x < dest_mip_w; x++) {
243
ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - 1];
244
}
245
}
246
247
// Then, smear in y.
248
for (; y < dest_mip_h; y++) {
249
for (x = 0; x < dest_mip_w; x++) {
250
ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - dest_mip_w];
251
}
252
}
253
254
// Override the src_mip_read pointer to our temporary Vector.
255
src_mip_read = padded_src.ptr();
256
}
257
258
switch (p_compress_type) {
259
case EtcpakType::ETCPAK_TYPE_ETC1:
260
CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, dest_mip_w);
261
break;
262
263
case EtcpakType::ETCPAK_TYPE_ETC2:
264
CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, dest_mip_w, true);
265
break;
266
267
case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA:
268
case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG:
269
CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, dest_mip_w, true);
270
break;
271
272
case EtcpakType::ETCPAK_TYPE_ETC2_R:
273
CompressEacR(src_mip_read, dest_mip_write, blocks, dest_mip_w);
274
break;
275
276
case EtcpakType::ETCPAK_TYPE_ETC2_RG:
277
CompressEacRg(src_mip_read, dest_mip_write, blocks, dest_mip_w);
278
break;
279
280
case EtcpakType::ETCPAK_TYPE_DXT1:
281
CompressBc1Dither(src_mip_read, dest_mip_write, blocks, dest_mip_w);
282
break;
283
284
case EtcpakType::ETCPAK_TYPE_DXT5:
285
case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG:
286
CompressBc3(src_mip_read, dest_mip_write, blocks, dest_mip_w);
287
break;
288
289
case EtcpakType::ETCPAK_TYPE_RGTC_R:
290
CompressBc4(src_mip_read, dest_mip_write, blocks, dest_mip_w);
291
break;
292
293
case EtcpakType::ETCPAK_TYPE_RGTC_RG:
294
CompressBc5(src_mip_read, dest_mip_write, blocks, dest_mip_w);
295
break;
296
297
default:
298
ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format.");
299
break;
300
}
301
}
302
303
// Replace original image with compressed one.
304
r_img->set_data(width, height, has_mipmaps, target_format, dest_data);
305
306
print_verbose(vformat("etcpak: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
307
}
308
#endif // TOOLS_ENABLED
309
310