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 | |
35 | Error 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 [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 | |
91 | void 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 | |
99 | void 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 | |
106 | void 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 | |
113 | void 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 | |
120 | CompressedTexture2D::TextureFormatRequestCallback CompressedTexture2D::request_3d_callback = nullptr; |
121 | CompressedTexture2D::TextureFormatRoughnessRequestCallback CompressedTexture2D::request_roughness_callback = nullptr; |
122 | CompressedTexture2D::TextureFormatRequestCallback CompressedTexture2D::request_normal_callback = nullptr; |
123 | |
124 | Image::Format CompressedTexture2D::get_format() const { |
125 | return format; |
126 | } |
127 | |
128 | Error 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 | |
195 | String CompressedTexture2D::get_load_path() const { |
196 | return path_to_file; |
197 | } |
198 | |
199 | int CompressedTexture2D::get_width() const { |
200 | return w; |
201 | } |
202 | |
203 | int CompressedTexture2D::get_height() const { |
204 | return h; |
205 | } |
206 | |
207 | RID CompressedTexture2D::get_rid() const { |
208 | if (!texture.is_valid()) { |
209 | texture = RS::get_singleton()->texture_2d_placeholder_create(); |
210 | } |
211 | return texture; |
212 | } |
213 | |
214 | void 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 | |
221 | void 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 | |
228 | void 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 | |
235 | bool CompressedTexture2D::has_alpha() const { |
236 | return false; |
237 | } |
238 | |
239 | Ref<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 | |
247 | bool 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 | |
281 | void 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 | |
296 | void CompressedTexture2D::_validate_property(PropertyInfo &p_property) const { |
297 | } |
298 | |
299 | Ref<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 | |
453 | void 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 | |
460 | CompressedTexture2D::CompressedTexture2D() {} |
461 | |
462 | CompressedTexture2D::~CompressedTexture2D() { |
463 | if (texture.is_valid()) { |
464 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
465 | RS::get_singleton()->free(texture); |
466 | } |
467 | } |
468 | |
469 | Ref<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 | |
483 | void ResourceFormatLoaderCompressedTexture2D::get_recognized_extensions(List<String> *p_extensions) const { |
484 | p_extensions->push_back("ctex" ); |
485 | } |
486 | |
487 | bool ResourceFormatLoaderCompressedTexture2D::handles_type(const String &p_type) const { |
488 | return p_type == "CompressedTexture2D" ; |
489 | } |
490 | |
491 | String 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 | |
498 | void 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 | |
506 | Image::Format CompressedTexture3D::get_format() const { |
507 | return format; |
508 | } |
509 | |
510 | Error 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 [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 | |
552 | Error 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 | |
589 | String CompressedTexture3D::get_load_path() const { |
590 | return path_to_file; |
591 | } |
592 | |
593 | int CompressedTexture3D::get_width() const { |
594 | return w; |
595 | } |
596 | |
597 | int CompressedTexture3D::get_height() const { |
598 | return h; |
599 | } |
600 | |
601 | int CompressedTexture3D::get_depth() const { |
602 | return d; |
603 | } |
604 | |
605 | bool CompressedTexture3D::has_mipmaps() const { |
606 | return mipmaps; |
607 | } |
608 | |
609 | RID CompressedTexture3D::get_rid() const { |
610 | if (!texture.is_valid()) { |
611 | texture = RS::get_singleton()->texture_3d_placeholder_create(); |
612 | } |
613 | return texture; |
614 | } |
615 | |
616 | Vector<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 | |
624 | void 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 | |
639 | void CompressedTexture3D::_validate_property(PropertyInfo &p_property) const { |
640 | } |
641 | |
642 | void 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 | |
649 | CompressedTexture3D::CompressedTexture3D() {} |
650 | |
651 | CompressedTexture3D::~CompressedTexture3D() { |
652 | if (texture.is_valid()) { |
653 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
654 | RS::get_singleton()->free(texture); |
655 | } |
656 | } |
657 | |
658 | Ref<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 | |
672 | void ResourceFormatLoaderCompressedTexture3D::get_recognized_extensions(List<String> *p_extensions) const { |
673 | p_extensions->push_back("ctex3d" ); |
674 | } |
675 | |
676 | bool ResourceFormatLoaderCompressedTexture3D::handles_type(const String &p_type) const { |
677 | return p_type == "CompressedTexture3D" ; |
678 | } |
679 | |
680 | String 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 | |
687 | void 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 | |
695 | Image::Format CompressedTextureLayered::get_format() const { |
696 | return format; |
697 | } |
698 | |
699 | Error 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 [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 | |
743 | Error 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 | |
778 | String CompressedTextureLayered::get_load_path() const { |
779 | return path_to_file; |
780 | } |
781 | |
782 | int CompressedTextureLayered::get_width() const { |
783 | return w; |
784 | } |
785 | |
786 | int CompressedTextureLayered::get_height() const { |
787 | return h; |
788 | } |
789 | |
790 | int CompressedTextureLayered::get_layers() const { |
791 | return layers; |
792 | } |
793 | |
794 | bool CompressedTextureLayered::has_mipmaps() const { |
795 | return mipmaps; |
796 | } |
797 | |
798 | TextureLayered::LayeredType CompressedTextureLayered::get_layered_type() const { |
799 | return layered_type; |
800 | } |
801 | |
802 | RID 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 | |
809 | Ref<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 | |
817 | void 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 | |
832 | void CompressedTextureLayered::_validate_property(PropertyInfo &p_property) const { |
833 | } |
834 | |
835 | void 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 | |
842 | CompressedTextureLayered::CompressedTextureLayered(LayeredType p_type) { |
843 | layered_type = p_type; |
844 | } |
845 | |
846 | CompressedTextureLayered::~CompressedTextureLayered() { |
847 | if (texture.is_valid()) { |
848 | ERR_FAIL_NULL(RenderingServer::get_singleton()); |
849 | RS::get_singleton()->free(texture); |
850 | } |
851 | } |
852 | |
853 | ///////////////////////////////////////////////// |
854 | |
855 | Ref<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 | |
886 | void 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 | |
892 | bool ResourceFormatLoaderCompressedTextureLayered::handles_type(const String &p_type) const { |
893 | return p_type == "CompressedTexture2DArray" || p_type == "CompressedCubemap" || p_type == "CompressedCubemapArray" ; |
894 | } |
895 | |
896 | String 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 | |