1/**************************************************************************/
2/* compressed_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 "compressed_texture.h"
32
33#include "scene/resources/bit_map.h"
34
35Error CompressedTexture2D::_load_data(const String &p_path, int &r_width, int &r_height, Ref<Image> &image, bool &r_request_3d, bool &r_request_normal, bool &r_request_roughness, int &mipmap_limit, int p_size_limit) {
36 alpha_cache.unref();
37
38 ERR_FAIL_COND_V(image.is_null(), ERR_INVALID_PARAMETER);
39
40 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
41 ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path));
42
43 uint8_t header[4];
44 f->get_buffer(header, 4);
45 if (header[0] != 'G' || header[1] != 'S' || header[2] != 'T' || header[3] != '2') {
46 ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is corrupt (Bad header).");
47 }
48
49 uint32_t version = f->get_32();
50
51 if (version > FORMAT_VERSION) {
52 ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is too new.");
53 }
54 r_width = f->get_32();
55 r_height = f->get_32();
56 uint32_t df = f->get_32(); //data format
57
58 //skip reserved
59 mipmap_limit = int(f->get_32());
60 //reserved
61 f->get_32();
62 f->get_32();
63 f->get_32();
64
65#ifdef TOOLS_ENABLED
66
67 r_request_3d = request_3d_callback && df & FORMAT_BIT_DETECT_3D;
68 r_request_roughness = request_roughness_callback && df & FORMAT_BIT_DETECT_ROUGNESS;
69 r_request_normal = request_normal_callback && df & FORMAT_BIT_DETECT_NORMAL;
70
71#else
72
73 r_request_3d = false;
74 r_request_roughness = false;
75 r_request_normal = false;
76
77#endif
78 if (!(df & FORMAT_BIT_STREAM)) {
79 p_size_limit = 0;
80 }
81
82 image = load_image_from_file(f, p_size_limit);
83
84 if (image.is_null() || image->is_empty()) {
85 return ERR_CANT_OPEN;
86 }
87
88 return OK;
89}
90
91void CompressedTexture2D::set_path(const String &p_path, bool p_take_over) {
92 if (texture.is_valid()) {
93 RenderingServer::get_singleton()->texture_set_path(texture, p_path);
94 }
95
96 Resource::set_path(p_path, p_take_over);
97}
98
99void CompressedTexture2D::_requested_3d(void *p_ud) {
100 CompressedTexture2D *ct = (CompressedTexture2D *)p_ud;
101 Ref<CompressedTexture2D> ctex(ct);
102 ERR_FAIL_NULL(request_3d_callback);
103 request_3d_callback(ctex);
104}
105
106void CompressedTexture2D::_requested_roughness(void *p_ud, const String &p_normal_path, RS::TextureDetectRoughnessChannel p_roughness_channel) {
107 CompressedTexture2D *ct = (CompressedTexture2D *)p_ud;
108 Ref<CompressedTexture2D> ctex(ct);
109 ERR_FAIL_NULL(request_roughness_callback);
110 request_roughness_callback(ctex, p_normal_path, p_roughness_channel);
111}
112
113void CompressedTexture2D::_requested_normal(void *p_ud) {
114 CompressedTexture2D *ct = (CompressedTexture2D *)p_ud;
115 Ref<CompressedTexture2D> ctex(ct);
116 ERR_FAIL_NULL(request_normal_callback);
117 request_normal_callback(ctex);
118}
119
120CompressedTexture2D::TextureFormatRequestCallback CompressedTexture2D::request_3d_callback = nullptr;
121CompressedTexture2D::TextureFormatRoughnessRequestCallback CompressedTexture2D::request_roughness_callback = nullptr;
122CompressedTexture2D::TextureFormatRequestCallback CompressedTexture2D::request_normal_callback = nullptr;
123
124Image::Format CompressedTexture2D::get_format() const {
125 return format;
126}
127
128Error CompressedTexture2D::load(const String &p_path) {
129 int lw, lh;
130 Ref<Image> image;
131 image.instantiate();
132
133 bool request_3d;
134 bool request_normal;
135 bool request_roughness;
136 int mipmap_limit;
137
138 Error err = _load_data(p_path, lw, lh, image, request_3d, request_normal, request_roughness, mipmap_limit);
139 if (err) {
140 return err;
141 }
142
143 if (texture.is_valid()) {
144 RID new_texture = RS::get_singleton()->texture_2d_create(image);
145 RS::get_singleton()->texture_replace(texture, new_texture);
146 } else {
147 texture = RS::get_singleton()->texture_2d_create(image);
148 }
149 if (lw || lh) {
150 RS::get_singleton()->texture_set_size_override(texture, lw, lh);
151 }
152
153 w = lw;
154 h = lh;
155 path_to_file = p_path;
156 format = image->get_format();
157
158 if (get_path().is_empty()) {
159 //temporarily set path if no path set for resource, helps find errors
160 RenderingServer::get_singleton()->texture_set_path(texture, p_path);
161 }
162
163#ifdef TOOLS_ENABLED
164
165 if (request_3d) {
166 //print_line("request detect 3D at " + p_path);
167 RS::get_singleton()->texture_set_detect_3d_callback(texture, _requested_3d, this);
168 } else {
169 //print_line("not requesting detect 3D at " + p_path);
170 RS::get_singleton()->texture_set_detect_3d_callback(texture, nullptr, nullptr);
171 }
172
173 if (request_roughness) {
174 //print_line("request detect srgb at " + p_path);
175 RS::get_singleton()->texture_set_detect_roughness_callback(texture, _requested_roughness, this);
176 } else {
177 //print_line("not requesting detect srgb at " + p_path);
178 RS::get_singleton()->texture_set_detect_roughness_callback(texture, nullptr, nullptr);
179 }
180
181 if (request_normal) {
182 //print_line("request detect srgb at " + p_path);
183 RS::get_singleton()->texture_set_detect_normal_callback(texture, _requested_normal, this);
184 } else {
185 //print_line("not requesting detect normal at " + p_path);
186 RS::get_singleton()->texture_set_detect_normal_callback(texture, nullptr, nullptr);
187 }
188
189#endif
190 notify_property_list_changed();
191 emit_changed();
192 return OK;
193}
194
195String CompressedTexture2D::get_load_path() const {
196 return path_to_file;
197}
198
199int CompressedTexture2D::get_width() const {
200 return w;
201}
202
203int CompressedTexture2D::get_height() const {
204 return h;
205}
206
207RID CompressedTexture2D::get_rid() const {
208 if (!texture.is_valid()) {
209 texture = RS::get_singleton()->texture_2d_placeholder_create();
210 }
211 return texture;
212}
213
214void CompressedTexture2D::draw(RID p_canvas_item, const Point2 &p_pos, const Color &p_modulate, bool p_transpose) const {
215 if ((w | h) == 0) {
216 return;
217 }
218 RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, Rect2(p_pos, Size2(w, h)), texture, false, p_modulate, p_transpose);
219}
220
221void CompressedTexture2D::draw_rect(RID p_canvas_item, const Rect2 &p_rect, bool p_tile, const Color &p_modulate, bool p_transpose) const {
222 if ((w | h) == 0) {
223 return;
224 }
225 RenderingServer::get_singleton()->canvas_item_add_texture_rect(p_canvas_item, p_rect, texture, p_tile, p_modulate, p_transpose);
226}
227
228void CompressedTexture2D::draw_rect_region(RID p_canvas_item, const Rect2 &p_rect, const Rect2 &p_src_rect, const Color &p_modulate, bool p_transpose, bool p_clip_uv) const {
229 if ((w | h) == 0) {
230 return;
231 }
232 RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas_item, p_rect, texture, p_src_rect, p_modulate, p_transpose, p_clip_uv);
233}
234
235bool CompressedTexture2D::has_alpha() const {
236 return false;
237}
238
239Ref<Image> CompressedTexture2D::get_image() const {
240 if (texture.is_valid()) {
241 return RS::get_singleton()->texture_2d_get(texture);
242 } else {
243 return Ref<Image>();
244 }
245}
246
247bool CompressedTexture2D::is_pixel_opaque(int p_x, int p_y) const {
248 if (!alpha_cache.is_valid()) {
249 Ref<Image> img = get_image();
250 if (img.is_valid()) {
251 if (img->is_compressed()) { //must decompress, if compressed
252 Ref<Image> decom = img->duplicate();
253 decom->decompress();
254 img = decom;
255 }
256
257 alpha_cache.instantiate();
258 alpha_cache->create_from_image_alpha(img);
259 }
260 }
261
262 if (alpha_cache.is_valid()) {
263 int aw = int(alpha_cache->get_size().width);
264 int ah = int(alpha_cache->get_size().height);
265 if (aw == 0 || ah == 0) {
266 return true;
267 }
268
269 int x = p_x * aw / w;
270 int y = p_y * ah / h;
271
272 x = CLAMP(x, 0, aw);
273 y = CLAMP(y, 0, ah);
274
275 return alpha_cache->get_bit(x, y);
276 }
277
278 return true;
279}
280
281void CompressedTexture2D::reload_from_file() {
282 String path = get_path();
283 if (!path.is_resource_file()) {
284 return;
285 }
286
287 path = ResourceLoader::path_remap(path); //remap for translation
288 path = ResourceLoader::import_remap(path); //remap for import
289 if (!path.is_resource_file()) {
290 return;
291 }
292
293 load(path);
294}
295
296void CompressedTexture2D::_validate_property(PropertyInfo &p_property) const {
297}
298
299Ref<Image> CompressedTexture2D::load_image_from_file(Ref<FileAccess> f, int p_size_limit) {
300 uint32_t data_format = f->get_32();
301 uint32_t w = f->get_16();
302 uint32_t h = f->get_16();
303 uint32_t mipmaps = f->get_32();
304 Image::Format format = Image::Format(f->get_32());
305
306 if (data_format == DATA_FORMAT_PNG || data_format == DATA_FORMAT_WEBP) {
307 //look for a PNG or WebP file inside
308
309 int sw = w;
310 int sh = h;
311
312 //mipmaps need to be read independently, they will be later combined
313 Vector<Ref<Image>> mipmap_images;
314 uint64_t total_size = 0;
315
316 bool first = true;
317
318 for (uint32_t i = 0; i < mipmaps + 1; i++) {
319 uint32_t size = f->get_32();
320
321 if (p_size_limit > 0 && i < (mipmaps - 1) && (sw > p_size_limit || sh > p_size_limit)) {
322 //can't load this due to size limit
323 sw = MAX(sw >> 1, 1);
324 sh = MAX(sh >> 1, 1);
325 f->seek(f->get_position() + size);
326 continue;
327 }
328
329 Vector<uint8_t> pv;
330 pv.resize(size);
331 {
332 uint8_t *wr = pv.ptrw();
333 f->get_buffer(wr, size);
334 }
335
336 Ref<Image> img;
337 if (data_format == DATA_FORMAT_PNG && Image::png_unpacker) {
338 img = Image::png_unpacker(pv);
339 } else if (data_format == DATA_FORMAT_WEBP && Image::webp_unpacker) {
340 img = Image::webp_unpacker(pv);
341 }
342
343 if (img.is_null() || img->is_empty()) {
344 ERR_FAIL_COND_V(img.is_null() || img->is_empty(), Ref<Image>());
345 }
346
347 if (first) {
348 //format will actually be the format of the first image,
349 //as it may have changed on compression
350 format = img->get_format();
351 first = false;
352 } else if (img->get_format() != format) {
353 img->convert(format); //all needs to be the same format
354 }
355
356 total_size += img->get_data().size();
357
358 mipmap_images.push_back(img);
359
360 sw = MAX(sw >> 1, 1);
361 sh = MAX(sh >> 1, 1);
362 }
363
364 //print_line("mipmap read total: " + itos(mipmap_images.size()));
365
366 Ref<Image> image;
367 image.instantiate();
368
369 if (mipmap_images.size() == 1) {
370 //only one image (which will most likely be the case anyway for this format)
371 image = mipmap_images[0];
372 return image;
373
374 } else {
375 //rarer use case, but needs to be supported
376 Vector<uint8_t> img_data;
377 img_data.resize(total_size);
378
379 {
380 uint8_t *wr = img_data.ptrw();
381
382 int ofs = 0;
383 for (int i = 0; i < mipmap_images.size(); i++) {
384 Vector<uint8_t> id = mipmap_images[i]->get_data();
385 int len = id.size();
386 const uint8_t *r = id.ptr();
387 memcpy(&wr[ofs], r, len);
388 ofs += len;
389 }
390 }
391
392 image->set_data(w, h, true, mipmap_images[0]->get_format(), img_data);
393 return image;
394 }
395
396 } else if (data_format == DATA_FORMAT_BASIS_UNIVERSAL) {
397 int sw = w;
398 int sh = h;
399 uint32_t size = f->get_32();
400 if (p_size_limit > 0 && (sw > p_size_limit || sh > p_size_limit)) {
401 //can't load this due to size limit
402 sw = MAX(sw >> 1, 1);
403 sh = MAX(sh >> 1, 1);
404 f->seek(f->get_position() + size);
405 return Ref<Image>();
406 }
407 Vector<uint8_t> pv;
408 pv.resize(size);
409 {
410 uint8_t *wr = pv.ptrw();
411 f->get_buffer(wr, size);
412 }
413 Ref<Image> img;
414 img = Image::basis_universal_unpacker(pv);
415 if (img.is_null() || img->is_empty()) {
416 ERR_FAIL_COND_V(img.is_null() || img->is_empty(), Ref<Image>());
417 }
418 format = img->get_format();
419 sw = MAX(sw >> 1, 1);
420 sh = MAX(sh >> 1, 1);
421 return img;
422 } else if (data_format == DATA_FORMAT_IMAGE) {
423 int size = Image::get_image_data_size(w, h, format, mipmaps ? true : false);
424
425 for (uint32_t i = 0; i < mipmaps + 1; i++) {
426 int tw, th;
427 int ofs = Image::get_image_mipmap_offset_and_dimensions(w, h, format, i, tw, th);
428
429 if (p_size_limit > 0 && i < mipmaps && (p_size_limit > tw || p_size_limit > th)) {
430 if (ofs) {
431 f->seek(f->get_position() + ofs);
432 }
433 continue; //oops, size limit enforced, go to next
434 }
435
436 Vector<uint8_t> data;
437 data.resize(size - ofs);
438
439 {
440 uint8_t *wr = data.ptrw();
441 f->get_buffer(wr, data.size());
442 }
443
444 Ref<Image> image = Image::create_from_data(tw, th, mipmaps - i ? true : false, format, data);
445
446 return image;
447 }
448 }
449
450 return Ref<Image>();
451}
452
453void CompressedTexture2D::_bind_methods() {
454 ClassDB::bind_method(D_METHOD("load", "path"), &CompressedTexture2D::load);
455 ClassDB::bind_method(D_METHOD("get_load_path"), &CompressedTexture2D::get_load_path);
456
457 ADD_PROPERTY(PropertyInfo(Variant::STRING, "load_path", PROPERTY_HINT_FILE, "*.ctex"), "load", "get_load_path");
458}
459
460CompressedTexture2D::CompressedTexture2D() {}
461
462CompressedTexture2D::~CompressedTexture2D() {
463 if (texture.is_valid()) {
464 ERR_FAIL_NULL(RenderingServer::get_singleton());
465 RS::get_singleton()->free(texture);
466 }
467}
468
469Ref<Resource> ResourceFormatLoaderCompressedTexture2D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
470 Ref<CompressedTexture2D> st;
471 st.instantiate();
472 Error err = st->load(p_path);
473 if (r_error) {
474 *r_error = err;
475 }
476 if (err != OK) {
477 return Ref<Resource>();
478 }
479
480 return st;
481}
482
483void ResourceFormatLoaderCompressedTexture2D::get_recognized_extensions(List<String> *p_extensions) const {
484 p_extensions->push_back("ctex");
485}
486
487bool ResourceFormatLoaderCompressedTexture2D::handles_type(const String &p_type) const {
488 return p_type == "CompressedTexture2D";
489}
490
491String ResourceFormatLoaderCompressedTexture2D::get_resource_type(const String &p_path) const {
492 if (p_path.get_extension().to_lower() == "ctex") {
493 return "CompressedTexture2D";
494 }
495 return "";
496}
497
498void CompressedTexture3D::set_path(const String &p_path, bool p_take_over) {
499 if (texture.is_valid()) {
500 RenderingServer::get_singleton()->texture_set_path(texture, p_path);
501 }
502
503 Resource::set_path(p_path, p_take_over);
504}
505
506Image::Format CompressedTexture3D::get_format() const {
507 return format;
508}
509
510Error CompressedTexture3D::_load_data(const String &p_path, Vector<Ref<Image>> &r_data, Image::Format &r_format, int &r_width, int &r_height, int &r_depth, bool &r_mipmaps) {
511 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
512 ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path));
513
514 uint8_t header[4];
515 f->get_buffer(header, 4);
516 ERR_FAIL_COND_V(header[0] != 'G' || header[1] != 'S' || header[2] != 'T' || header[3] != 'L', ERR_FILE_UNRECOGNIZED);
517
518 //stored as compressed textures (used for lossless and lossy compression)
519 uint32_t version = f->get_32();
520
521 if (version > FORMAT_VERSION) {
522 ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is too new.");
523 }
524
525 r_depth = f->get_32(); //depth
526 f->get_32(); //ignored (mode)
527 f->get_32(); // ignored (data format)
528
529 f->get_32(); //ignored
530 int mipmap_count = f->get_32();
531 f->get_32(); //ignored
532 f->get_32(); //ignored
533
534 r_mipmaps = mipmap_count != 0;
535
536 r_data.clear();
537
538 for (int i = 0; i < (r_depth + mipmap_count); i++) {
539 Ref<Image> image = CompressedTexture2D::load_image_from_file(f, 0);
540 ERR_FAIL_COND_V(image.is_null() || image->is_empty(), ERR_CANT_OPEN);
541 if (i == 0) {
542 r_format = image->get_format();
543 r_width = image->get_width();
544 r_height = image->get_height();
545 }
546 r_data.push_back(image);
547 }
548
549 return OK;
550}
551
552Error CompressedTexture3D::load(const String &p_path) {
553 Vector<Ref<Image>> data;
554
555 int tw, th, td;
556 Image::Format tfmt;
557 bool tmm;
558
559 Error err = _load_data(p_path, data, tfmt, tw, th, td, tmm);
560 if (err) {
561 return err;
562 }
563
564 if (texture.is_valid()) {
565 RID new_texture = RS::get_singleton()->texture_3d_create(tfmt, tw, th, td, tmm, data);
566 RS::get_singleton()->texture_replace(texture, new_texture);
567 } else {
568 texture = RS::get_singleton()->texture_3d_create(tfmt, tw, th, td, tmm, data);
569 }
570
571 w = tw;
572 h = th;
573 d = td;
574 mipmaps = tmm;
575 format = tfmt;
576
577 path_to_file = p_path;
578
579 if (get_path().is_empty()) {
580 //temporarily set path if no path set for resource, helps find errors
581 RenderingServer::get_singleton()->texture_set_path(texture, p_path);
582 }
583
584 notify_property_list_changed();
585 emit_changed();
586 return OK;
587}
588
589String CompressedTexture3D::get_load_path() const {
590 return path_to_file;
591}
592
593int CompressedTexture3D::get_width() const {
594 return w;
595}
596
597int CompressedTexture3D::get_height() const {
598 return h;
599}
600
601int CompressedTexture3D::get_depth() const {
602 return d;
603}
604
605bool CompressedTexture3D::has_mipmaps() const {
606 return mipmaps;
607}
608
609RID CompressedTexture3D::get_rid() const {
610 if (!texture.is_valid()) {
611 texture = RS::get_singleton()->texture_3d_placeholder_create();
612 }
613 return texture;
614}
615
616Vector<Ref<Image>> CompressedTexture3D::get_data() const {
617 if (texture.is_valid()) {
618 return RS::get_singleton()->texture_3d_get(texture);
619 } else {
620 return Vector<Ref<Image>>();
621 }
622}
623
624void CompressedTexture3D::reload_from_file() {
625 String path = get_path();
626 if (!path.is_resource_file()) {
627 return;
628 }
629
630 path = ResourceLoader::path_remap(path); //remap for translation
631 path = ResourceLoader::import_remap(path); //remap for import
632 if (!path.is_resource_file()) {
633 return;
634 }
635
636 load(path);
637}
638
639void CompressedTexture3D::_validate_property(PropertyInfo &p_property) const {
640}
641
642void CompressedTexture3D::_bind_methods() {
643 ClassDB::bind_method(D_METHOD("load", "path"), &CompressedTexture3D::load);
644 ClassDB::bind_method(D_METHOD("get_load_path"), &CompressedTexture3D::get_load_path);
645
646 ADD_PROPERTY(PropertyInfo(Variant::STRING, "load_path", PROPERTY_HINT_FILE, "*.ctex"), "load", "get_load_path");
647}
648
649CompressedTexture3D::CompressedTexture3D() {}
650
651CompressedTexture3D::~CompressedTexture3D() {
652 if (texture.is_valid()) {
653 ERR_FAIL_NULL(RenderingServer::get_singleton());
654 RS::get_singleton()->free(texture);
655 }
656}
657
658Ref<Resource> ResourceFormatLoaderCompressedTexture3D::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
659 Ref<CompressedTexture3D> st;
660 st.instantiate();
661 Error err = st->load(p_path);
662 if (r_error) {
663 *r_error = err;
664 }
665 if (err != OK) {
666 return Ref<Resource>();
667 }
668
669 return st;
670}
671
672void ResourceFormatLoaderCompressedTexture3D::get_recognized_extensions(List<String> *p_extensions) const {
673 p_extensions->push_back("ctex3d");
674}
675
676bool ResourceFormatLoaderCompressedTexture3D::handles_type(const String &p_type) const {
677 return p_type == "CompressedTexture3D";
678}
679
680String ResourceFormatLoaderCompressedTexture3D::get_resource_type(const String &p_path) const {
681 if (p_path.get_extension().to_lower() == "ctex3d") {
682 return "CompressedTexture3D";
683 }
684 return "";
685}
686
687void CompressedTextureLayered::set_path(const String &p_path, bool p_take_over) {
688 if (texture.is_valid()) {
689 RenderingServer::get_singleton()->texture_set_path(texture, p_path);
690 }
691
692 Resource::set_path(p_path, p_take_over);
693}
694
695Image::Format CompressedTextureLayered::get_format() const {
696 return format;
697}
698
699Error CompressedTextureLayered::_load_data(const String &p_path, Vector<Ref<Image>> &images, int &mipmap_limit, int p_size_limit) {
700 ERR_FAIL_COND_V(images.size() != 0, ERR_INVALID_PARAMETER);
701
702 Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
703 ERR_FAIL_COND_V_MSG(f.is_null(), ERR_CANT_OPEN, vformat("Unable to open file: %s.", p_path));
704
705 uint8_t header[4];
706 f->get_buffer(header, 4);
707 if (header[0] != 'G' || header[1] != 'S' || header[2] != 'T' || header[3] != 'L') {
708 ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture layered file is corrupt (Bad header).");
709 }
710
711 uint32_t version = f->get_32();
712
713 if (version > FORMAT_VERSION) {
714 ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Compressed texture file is too new.");
715 }
716
717 uint32_t layer_count = f->get_32(); //layer count
718 uint32_t type = f->get_32(); //layer count
719 ERR_FAIL_COND_V((int)type != layered_type, ERR_INVALID_DATA);
720
721 uint32_t df = f->get_32(); //data format
722 mipmap_limit = int(f->get_32());
723 //reserved
724 f->get_32();
725 f->get_32();
726 f->get_32();
727
728 if (!(df & FORMAT_BIT_STREAM)) {
729 p_size_limit = 0;
730 }
731
732 images.resize(layer_count);
733
734 for (uint32_t i = 0; i < layer_count; i++) {
735 Ref<Image> image = CompressedTexture2D::load_image_from_file(f, p_size_limit);
736 ERR_FAIL_COND_V(image.is_null() || image->is_empty(), ERR_CANT_OPEN);
737 images.write[i] = image;
738 }
739
740 return OK;
741}
742
743Error CompressedTextureLayered::load(const String &p_path) {
744 Vector<Ref<Image>> images;
745
746 int mipmap_limit;
747
748 Error err = _load_data(p_path, images, mipmap_limit);
749 if (err) {
750 return err;
751 }
752
753 if (texture.is_valid()) {
754 RID new_texture = RS::get_singleton()->texture_2d_layered_create(images, RS::TextureLayeredType(layered_type));
755 RS::get_singleton()->texture_replace(texture, new_texture);
756 } else {
757 texture = RS::get_singleton()->texture_2d_layered_create(images, RS::TextureLayeredType(layered_type));
758 }
759
760 w = images[0]->get_width();
761 h = images[0]->get_height();
762 mipmaps = images[0]->has_mipmaps();
763 format = images[0]->get_format();
764 layers = images.size();
765
766 path_to_file = p_path;
767
768 if (get_path().is_empty()) {
769 //temporarily set path if no path set for resource, helps find errors
770 RenderingServer::get_singleton()->texture_set_path(texture, p_path);
771 }
772
773 notify_property_list_changed();
774 emit_changed();
775 return OK;
776}
777
778String CompressedTextureLayered::get_load_path() const {
779 return path_to_file;
780}
781
782int CompressedTextureLayered::get_width() const {
783 return w;
784}
785
786int CompressedTextureLayered::get_height() const {
787 return h;
788}
789
790int CompressedTextureLayered::get_layers() const {
791 return layers;
792}
793
794bool CompressedTextureLayered::has_mipmaps() const {
795 return mipmaps;
796}
797
798TextureLayered::LayeredType CompressedTextureLayered::get_layered_type() const {
799 return layered_type;
800}
801
802RID CompressedTextureLayered::get_rid() const {
803 if (!texture.is_valid()) {
804 texture = RS::get_singleton()->texture_2d_layered_placeholder_create(RS::TextureLayeredType(layered_type));
805 }
806 return texture;
807}
808
809Ref<Image> CompressedTextureLayered::get_layer_data(int p_layer) const {
810 if (texture.is_valid()) {
811 return RS::get_singleton()->texture_2d_layer_get(texture, p_layer);
812 } else {
813 return Ref<Image>();
814 }
815}
816
817void CompressedTextureLayered::reload_from_file() {
818 String path = get_path();
819 if (!path.is_resource_file()) {
820 return;
821 }
822
823 path = ResourceLoader::path_remap(path); //remap for translation
824 path = ResourceLoader::import_remap(path); //remap for import
825 if (!path.is_resource_file()) {
826 return;
827 }
828
829 load(path);
830}
831
832void CompressedTextureLayered::_validate_property(PropertyInfo &p_property) const {
833}
834
835void CompressedTextureLayered::_bind_methods() {
836 ClassDB::bind_method(D_METHOD("load", "path"), &CompressedTextureLayered::load);
837 ClassDB::bind_method(D_METHOD("get_load_path"), &CompressedTextureLayered::get_load_path);
838
839 ADD_PROPERTY(PropertyInfo(Variant::STRING, "load_path", PROPERTY_HINT_FILE, "*.ctex"), "load", "get_load_path");
840}
841
842CompressedTextureLayered::CompressedTextureLayered(LayeredType p_type) {
843 layered_type = p_type;
844}
845
846CompressedTextureLayered::~CompressedTextureLayered() {
847 if (texture.is_valid()) {
848 ERR_FAIL_NULL(RenderingServer::get_singleton());
849 RS::get_singleton()->free(texture);
850 }
851}
852
853/////////////////////////////////////////////////
854
855Ref<Resource> ResourceFormatLoaderCompressedTextureLayered::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
856 Ref<CompressedTextureLayered> ct;
857 if (p_path.get_extension().to_lower() == "ctexarray") {
858 Ref<CompressedTexture2DArray> c;
859 c.instantiate();
860 ct = c;
861 } else if (p_path.get_extension().to_lower() == "ccube") {
862 Ref<CompressedCubemap> c;
863 c.instantiate();
864 ct = c;
865 } else if (p_path.get_extension().to_lower() == "ccubearray") {
866 Ref<CompressedCubemapArray> c;
867 c.instantiate();
868 ct = c;
869 } else {
870 if (r_error) {
871 *r_error = ERR_FILE_UNRECOGNIZED;
872 }
873 return Ref<Resource>();
874 }
875 Error err = ct->load(p_path);
876 if (r_error) {
877 *r_error = err;
878 }
879 if (err != OK) {
880 return Ref<Resource>();
881 }
882
883 return ct;
884}
885
886void ResourceFormatLoaderCompressedTextureLayered::get_recognized_extensions(List<String> *p_extensions) const {
887 p_extensions->push_back("ctexarray");
888 p_extensions->push_back("ccube");
889 p_extensions->push_back("ccubearray");
890}
891
892bool ResourceFormatLoaderCompressedTextureLayered::handles_type(const String &p_type) const {
893 return p_type == "CompressedTexture2DArray" || p_type == "CompressedCubemap" || p_type == "CompressedCubemapArray";
894}
895
896String ResourceFormatLoaderCompressedTextureLayered::get_resource_type(const String &p_path) const {
897 if (p_path.get_extension().to_lower() == "ctexarray") {
898 return "CompressedTexture2DArray";
899 }
900 if (p_path.get_extension().to_lower() == "ccube") {
901 return "CompressedCubemap";
902 }
903 if (p_path.get_extension().to_lower() == "ccubearray") {
904 return "CompressedCubemapArray";
905 }
906 return "";
907}
908