From bc24d0135944dedbdbaa8f6aff8f9faee772fe3e Mon Sep 17 00:00:00 2001 From: Lyuma Date: Mon, 30 Jan 2023 23:37:55 -0800 Subject: [PATCH] gltf: Add GLTFHandleBinary::HANDLE_BINARY_EMBED_AS_UNCOMPRESSED This option allows for a safe fallback for embedded gltf textures in cases where VRAM compression is not needed. Add an is_editor_hint guard around GLTFHandleBinary::HANDLE_BINARY_EXTRACT_TEXTURES, to use EMBED_AS_UNCOMPRESSED by default at runtime. This provides an option for pixel art to be stored losslessly. Additionally, respect project importer defaults for texture import settings. Avoid writing and reimporting extracted textures identical to version on disk. --- editor/import/resource_importer_scene.cpp | 9 +- modules/gltf/doc_classes/GLTFState.xml | 6 ++ .../editor/editor_scene_importer_gltf.cpp | 6 +- modules/gltf/gltf_document.cpp | 87 +++++++++++++------ modules/gltf/gltf_state.cpp | 3 +- modules/gltf/gltf_state.h | 1 + 6 files changed, 81 insertions(+), 31 deletions(-) diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 6c6c89bcc06..6fa37690c67 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -2289,7 +2289,14 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file, const HashM ERR_FAIL_COND_V(!importer.is_valid(), nullptr); Error err = OK; - Node *scene = importer->import_scene(p_source_file, EditorSceneFormatImporter::IMPORT_ANIMATION | EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS, p_options, nullptr, &err); + HashMap options_dupe = p_options; + + // By default, the GLTF importer will extract embedded images into files on disk + // However, we do not want the advanced settings dialog to be able to write files on disk. + // To avoid this and also avoid compressing to basis every time, we are using the uncompressed option. + options_dupe["gltf/embedded_image_handling"] = 3; // Embed as Uncompressed defined in GLTFState::GLTFHandleBinary::HANDLE_BINARY_EMBED_AS_UNCOMPRESSED + + Node *scene = importer->import_scene(p_source_file, EditorSceneFormatImporter::IMPORT_ANIMATION | EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS, options_dupe, nullptr, &err); if (!scene || err != OK) { return nullptr; } diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index b8943795a0f..b322c07cec0 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -264,10 +264,16 @@ + Discards all embedded textures and uses untextured materials. + Extracts embedded textures to be reimported and compressed. Editor only. Acts as uncompressed at runtime. + Embeds textures VRAM compressed with Basis Universal into the generated scene. + + + Embeds textures compressed losslessly into the generated scene, matching old behavior. diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index 67bbf8dd152..012a144d52c 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -51,8 +51,8 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t doc.instantiate(); Ref state; state.instantiate(); - if (p_options.has("meshes/handle_gltf_embedded_images")) { - int32_t enum_option = p_options["meshes/handle_gltf_embedded_images"]; + if (p_options.has("gltf/embedded_image_handling")) { + int32_t enum_option = p_options["gltf/embedded_image_handling"]; state->set_handle_binary_image(enum_option); } Error err = doc->append_from_file(p_path, state, p_flags); @@ -87,7 +87,7 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path, List *r_options) { - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "meshes/handle_gltf_embedded_images", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); } #endif // TOOLS_ENABLED diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 5950ad33b5b..1a09b5bdccd 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -32,6 +32,7 @@ #include "extensions/gltf_spec_gloss.h" +#include "core/config/project_settings.h" #include "core/crypto/crypto_core.h" #include "core/io/config_file.h" #include "core/io/dir_access.h" @@ -3220,8 +3221,8 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_DISCARD_TEXTURES) { p_state->images.push_back(Ref()); p_state->source_images.push_back(Ref()); - continue; - } else if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EXTRACT_TEXTURES) { +#ifdef TOOLS_ENABLED + } else if (Engine::get_singleton()->is_editor_hint() && GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EXTRACT_TEXTURES) { if (p_state->base_path.is_empty()) { p_state->images.push_back(Ref()); p_state->source_images.push_back(Ref()); @@ -3230,26 +3231,56 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p p_state->images.push_back(Ref()); p_state->source_images.push_back(Ref()); } else { - String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + img->get_name() + ".png"; - Ref config; - config.instantiate(); - if (FileAccess::exists(file_path + ".import")) { - config->load(file_path + ".import"); - } - config->set_value("remap", "importer", "texture"); - config->set_value("remap", "type", "Texture2D"); - if (!config->has_section_key("params", "compress/mode")) { - config->set_value("remap", "compress/mode", 2); //user may want another compression, so leave it bes - } - if (!config->has_section_key("params", "mipmaps/generate")) { - config->set_value("params", "mipmaps/generate", true); - } Error err = OK; - err = config->save(file_path + ".import"); - ERR_FAIL_COND_V(err != OK, err); - img->save_png(file_path); - ERR_FAIL_COND_V(err != OK, err); - ResourceLoader::import(file_path); + bool must_import = false; + String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + img->get_name() + ".png"; + if (!FileAccess::exists(file_path + ".import")) { + Ref config; + config.instantiate(); + config->set_value("remap", "importer", "texture"); + config->set_value("remap", "type", "Texture2D"); + // Currently, it will likely use project defaults of Detect 3D, so textures will be reimported again. + if (!config->has_section_key("params", "mipmaps/generate")) { + config->set_value("params", "mipmaps/generate", true); + } + + if (ProjectSettings::get_singleton()->has_setting("importer_defaults/texture")) { + //use defaults if exist + Dictionary importer_defaults = GLOBAL_GET("importer_defaults/texture"); + List importer_def_keys; + importer_defaults.get_key_list(&importer_def_keys); + for (const Variant &key : importer_def_keys) { + if (!config->has_section_key("params", (String)key)) { + config->set_value("params", (String)key, importer_defaults[key]); + } + } + } + err = config->save(file_path + ".import"); + ERR_FAIL_COND_V(err != OK, err); + must_import = true; + } + Vector png_buffer = img->save_png_to_buffer(); + if (ResourceLoader::exists(file_path)) { + Ref file = FileAccess::open(file_path, FileAccess::READ, &err); + if (err == OK && file.is_valid()) { + Vector orig_png_buffer = file->get_buffer(file->get_length()); + if (png_buffer != orig_png_buffer) { + must_import = true; + } + } + } else { + must_import = true; + } + if (must_import) { + Ref file = FileAccess::open(file_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V(err != OK, err); + ERR_FAIL_COND_V(file.is_null(), FAILED); + file->store_buffer(png_buffer); + file->flush(); + file.unref(); + // ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed. + ResourceLoader::import(file_path); + } Ref saved_image = ResourceLoader::load(file_path, "Texture2D"); if (saved_image.is_valid()) { p_state->images.push_back(saved_image); @@ -3261,7 +3292,7 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p p_state->source_images.push_back(Ref()); } } - continue; +#endif } else if (GLTFState::GLTFHandleBinary(p_state->handle_binary_image) == GLTFState::GLTFHandleBinary::HANDLE_BINARY_EMBED_AS_BASISU) { Ref tex; tex.instantiate(); @@ -3271,11 +3302,15 @@ Error GLTFDocument::_parse_images(Ref p_state, const String &p_base_p tex->create_from_image(img, PortableCompressedTexture2D::COMPRESSION_MODE_BASIS_UNIVERSAL); p_state->images.push_back(tex); p_state->source_images.push_back(img); - continue; + } else { + // This handles two cases: if editor hint and HANDLE_BINARY_EXTRACT_TEXTURES; or if HANDLE_BINARY_EMBED_AS_UNCOMPRESSED + Ref tex; + tex.instantiate(); + tex->set_name(img->get_name()); + tex->set_image(img); + p_state->images.push_back(tex); + p_state->source_images.push_back(img); } - - p_state->images.push_back(Ref()); - p_state->source_images.push_back(Ref()); } print_verbose("glTF: Total images: " + itos(p_state->images.size())); diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp index b67484fc8e7..b7b7113a97b 100644 --- a/modules/gltf/gltf_state.cpp +++ b/modules/gltf/gltf_state.cpp @@ -120,11 +120,12 @@ void GLTFState::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "skeleton_to_node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_skeleton_to_node", "get_skeleton_to_node"); // RBMap> - ADD_PROPERTY(PropertyInfo(Variant::INT, "handle_binary_image", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_handle_binary_image", "get_handle_binary_image"); // enum + ADD_PROPERTY(PropertyInfo(Variant::INT, "handle_binary_image", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed As Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_EDITOR), "set_handle_binary_image", "get_handle_binary_image"); // enum BIND_CONSTANT(HANDLE_BINARY_DISCARD_TEXTURES); BIND_CONSTANT(HANDLE_BINARY_EXTRACT_TEXTURES); BIND_CONSTANT(HANDLE_BINARY_EMBED_AS_BASISU); + BIND_CONSTANT(HANDLE_BINARY_EMBED_AS_UNCOMPRESSED); } void GLTFState::add_used_extension(const String &p_extension_name, bool p_required) { diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h index 52d7949d030..b6979ca48e5 100644 --- a/modules/gltf/gltf_state.h +++ b/modules/gltf/gltf_state.h @@ -108,6 +108,7 @@ public: HANDLE_BINARY_DISCARD_TEXTURES = 0, HANDLE_BINARY_EXTRACT_TEXTURES, HANDLE_BINARY_EMBED_AS_BASISU, + HANDLE_BINARY_EMBED_AS_UNCOMPRESSED, // if this value changes from 3, ResourceImporterScene::pre_import must be changed as well. }; int32_t get_handle_binary_image() { return handle_binary_image;