1 | /**************************************************************************/ |
2 | /* resource_importer_texture.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 "resource_importer_texture.h" |
32 | |
33 | #include "core/config/project_settings.h" |
34 | #include "core/io/config_file.h" |
35 | #include "core/io/image_loader.h" |
36 | #include "core/version.h" |
37 | #include "editor/editor_file_system.h" |
38 | #include "editor/editor_node.h" |
39 | #include "editor/editor_scale.h" |
40 | #include "editor/editor_settings.h" |
41 | #include "editor/import/resource_importer_texture_settings.h" |
42 | #include "scene/resources/compressed_texture.h" |
43 | |
44 | void ResourceImporterTexture::_texture_reimport_roughness(const Ref<CompressedTexture2D> &p_tex, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_channel) { |
45 | ERR_FAIL_COND(p_tex.is_null()); |
46 | |
47 | MutexLock lock(singleton->mutex); |
48 | |
49 | StringName path = p_tex->get_path(); |
50 | |
51 | if (!singleton->make_flags.has(path)) { |
52 | singleton->make_flags[path] = MakeInfo(); |
53 | } |
54 | |
55 | singleton->make_flags[path].flags |= MAKE_ROUGHNESS_FLAG; |
56 | singleton->make_flags[path].channel_for_roughness = p_channel; |
57 | singleton->make_flags[path].normal_path_for_roughness = p_normal_path; |
58 | } |
59 | |
60 | void ResourceImporterTexture::_texture_reimport_3d(const Ref<CompressedTexture2D> &p_tex) { |
61 | ERR_FAIL_COND(p_tex.is_null()); |
62 | |
63 | MutexLock lock(singleton->mutex); |
64 | |
65 | StringName path = p_tex->get_path(); |
66 | |
67 | if (!singleton->make_flags.has(path)) { |
68 | singleton->make_flags[path] = MakeInfo(); |
69 | } |
70 | |
71 | singleton->make_flags[path].flags |= MAKE_3D_FLAG; |
72 | } |
73 | |
74 | void ResourceImporterTexture::_texture_reimport_normal(const Ref<CompressedTexture2D> &p_tex) { |
75 | ERR_FAIL_COND(p_tex.is_null()); |
76 | |
77 | MutexLock lock(singleton->mutex); |
78 | |
79 | StringName path = p_tex->get_path(); |
80 | |
81 | if (!singleton->make_flags.has(path)) { |
82 | singleton->make_flags[path] = MakeInfo(); |
83 | } |
84 | |
85 | singleton->make_flags[path].flags |= MAKE_NORMAL_FLAG; |
86 | } |
87 | |
88 | void ResourceImporterTexture::update_imports() { |
89 | if (EditorFileSystem::get_singleton()->is_scanning() || EditorFileSystem::get_singleton()->is_importing()) { |
90 | return; // do nothing for now |
91 | } |
92 | |
93 | MutexLock lock(mutex); |
94 | Vector<String> to_reimport; |
95 | { |
96 | if (make_flags.is_empty()) { |
97 | return; |
98 | } |
99 | |
100 | for (const KeyValue<StringName, MakeInfo> &E : make_flags) { |
101 | Ref<ConfigFile> cf; |
102 | cf.instantiate(); |
103 | String src_path = String(E.key) + ".import" ; |
104 | |
105 | Error err = cf->load(src_path); |
106 | ERR_CONTINUE(err != OK); |
107 | |
108 | bool changed = false; |
109 | |
110 | if (E.value.flags & MAKE_NORMAL_FLAG && int(cf->get_value("params" , "compress/normal_map" )) == 0) { |
111 | print_line(vformat(TTR("%s: Texture detected as used as a normal map in 3D. Enabling red-green texture compression to reduce memory usage (blue channel is discarded)." ), String(E.key))); |
112 | cf->set_value("params" , "compress/normal_map" , 1); |
113 | changed = true; |
114 | } |
115 | |
116 | if (E.value.flags & MAKE_ROUGHNESS_FLAG && int(cf->get_value("params" , "roughness/mode" )) == 0) { |
117 | print_line(vformat(TTR("%s: Texture detected as used as a roughness map in 3D. Enabling roughness limiter based on the detected associated normal map at %s." ), String(E.key), E.value.normal_path_for_roughness)); |
118 | cf->set_value("params" , "roughness/mode" , E.value.channel_for_roughness + 2); |
119 | cf->set_value("params" , "roughness/src_normal" , E.value.normal_path_for_roughness); |
120 | changed = true; |
121 | } |
122 | |
123 | if (E.value.flags & MAKE_3D_FLAG && bool(cf->get_value("params" , "detect_3d/compress_to" ))) { |
124 | const int compress_to = cf->get_value("params" , "detect_3d/compress_to" ); |
125 | String compress_string; |
126 | cf->set_value("params" , "detect_3d/compress_to" , 0); |
127 | if (compress_to == 1) { |
128 | cf->set_value("params" , "compress/mode" , COMPRESS_VRAM_COMPRESSED); |
129 | compress_string = "VRAM Compressed (S3TC/ETC/BPTC)" ; |
130 | } else if (compress_to == 2) { |
131 | cf->set_value("params" , "compress/mode" , COMPRESS_BASIS_UNIVERSAL); |
132 | compress_string = "Basis Universal" ; |
133 | } |
134 | print_line(vformat(TTR("%s: Texture detected as used in 3D. Enabling mipmap generation and setting the texture compression mode to %s." ), String(E.key), compress_string)); |
135 | cf->set_value("params" , "mipmaps/generate" , true); |
136 | changed = true; |
137 | } |
138 | |
139 | if (changed) { |
140 | cf->save(src_path); |
141 | to_reimport.push_back(E.key); |
142 | } |
143 | } |
144 | |
145 | make_flags.clear(); |
146 | } |
147 | |
148 | if (to_reimport.size()) { |
149 | EditorFileSystem::get_singleton()->reimport_files(to_reimport); |
150 | } |
151 | } |
152 | |
153 | String ResourceImporterTexture::get_importer_name() const { |
154 | return "texture" ; |
155 | } |
156 | |
157 | String ResourceImporterTexture::get_visible_name() const { |
158 | return "Texture2D" ; |
159 | } |
160 | |
161 | void ResourceImporterTexture::get_recognized_extensions(List<String> *p_extensions) const { |
162 | ImageLoader::get_recognized_extensions(p_extensions); |
163 | } |
164 | |
165 | String ResourceImporterTexture::get_save_extension() const { |
166 | return "ctex" ; |
167 | } |
168 | |
169 | String ResourceImporterTexture::get_resource_type() const { |
170 | return "CompressedTexture2D" ; |
171 | } |
172 | |
173 | bool ResourceImporterTexture::get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const { |
174 | if (p_option == "compress/high_quality" || p_option == "compress/hdr_compression" ) { |
175 | int compress_mode = int(p_options["compress/mode" ]); |
176 | if (compress_mode != COMPRESS_VRAM_COMPRESSED) { |
177 | return false; |
178 | } |
179 | } else if (p_option == "compress/lossy_quality" ) { |
180 | int compress_mode = int(p_options["compress/mode" ]); |
181 | if (compress_mode != COMPRESS_LOSSY) { |
182 | return false; |
183 | } |
184 | } else if (p_option == "compress/hdr_mode" ) { |
185 | int compress_mode = int(p_options["compress/mode" ]); |
186 | if (compress_mode < COMPRESS_VRAM_COMPRESSED) { |
187 | return false; |
188 | } |
189 | } else if (p_option == "compress/normal_map" ) { |
190 | int compress_mode = int(p_options["compress/mode" ]); |
191 | if (compress_mode == COMPRESS_LOSSLESS) { |
192 | return false; |
193 | } |
194 | } else if (p_option == "mipmaps/limit" ) { |
195 | return p_options["mipmaps/generate" ]; |
196 | } |
197 | |
198 | return true; |
199 | } |
200 | |
201 | int ResourceImporterTexture::get_preset_count() const { |
202 | return 3; |
203 | } |
204 | |
205 | String ResourceImporterTexture::get_preset_name(int p_idx) const { |
206 | static const char *preset_names[] = { |
207 | TTRC("2D/3D (Auto-Detect)" ), |
208 | TTRC("2D" ), |
209 | TTRC("3D" ), |
210 | }; |
211 | |
212 | return TTRGET(preset_names[p_idx]); |
213 | } |
214 | |
215 | void ResourceImporterTexture::get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const { |
216 | r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode" , PROPERTY_HINT_ENUM, "Lossless,Lossy,VRAM Compressed,VRAM Uncompressed,Basis Universal" , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), p_preset == PRESET_3D ? 2 : 0)); |
217 | r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "compress/high_quality" ), false)); |
218 | r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "compress/lossy_quality" , PROPERTY_HINT_RANGE, "0,1,0.01" ), 0.7)); |
219 | r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/hdr_compression" , PROPERTY_HINT_ENUM, "Disabled,Opaque Only,Always" ), 1)); |
220 | r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/normal_map" , PROPERTY_HINT_ENUM, "Detect,Enable,Disabled" ), 0)); |
221 | r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/channel_pack" , PROPERTY_HINT_ENUM, "sRGB Friendly,Optimized" ), 0)); |
222 | r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "mipmaps/generate" ), (p_preset == PRESET_3D ? true : false))); |
223 | r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "mipmaps/limit" , PROPERTY_HINT_RANGE, "-1,256" ), -1)); |
224 | r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "roughness/mode" , PROPERTY_HINT_ENUM, "Detect,Disabled,Red,Green,Blue,Alpha,Gray" ), 0)); |
225 | r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "roughness/src_normal" , PROPERTY_HINT_FILE, "*.bmp,*.dds,*.exr,*.jpeg,*.jpg,*.hdr,*.png,*.svg,*.tga,*.webp" ), "" )); |
226 | r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/fix_alpha_border" ), p_preset != PRESET_3D)); |
227 | r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/premult_alpha" ), false)); |
228 | r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/normal_map_invert_y" ), false)); |
229 | r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/hdr_as_srgb" ), false)); |
230 | r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "process/hdr_clamp_exposure" ), false)); |
231 | r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "process/size_limit" , PROPERTY_HINT_RANGE, "0,4096,1" ), 0)); |
232 | r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "detect_3d/compress_to" , PROPERTY_HINT_ENUM, "Disabled,VRAM Compressed,Basis Universal" ), (p_preset == PRESET_DETECT) ? 1 : 0)); |
233 | |
234 | // Do path based customization only if a path was passed. |
235 | if (p_path.is_empty() || p_path.get_extension() == "svg" ) { |
236 | r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "svg/scale" , PROPERTY_HINT_RANGE, "0.001,100,0.001" ), 1.0)); |
237 | |
238 | // Editor use only, applies to SVG. |
239 | r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "editor/scale_with_editor_scale" ), false)); |
240 | r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "editor/convert_colors_with_editor_theme" ), false)); |
241 | } |
242 | } |
243 | |
244 | void ResourceImporterTexture::save_to_ctex_format(Ref<FileAccess> f, const Ref<Image> &p_image, CompressMode p_compress_mode, Image::UsedChannels p_channels, Image::CompressMode p_compress_format, float p_lossy_quality) { |
245 | switch (p_compress_mode) { |
246 | case COMPRESS_LOSSLESS: { |
247 | bool lossless_force_png = GLOBAL_GET("rendering/textures/lossless_compression/force_png" ) || |
248 | !Image::_webp_mem_loader_func; // WebP module disabled. |
249 | bool use_webp = !lossless_force_png && p_image->get_width() <= 16383 && p_image->get_height() <= 16383; // WebP has a size limit |
250 | f->store_32(use_webp ? CompressedTexture2D::DATA_FORMAT_WEBP : CompressedTexture2D::DATA_FORMAT_PNG); |
251 | f->store_16(p_image->get_width()); |
252 | f->store_16(p_image->get_height()); |
253 | f->store_32(p_image->get_mipmap_count()); |
254 | f->store_32(p_image->get_format()); |
255 | |
256 | for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { |
257 | Vector<uint8_t> data; |
258 | if (use_webp) { |
259 | data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); |
260 | } else { |
261 | data = Image::png_packer(p_image->get_image_from_mipmap(i)); |
262 | } |
263 | int data_len = data.size(); |
264 | f->store_32(data_len); |
265 | |
266 | const uint8_t *r = data.ptr(); |
267 | f->store_buffer(r, data_len); |
268 | } |
269 | |
270 | } break; |
271 | case COMPRESS_LOSSY: { |
272 | f->store_32(CompressedTexture2D::DATA_FORMAT_WEBP); |
273 | f->store_16(p_image->get_width()); |
274 | f->store_16(p_image->get_height()); |
275 | f->store_32(p_image->get_mipmap_count()); |
276 | f->store_32(p_image->get_format()); |
277 | |
278 | for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { |
279 | Vector<uint8_t> data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality); |
280 | int data_len = data.size(); |
281 | f->store_32(data_len); |
282 | |
283 | const uint8_t *r = data.ptr(); |
284 | f->store_buffer(r, data_len); |
285 | } |
286 | } break; |
287 | case COMPRESS_VRAM_COMPRESSED: { |
288 | Ref<Image> image = p_image->duplicate(); |
289 | |
290 | image->compress_from_channels(p_compress_format, p_channels); |
291 | |
292 | f->store_32(CompressedTexture2D::DATA_FORMAT_IMAGE); |
293 | f->store_16(image->get_width()); |
294 | f->store_16(image->get_height()); |
295 | f->store_32(image->get_mipmap_count()); |
296 | f->store_32(image->get_format()); |
297 | |
298 | Vector<uint8_t> data = image->get_data(); |
299 | int dl = data.size(); |
300 | const uint8_t *r = data.ptr(); |
301 | f->store_buffer(r, dl); |
302 | } break; |
303 | case COMPRESS_VRAM_UNCOMPRESSED: { |
304 | f->store_32(CompressedTexture2D::DATA_FORMAT_IMAGE); |
305 | f->store_16(p_image->get_width()); |
306 | f->store_16(p_image->get_height()); |
307 | f->store_32(p_image->get_mipmap_count()); |
308 | f->store_32(p_image->get_format()); |
309 | |
310 | Vector<uint8_t> data = p_image->get_data(); |
311 | int dl = data.size(); |
312 | const uint8_t *r = data.ptr(); |
313 | |
314 | f->store_buffer(r, dl); |
315 | |
316 | } break; |
317 | case COMPRESS_BASIS_UNIVERSAL: { |
318 | f->store_32(CompressedTexture2D::DATA_FORMAT_BASIS_UNIVERSAL); |
319 | f->store_16(p_image->get_width()); |
320 | f->store_16(p_image->get_height()); |
321 | f->store_32(p_image->get_mipmap_count()); |
322 | f->store_32(p_image->get_format()); |
323 | Vector<uint8_t> data = Image::basis_universal_packer(p_image, p_channels); |
324 | int data_len = data.size(); |
325 | f->store_32(data_len); |
326 | const uint8_t *r = data.ptr(); |
327 | f->store_buffer(r, data_len); |
328 | } break; |
329 | } |
330 | } |
331 | |
332 | void ResourceImporterTexture::_save_ctex(const Ref<Image> &p_image, const String &p_to_path, CompressMode p_compress_mode, float p_lossy_quality, Image::CompressMode p_vram_compression, bool p_mipmaps, bool p_streamable, bool p_detect_3d, bool p_detect_roughness, bool p_detect_normal, bool p_force_normal, bool p_srgb_friendly, bool p_force_po2_for_compressed, uint32_t p_limit_mipmap, const Ref<Image> &p_normal, Image::RoughnessChannel p_roughness_channel) { |
333 | Ref<FileAccess> f = FileAccess::open(p_to_path, FileAccess::WRITE); |
334 | ERR_FAIL_COND(f.is_null()); |
335 | f->store_8('G'); |
336 | f->store_8('S'); |
337 | f->store_8('T'); |
338 | f->store_8('2'); //godot streamable texture 2D |
339 | |
340 | //format version |
341 | f->store_32(CompressedTexture2D::FORMAT_VERSION); |
342 | //texture may be resized later, so original size must be saved first |
343 | f->store_32(p_image->get_width()); |
344 | f->store_32(p_image->get_height()); |
345 | |
346 | uint32_t flags = 0; |
347 | if (p_streamable) { |
348 | flags |= CompressedTexture2D::FORMAT_BIT_STREAM; |
349 | } |
350 | if (p_mipmaps) { |
351 | flags |= CompressedTexture2D::FORMAT_BIT_HAS_MIPMAPS; //mipmaps bit |
352 | } |
353 | if (p_detect_3d) { |
354 | flags |= CompressedTexture2D::FORMAT_BIT_DETECT_3D; |
355 | } |
356 | if (p_detect_roughness) { |
357 | flags |= CompressedTexture2D::FORMAT_BIT_DETECT_ROUGNESS; |
358 | } |
359 | if (p_detect_normal) { |
360 | flags |= CompressedTexture2D::FORMAT_BIT_DETECT_NORMAL; |
361 | } |
362 | |
363 | f->store_32(flags); |
364 | f->store_32(p_limit_mipmap); |
365 | //reserved for future use |
366 | f->store_32(0); |
367 | f->store_32(0); |
368 | f->store_32(0); |
369 | |
370 | /* |
371 | print_line("streamable " + itos(p_streamable)); |
372 | print_line("mipmaps " + itos(p_mipmaps)); |
373 | print_line("detect_3d " + itos(p_detect_3d)); |
374 | print_line("roughness " + itos(p_detect_roughness)); |
375 | print_line("normal " + itos(p_detect_normal)); |
376 | */ |
377 | |
378 | if ((p_compress_mode == COMPRESS_LOSSLESS || p_compress_mode == COMPRESS_LOSSY) && p_image->get_format() > Image::FORMAT_RGBA8) { |
379 | p_compress_mode = COMPRESS_VRAM_UNCOMPRESSED; //these can't go as lossy |
380 | } |
381 | |
382 | Ref<Image> image = p_image->duplicate(); |
383 | |
384 | if (p_force_po2_for_compressed && p_mipmaps && ((p_compress_mode == COMPRESS_BASIS_UNIVERSAL) || (p_compress_mode == COMPRESS_VRAM_COMPRESSED))) { |
385 | image->resize_to_po2(); |
386 | } |
387 | |
388 | if (p_mipmaps && (!image->has_mipmaps() || p_force_normal)) { |
389 | image->generate_mipmaps(p_force_normal); |
390 | } |
391 | |
392 | if (!p_mipmaps) { |
393 | image->clear_mipmaps(); |
394 | } |
395 | |
396 | if (image->has_mipmaps() && p_normal.is_valid()) { |
397 | image->generate_mipmap_roughness(p_roughness_channel, p_normal); |
398 | } |
399 | |
400 | Image::CompressSource csource = Image::COMPRESS_SOURCE_GENERIC; |
401 | if (p_force_normal) { |
402 | csource = Image::COMPRESS_SOURCE_NORMAL; |
403 | } else if (p_srgb_friendly) { |
404 | csource = Image::COMPRESS_SOURCE_SRGB; |
405 | } |
406 | |
407 | Image::UsedChannels used_channels = image->detect_used_channels(csource); |
408 | |
409 | save_to_ctex_format(f, image, p_compress_mode, used_channels, p_vram_compression, p_lossy_quality); |
410 | } |
411 | |
412 | void ResourceImporterTexture::_save_editor_meta(const Dictionary &p_metadata, const String &p_to_path) { |
413 | Ref<FileAccess> f = FileAccess::open(p_to_path, FileAccess::WRITE); |
414 | ERR_FAIL_COND(f.is_null()); |
415 | |
416 | f->store_var(p_metadata); |
417 | } |
418 | |
419 | Dictionary ResourceImporterTexture::_load_editor_meta(const String &p_path) const { |
420 | Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ); |
421 | ERR_FAIL_COND_V_MSG(f.is_null(), Dictionary(), vformat("Missing required editor-specific import metadata for a texture (please reimport it using the 'Import' tab): '%s'" , p_path)); |
422 | |
423 | return f->get_var(); |
424 | } |
425 | |
426 | Error ResourceImporterTexture::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { |
427 | // Parse import options. |
428 | int32_t loader_flags = ImageFormatLoader::FLAG_NONE; |
429 | |
430 | // Compression. |
431 | CompressMode compress_mode = CompressMode(int(p_options["compress/mode" ])); |
432 | const float lossy = p_options["compress/lossy_quality" ]; |
433 | const int pack_channels = p_options["compress/channel_pack" ]; |
434 | const int normal = p_options["compress/normal_map" ]; |
435 | const int hdr_compression = p_options["compress/hdr_compression" ]; |
436 | const int high_quality = p_options["compress/high_quality" ]; |
437 | |
438 | // Mipmaps. |
439 | const bool mipmaps = p_options["mipmaps/generate" ]; |
440 | const uint32_t mipmap_limit = mipmaps ? uint32_t(p_options["mipmaps/limit" ]) : uint32_t(-1); |
441 | |
442 | // Roughness. |
443 | const int roughness = p_options["roughness/mode" ]; |
444 | const String normal_map = p_options["roughness/src_normal" ]; |
445 | |
446 | // Processing. |
447 | const bool fix_alpha_border = p_options["process/fix_alpha_border" ]; |
448 | const bool premult_alpha = p_options["process/premult_alpha" ]; |
449 | const bool normal_map_invert_y = p_options["process/normal_map_invert_y" ]; |
450 | // Support for texture streaming is not implemented yet. |
451 | const bool stream = false; |
452 | const int size_limit = p_options["process/size_limit" ]; |
453 | const bool hdr_as_srgb = p_options["process/hdr_as_srgb" ]; |
454 | if (hdr_as_srgb) { |
455 | loader_flags |= ImageFormatLoader::FLAG_FORCE_LINEAR; |
456 | } |
457 | const bool hdr_clamp_exposure = p_options["process/hdr_clamp_exposure" ]; |
458 | |
459 | float scale = 1.0; |
460 | // SVG-specific options. |
461 | if (p_options.has("svg/scale" )) { |
462 | scale = p_options["svg/scale" ]; |
463 | } |
464 | |
465 | // Editor-specific options. |
466 | bool use_editor_scale = p_options.has("editor/scale_with_editor_scale" ) && p_options["editor/scale_with_editor_scale" ]; |
467 | bool convert_editor_colors = p_options.has("editor/convert_colors_with_editor_theme" ) && p_options["editor/convert_colors_with_editor_theme" ]; |
468 | |
469 | // Start importing images. |
470 | List<Ref<Image>> images_imported; |
471 | |
472 | // Load the normal image. |
473 | Ref<Image> normal_image; |
474 | Image::RoughnessChannel roughness_channel = Image::ROUGHNESS_CHANNEL_R; |
475 | if (mipmaps && roughness > 1 && FileAccess::exists(normal_map)) { |
476 | normal_image.instantiate(); |
477 | if (ImageLoader::load_image(normal_map, normal_image) == OK) { |
478 | roughness_channel = Image::RoughnessChannel(roughness - 2); |
479 | } |
480 | } |
481 | |
482 | // Load the main image. |
483 | Ref<Image> image; |
484 | image.instantiate(); |
485 | Error err = ImageLoader::load_image(p_source_file, image, nullptr, loader_flags, scale); |
486 | if (err != OK) { |
487 | return err; |
488 | } |
489 | images_imported.push_back(image); |
490 | |
491 | // Load the editor-only image. |
492 | Ref<Image> editor_image; |
493 | bool import_editor_image = use_editor_scale || convert_editor_colors; |
494 | if (import_editor_image) { |
495 | float editor_scale = scale; |
496 | if (use_editor_scale) { |
497 | editor_scale = scale * EDSCALE; |
498 | } |
499 | |
500 | int32_t editor_loader_flags = loader_flags; |
501 | if (convert_editor_colors) { |
502 | editor_loader_flags |= ImageFormatLoader::FLAG_CONVERT_COLORS; |
503 | } |
504 | |
505 | editor_image.instantiate(); |
506 | err = ImageLoader::load_image(p_source_file, editor_image, nullptr, editor_loader_flags, editor_scale); |
507 | if (err != OK) { |
508 | WARN_PRINT("Failed to import an image resource for editor use from '" + p_source_file + "'" ); |
509 | } else { |
510 | images_imported.push_back(editor_image); |
511 | } |
512 | } |
513 | |
514 | for (Ref<Image> &target_image : images_imported) { |
515 | // Apply the size limit. |
516 | if (size_limit > 0 && (target_image->get_width() > size_limit || target_image->get_height() > size_limit)) { |
517 | if (target_image->get_width() >= target_image->get_height()) { |
518 | int new_width = size_limit; |
519 | int new_height = target_image->get_height() * new_width / target_image->get_width(); |
520 | |
521 | target_image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC); |
522 | } else { |
523 | int new_height = size_limit; |
524 | int new_width = target_image->get_width() * new_height / target_image->get_height(); |
525 | |
526 | target_image->resize(new_width, new_height, Image::INTERPOLATE_CUBIC); |
527 | } |
528 | |
529 | if (normal == 1) { |
530 | target_image->normalize(); |
531 | } |
532 | } |
533 | |
534 | // Fix alpha border. |
535 | if (fix_alpha_border) { |
536 | target_image->fix_alpha_edges(); |
537 | } |
538 | |
539 | // Premultiply the alpha. |
540 | if (premult_alpha) { |
541 | target_image->premultiply_alpha(); |
542 | } |
543 | |
544 | // Invert the green channel of the image to flip the normal map it contains. |
545 | if (normal_map_invert_y) { |
546 | // Inverting the green channel can be used to flip a normal map's direction. |
547 | // There's no standard when it comes to normal map Y direction, so this is |
548 | // sometimes needed when using a normal map exported from another program. |
549 | // See <http://wiki.polycount.com/wiki/Normal_Map_Technical_Details#Common_Swizzle_Coordinates>. |
550 | const int height = target_image->get_height(); |
551 | const int width = target_image->get_width(); |
552 | |
553 | for (int i = 0; i < width; i++) { |
554 | for (int j = 0; j < height; j++) { |
555 | const Color color = target_image->get_pixel(i, j); |
556 | target_image->set_pixel(i, j, Color(color.r, 1 - color.g, color.b)); |
557 | } |
558 | } |
559 | } |
560 | |
561 | // Clamp HDR exposure. |
562 | if (hdr_clamp_exposure) { |
563 | // Clamp HDR exposure following Filament's tonemapping formula. |
564 | // This can be used to reduce fireflies in environment maps or reduce the influence |
565 | // of the sun from an HDRI panorama on environment lighting (when a DirectionalLight3D is used instead). |
566 | const int height = target_image->get_height(); |
567 | const int width = target_image->get_width(); |
568 | |
569 | // These values are chosen arbitrarily and seem to produce good results with 4,096 samples. |
570 | const float linear = 4096.0; |
571 | const float compressed = 16384.0; |
572 | |
573 | for (int i = 0; i < width; i++) { |
574 | for (int j = 0; j < height; j++) { |
575 | const Color color = target_image->get_pixel(i, j); |
576 | const float luma = color.get_luminance(); |
577 | |
578 | Color clamped_color; |
579 | if (luma <= linear) { |
580 | clamped_color = color; |
581 | } else { |
582 | clamped_color = (color / luma) * ((linear * linear - compressed * luma) / (2 * linear - compressed - luma)); |
583 | } |
584 | |
585 | target_image->set_pixel(i, j, clamped_color); |
586 | } |
587 | } |
588 | } |
589 | } |
590 | |
591 | if (compress_mode == COMPRESS_BASIS_UNIVERSAL && image->get_format() >= Image::FORMAT_RF) { |
592 | // Basis universal does not support float formats, fallback. |
593 | compress_mode = COMPRESS_VRAM_COMPRESSED; |
594 | } |
595 | |
596 | bool detect_3d = int(p_options["detect_3d/compress_to" ]) > 0; |
597 | bool detect_roughness = roughness == 0; |
598 | bool detect_normal = normal == 0; |
599 | bool force_normal = normal == 1; |
600 | bool srgb_friendly_pack = pack_channels == 0; |
601 | |
602 | Array formats_imported; |
603 | |
604 | if (compress_mode == COMPRESS_VRAM_COMPRESSED) { |
605 | // Must import in all formats, in order of priority (so platform choses the best supported one. IE, etc2 over etc). |
606 | // Android, GLES 2.x |
607 | |
608 | const bool is_hdr = (image->get_format() >= Image::FORMAT_RF && image->get_format() <= Image::FORMAT_RGBE9995); |
609 | const bool can_s3tc_bptc = ResourceImporterTextureSettings::should_import_s3tc_bptc(); |
610 | const bool can_etc2_astc = ResourceImporterTextureSettings::should_import_etc2_astc(); |
611 | |
612 | // Add list of formats imported |
613 | if (can_s3tc_bptc) { |
614 | formats_imported.push_back("s3tc_bptc" ); |
615 | } |
616 | if (can_etc2_astc) { |
617 | formats_imported.push_back("etc2_astc" ); |
618 | } |
619 | |
620 | bool can_compress_hdr = hdr_compression > 0; |
621 | bool has_alpha = image->detect_alpha() != Image::ALPHA_NONE; |
622 | bool use_uncompressed = false; |
623 | |
624 | if (is_hdr) { |
625 | if (has_alpha) { |
626 | // Can compress HDR, but HDR with alpha is not compressible. |
627 | if (hdr_compression == 2) { |
628 | // But user selected to compress HDR anyway, so force an alpha-less format. |
629 | if (image->get_format() == Image::FORMAT_RGBAF) { |
630 | image->convert(Image::FORMAT_RGBF); |
631 | } else if (image->get_format() == Image::FORMAT_RGBAH) { |
632 | image->convert(Image::FORMAT_RGBH); |
633 | } |
634 | } else { |
635 | can_compress_hdr = false; |
636 | } |
637 | } |
638 | |
639 | if (!can_compress_hdr) { |
640 | // Fallback to RGBE99995. |
641 | if (image->get_format() != Image::FORMAT_RGBE9995) { |
642 | image->convert(Image::FORMAT_RGBE9995); |
643 | use_uncompressed = true; |
644 | } |
645 | } |
646 | } |
647 | |
648 | if (use_uncompressed) { |
649 | _save_ctex(image, p_save_path + ".ctex" , COMPRESS_VRAM_UNCOMPRESSED, lossy, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel); |
650 | } else { |
651 | if (can_s3tc_bptc) { |
652 | Image::CompressMode image_compress_mode; |
653 | String image_compress_format; |
654 | if (high_quality || is_hdr) { |
655 | image_compress_mode = Image::COMPRESS_BPTC; |
656 | image_compress_format = "bptc" ; |
657 | } else { |
658 | image_compress_mode = Image::COMPRESS_S3TC; |
659 | image_compress_format = "s3tc" ; |
660 | } |
661 | _save_ctex(image, p_save_path + "." + image_compress_format + ".ctex" , compress_mode, lossy, image_compress_mode, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel); |
662 | r_platform_variants->push_back(image_compress_format); |
663 | } |
664 | |
665 | if (can_etc2_astc) { |
666 | Image::CompressMode image_compress_mode; |
667 | String image_compress_format; |
668 | if (high_quality || is_hdr) { |
669 | image_compress_mode = Image::COMPRESS_ASTC; |
670 | image_compress_format = "astc" ; |
671 | } else { |
672 | image_compress_mode = Image::COMPRESS_ETC2; |
673 | image_compress_format = "etc2" ; |
674 | } |
675 | _save_ctex(image, p_save_path + "." + image_compress_format + ".ctex" , compress_mode, lossy, image_compress_mode, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel); |
676 | r_platform_variants->push_back(image_compress_format); |
677 | } |
678 | } |
679 | } else { |
680 | // Import normally. |
681 | _save_ctex(image, p_save_path + ".ctex" , compress_mode, lossy, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel); |
682 | } |
683 | |
684 | if (editor_image.is_valid()) { |
685 | _save_ctex(editor_image, p_save_path + ".editor.ctex" , compress_mode, lossy, Image::COMPRESS_S3TC /*this is ignored */, mipmaps, stream, detect_3d, detect_roughness, detect_normal, force_normal, srgb_friendly_pack, false, mipmap_limit, normal_image, roughness_channel); |
686 | |
687 | // Generate and save editor-specific metadata, which we cannot save to the .import file. |
688 | Dictionary editor_meta; |
689 | |
690 | if (use_editor_scale) { |
691 | editor_meta["editor_scale" ] = EDSCALE; |
692 | } |
693 | if (convert_editor_colors) { |
694 | editor_meta["editor_dark_theme" ] = EditorSettings::get_singleton()->is_dark_theme(); |
695 | } |
696 | |
697 | _save_editor_meta(editor_meta, p_save_path + ".editor.meta" ); |
698 | } |
699 | |
700 | if (r_metadata) { |
701 | Dictionary meta; |
702 | meta["vram_texture" ] = compress_mode == COMPRESS_VRAM_COMPRESSED; |
703 | if (formats_imported.size()) { |
704 | meta["imported_formats" ] = formats_imported; |
705 | } |
706 | |
707 | if (editor_image.is_valid()) { |
708 | meta["has_editor_variant" ] = true; |
709 | } |
710 | |
711 | *r_metadata = meta; |
712 | } |
713 | |
714 | return OK; |
715 | } |
716 | |
717 | const char *ResourceImporterTexture::compression_formats[] = { |
718 | "s3tc_bptc" , |
719 | "etc2_astc" , |
720 | nullptr |
721 | }; |
722 | String ResourceImporterTexture::get_import_settings_string() const { |
723 | String s; |
724 | |
725 | int index = 0; |
726 | while (compression_formats[index]) { |
727 | String setting_path = "rendering/textures/vram_compression/import_" + String(compression_formats[index]); |
728 | bool test = GLOBAL_GET(setting_path); |
729 | if (test) { |
730 | s += String(compression_formats[index]); |
731 | } |
732 | index++; |
733 | } |
734 | |
735 | return s; |
736 | } |
737 | |
738 | bool ResourceImporterTexture::are_import_settings_valid(const String &p_path) const { |
739 | Dictionary meta = ResourceFormatImporter::get_singleton()->get_resource_metadata(p_path); |
740 | |
741 | if (meta.has("has_editor_variant" )) { |
742 | String imported_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(p_path); |
743 | String editor_meta_path = imported_path.replace(".editor.ctex" , ".editor.meta" ); |
744 | Dictionary editor_meta = _load_editor_meta(editor_meta_path); |
745 | |
746 | if (editor_meta.has("editor_scale" ) && (float)editor_meta["editor_scale" ] != EDSCALE) { |
747 | return false; |
748 | } |
749 | if (editor_meta.has("editor_dark_theme" ) && (bool)editor_meta["editor_dark_theme" ] != EditorSettings::get_singleton()->is_dark_theme()) { |
750 | return false; |
751 | } |
752 | } |
753 | |
754 | if (!meta.has("vram_texture" )) { |
755 | return false; |
756 | } |
757 | |
758 | bool vram = meta["vram_texture" ]; |
759 | if (!vram) { |
760 | return true; // Do not care about non-VRAM. |
761 | } |
762 | |
763 | // Will become invalid if formats are missing to import. |
764 | Vector<String> formats_imported; |
765 | if (meta.has("imported_formats" )) { |
766 | formats_imported = meta["imported_formats" ]; |
767 | } |
768 | |
769 | int index = 0; |
770 | bool valid = true; |
771 | while (compression_formats[index]) { |
772 | String setting_path = "rendering/textures/vram_compression/import_" + String(compression_formats[index]); |
773 | if (ProjectSettings::get_singleton()->has_setting(setting_path)) { |
774 | bool test = GLOBAL_GET(setting_path); |
775 | if (test) { |
776 | if (!formats_imported.has(compression_formats[index])) { |
777 | valid = false; |
778 | break; |
779 | } |
780 | } |
781 | } else { |
782 | WARN_PRINT("Setting for imported format not found: " + setting_path); |
783 | } |
784 | index++; |
785 | } |
786 | |
787 | return valid; |
788 | } |
789 | |
790 | ResourceImporterTexture *ResourceImporterTexture::singleton = nullptr; |
791 | |
792 | ResourceImporterTexture::ResourceImporterTexture(bool p_singleton) { |
793 | // This should only be set through the EditorNode. |
794 | if (p_singleton) { |
795 | singleton = this; |
796 | } |
797 | |
798 | CompressedTexture2D::request_3d_callback = _texture_reimport_3d; |
799 | CompressedTexture2D::request_roughness_callback = _texture_reimport_roughness; |
800 | CompressedTexture2D::request_normal_callback = _texture_reimport_normal; |
801 | } |
802 | |
803 | ResourceImporterTexture::~ResourceImporterTexture() { |
804 | if (singleton == this) { |
805 | singleton = nullptr; |
806 | } |
807 | } |
808 | |