diff --git a/doc/classes/EditorResourcePreviewGenerator.xml b/doc/classes/EditorResourcePreviewGenerator.xml index 1484413536e..3dc60b3a98d 100644 --- a/doc/classes/EditorResourcePreviewGenerator.xml +++ b/doc/classes/EditorResourcePreviewGenerator.xml @@ -20,20 +20,24 @@ + Generate a preview from a given resource with the specified size. This must always be implemented. Returning an empty texture is an OK way to fail and let another generator take care. Care must be taken because this function is always called from a thread (not the main thread). + [param metadata] dictionary can modified to store file-specific metadata that can be used by the editor (like image size, sample length etc.). + Generate a preview directly from a path with the specified size. Implementing this is optional, as default code will load and call [method _generate]. Returning an empty texture is an OK way to fail and let another generator take care. Care must be taken because this function is always called from a thread (not the main thread). + [param metadata] dictionary can modified to store file-specific metadata that can be used by the editor (like image size, sample length etc.). diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index 6eef4e5a5a2..7dea1a7e016 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -35,6 +35,7 @@ #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/object/message_queue.h" +#include "core/variant/variant_utility.cpp" #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_scale.h" @@ -48,17 +49,17 @@ bool EditorResourcePreviewGenerator::handles(const String &p_type) const { ERR_FAIL_V_MSG(false, "EditorResourcePreviewGenerator::_handles needs to be overridden."); } -Ref EditorResourcePreviewGenerator::generate(const Ref &p_from, const Size2 &p_size) const { +Ref EditorResourcePreviewGenerator::generate(const Ref &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref preview; - if (GDVIRTUAL_CALL(_generate, p_from, p_size, preview)) { + if (GDVIRTUAL_CALL(_generate, p_from, p_size, p_metadata, preview)) { return preview; } ERR_FAIL_V_MSG(Ref(), "EditorResourcePreviewGenerator::_generate needs to be overridden."); } -Ref EditorResourcePreviewGenerator::generate_from_path(const String &p_path, const Size2 &p_size) const { +Ref EditorResourcePreviewGenerator::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const { Ref preview; - if (GDVIRTUAL_CALL(_generate_from_path, p_path, p_size, preview)) { + if (GDVIRTUAL_CALL(_generate_from_path, p_path, p_size, p_metadata, preview)) { return preview; } @@ -66,7 +67,7 @@ Ref EditorResourcePreviewGenerator::generate_from_path(const String & if (!res.is_valid()) { return res; } - return generate(res, p_size); + return generate(res, p_size, p_metadata); } bool EditorResourcePreviewGenerator::generate_small_preview_automatically() const { @@ -83,8 +84,8 @@ bool EditorResourcePreviewGenerator::can_generate_small_preview() const { void EditorResourcePreviewGenerator::_bind_methods() { GDVIRTUAL_BIND(_handles, "type"); - GDVIRTUAL_BIND(_generate, "resource", "size"); - GDVIRTUAL_BIND(_generate_from_path, "path", "size"); + GDVIRTUAL_BIND(_generate, "resource", "size", "metadata"); + GDVIRTUAL_BIND(_generate_from_path, "path", "size", "metadata"); GDVIRTUAL_BIND(_generate_small_preview_automatically); GDVIRTUAL_BIND(_can_generate_small_preview); } @@ -99,35 +100,31 @@ void EditorResourcePreview::_thread_func(void *ud) { erp->_thread(); } -void EditorResourcePreview::_preview_ready(const String &p_str, const Ref &p_texture, const Ref &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud) { - String path = p_str; +void EditorResourcePreview::_preview_ready(const String &p_path, int p_hash, const Ref &p_texture, const Ref &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud, const Dictionary &p_metadata) { { MutexLock lock(preview_mutex); - uint32_t hash = 0; uint64_t modified_time = 0; - if (p_str.begins_with("ID:")) { - hash = uint32_t(p_str.get_slicec(':', 2).to_int()); - path = "ID:" + p_str.get_slicec(':', 1); - } else { - modified_time = FileAccess::get_modified_time(path); + if (!p_path.begins_with("ID:")) { + modified_time = FileAccess::get_modified_time(p_path); } Item item; item.order = order++; item.preview = p_texture; item.small_preview = p_small_texture; - item.last_hash = hash; + item.last_hash = p_hash; item.modified_time = modified_time; + item.preview_metadata = p_metadata; - cache[path] = item; + cache[p_path] = item; } - MessageQueue::get_singleton()->push_call(id, p_func, path, p_texture, p_small_texture, p_ud); + MessageQueue::get_singleton()->push_call(id, p_func, p_path, p_texture, p_small_texture, p_ud); } -void EditorResourcePreview::_generate_preview(Ref &r_texture, Ref &r_small_texture, const QueueItem &p_item, const String &cache_base) { +void EditorResourcePreview::_generate_preview(Ref &r_texture, Ref &r_small_texture, const QueueItem &p_item, const String &cache_base, Dictionary &p_metadata) { String type; if (p_item.resource.is_valid()) { @@ -155,9 +152,9 @@ void EditorResourcePreview::_generate_preview(Ref &r_texture, Ref< Ref generated; if (p_item.resource.is_valid()) { - generated = preview_generators[i]->generate(p_item.resource, Vector2(thumbnail_size, thumbnail_size)); + generated = preview_generators.write[i]->generate(p_item.resource, Vector2(thumbnail_size, thumbnail_size), p_metadata); } else { - generated = preview_generators[i]->generate_from_path(p_item.path, Vector2(thumbnail_size, thumbnail_size)); + generated = preview_generators.write[i]->generate_from_path(p_item.path, Vector2(thumbnail_size, thumbnail_size), p_metadata); } r_texture = generated; @@ -165,10 +162,11 @@ void EditorResourcePreview::_generate_preview(Ref &r_texture, Ref< if (preview_generators[i]->can_generate_small_preview()) { Ref generated_small; + Dictionary d; if (p_item.resource.is_valid()) { - generated_small = preview_generators[i]->generate(p_item.resource, Vector2(small_thumbnail_size, small_thumbnail_size)); + generated_small = preview_generators.write[i]->generate(p_item.resource, Vector2(small_thumbnail_size, small_thumbnail_size), d); } else { - generated_small = preview_generators[i]->generate_from_path(p_item.path, Vector2(small_thumbnail_size, small_thumbnail_size)); + generated_small = preview_generators.write[i]->generate_from_path(p_item.path, Vector2(small_thumbnail_size, small_thumbnail_size), d); } r_small_texture = generated_small; } @@ -185,9 +183,9 @@ void EditorResourcePreview::_generate_preview(Ref &r_texture, Ref< } if (!p_item.resource.is_valid()) { - // cache the preview in case it's a resource on disk + // Cache the preview in case it's a resource on disk. if (r_texture.is_valid()) { - //wow it generated a preview... save cache + // Wow it generated a preview... save cache. bool has_small_texture = r_small_texture.is_valid(); ResourceSaver::save(r_texture, cache_base + ".png"); if (has_small_texture) { @@ -195,14 +193,16 @@ void EditorResourcePreview::_generate_preview(Ref &r_texture, Ref< } Ref f = FileAccess::open(cache_base + ".txt", FileAccess::WRITE); ERR_FAIL_COND_MSG(f.is_null(), "Cannot create file '" + cache_base + ".txt'. Check user write permissions."); - f->store_line(itos(thumbnail_size)); - f->store_line(itos(has_small_texture)); - f->store_line(itos(FileAccess::get_modified_time(p_item.path))); - f->store_line(FileAccess::get_md5(p_item.path)); + _write_preview_cache(f, thumbnail_size, has_small_texture, FileAccess::get_modified_time(p_item.path), FileAccess::get_md5(p_item.path), p_metadata); } } } +Variant EditorResourcePreview::get_preview_metadata(const String &p_path, const String &p_meta) const { + ERR_FAIL_COND_V(!cache.has(p_path), Variant()); + return cache[p_path].preview_metadata.get(p_meta, Variant()); +} + void EditorResourcePreview::_iterate() { preview_mutex.lock(); @@ -211,13 +211,8 @@ void EditorResourcePreview::_iterate() { queue.pop_front(); if (cache.has(item.path)) { - //already has it because someone loaded it, just let it know it's ready - String path = item.path; - if (item.resource.is_valid()) { - path += ":" + itos(cache[item.path].last_hash); //keep last hash (see description of what this is in condition below) - } - - _preview_ready(path, cache[item.path].preview, cache[item.path].small_preview, item.id, item.function, item.userdata); + // Already has it because someone loaded it, just let it know it's ready. + _preview_ready(item.path, cache[item.path].last_hash, cache[item.path].preview, cache[item.path].small_preview, item.id, item.function, item.userdata, cache[item.path].preview_metadata); preview_mutex.unlock(); } else { @@ -230,28 +225,31 @@ void EditorResourcePreview::_iterate() { thumbnail_size *= EDSCALE; if (item.resource.is_valid()) { - _generate_preview(texture, small_texture, item, String()); + Dictionary preview_metadata; + _generate_preview(texture, small_texture, item, String(), preview_metadata); - //adding hash to the end of path (should be ID::) because of 5 argument limit to call_deferred - _preview_ready(item.path + ":" + itos(item.resource->hash_edited_version()), texture, small_texture, item.id, item.function, item.userdata); + _preview_ready(item.path, item.resource->hash_edited_version(), texture, small_texture, item.id, item.function, item.userdata, preview_metadata); } else { + Dictionary preview_metadata; String temp_path = EditorPaths::get_singleton()->get_cache_dir(); String cache_base = ProjectSettings::get_singleton()->globalize_path(item.path).md5_text(); cache_base = temp_path.path_join("resthumb-" + cache_base); - //does not have it, try to load a cached thumbnail + // Does not have it, try to load a cached thumbnail. String file = cache_base + ".txt"; Ref f = FileAccess::open(file, FileAccess::READ); if (f.is_null()) { - // No cache found, generate - _generate_preview(texture, small_texture, item, cache_base); + // No cache found, generate. + _generate_preview(texture, small_texture, item, cache_base, preview_metadata); } else { uint64_t modtime = FileAccess::get_modified_time(item.path); - int tsize = f->get_line().to_int(); - bool has_small_texture = f->get_line().to_int(); - uint64_t last_modtime = f->get_line().to_int(); + int tsize; + bool has_small_texture; + uint64_t last_modtime; + String hash; + _read_preview_cache(f, &tsize, &has_small_texture, &last_modtime, &hash, &preview_metadata); bool cache_valid = true; @@ -266,7 +264,7 @@ void EditorResourcePreview::_iterate() { if (last_md5 != md5) { cache_valid = false; } else { - //update modified time + // Update modified time. Ref f2 = FileAccess::open(file, FileAccess::WRITE); if (f2.is_null()) { @@ -274,10 +272,7 @@ void EditorResourcePreview::_iterate() { // some proper cleanup/disabling of resource preview generation. ERR_PRINT("Cannot create file '" + file + "'. Check user write permissions."); } else { - f2->store_line(itos(thumbnail_size)); - f2->store_line(itos(has_small_texture)); - f2->store_line(itos(modtime)); - f2->store_line(md5); + _write_preview_cache(f2, thumbnail_size, has_small_texture, modtime, md5, preview_metadata); } } } else { @@ -308,10 +303,10 @@ void EditorResourcePreview::_iterate() { } if (!cache_valid) { - _generate_preview(texture, small_texture, item, cache_base); + _generate_preview(texture, small_texture, item, cache_base, preview_metadata); } } - _preview_ready(item.path, texture, small_texture, item.id, item.function, item.userdata); + _preview_ready(item.path, 0, texture, small_texture, item.id, item.function, item.userdata, preview_metadata); } } @@ -320,6 +315,22 @@ void EditorResourcePreview::_iterate() { } } +void EditorResourcePreview::_write_preview_cache(Ref p_file, int p_thumbnail_size, bool p_has_small_texture, uint64_t p_modified_time, String p_hash, const Dictionary &p_metadata) { + p_file->store_line(itos(p_thumbnail_size)); + p_file->store_line(itos(p_has_small_texture)); + p_file->store_line(itos(p_modified_time)); + p_file->store_line(p_hash); + p_file->store_line(VariantUtilityFunctions::var_to_str(p_metadata).replace("\n", " ")); +} + +void EditorResourcePreview::_read_preview_cache(Ref p_file, int *r_thumbnail_size, bool *r_has_small_texture, uint64_t *r_modified_time, String *r_hash, Dictionary *r_metadata) { + *r_thumbnail_size = p_file->get_line().to_int(); + *r_has_small_texture = p_file->get_line().to_int(); + *r_modified_time = p_file->get_line().to_int(); + *r_hash = p_file->get_line(); + *r_metadata = VariantUtilityFunctions::str_to_var(p_file->get_line()); +} + void EditorResourcePreview::_thread() { exited.clear(); while (!exit.is_set()) { diff --git a/editor/editor_resource_preview.h b/editor/editor_resource_preview.h index aae7c5b164c..c97e153c0a8 100644 --- a/editor/editor_resource_preview.h +++ b/editor/editor_resource_preview.h @@ -44,15 +44,15 @@ protected: static void _bind_methods(); GDVIRTUAL1RC(bool, _handles, String) - GDVIRTUAL2RC(Ref, _generate, Ref, Vector2i) - GDVIRTUAL2RC(Ref, _generate_from_path, String, Vector2i) + GDVIRTUAL3RC(Ref, _generate, Ref, Vector2i, Dictionary) + GDVIRTUAL3RC(Ref, _generate_from_path, String, Vector2i, Dictionary) GDVIRTUAL0RC(bool, _generate_small_preview_automatically) GDVIRTUAL0RC(bool, _can_generate_small_preview) public: virtual bool handles(const String &p_type) const; - virtual Ref generate(const Ref &p_from, const Size2 &p_size) const; - virtual Ref generate_from_path(const String &p_path, const Size2 &p_size) const; + virtual Ref generate(const Ref &p_from, const Size2 &p_size, Dictionary &p_metadata) const; + virtual Ref generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const; virtual bool generate_small_preview_automatically() const; virtual bool can_generate_small_preview() const; @@ -84,6 +84,7 @@ class EditorResourcePreview : public Node { struct Item { Ref preview; Ref small_preview; + Dictionary preview_metadata; int order = 0; uint32_t last_hash = 0; uint64_t modified_time = 0; @@ -93,13 +94,16 @@ class EditorResourcePreview : public Node { HashMap cache; - void _preview_ready(const String &p_str, const Ref &p_texture, const Ref &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud); - void _generate_preview(Ref &r_texture, Ref &r_small_texture, const QueueItem &p_item, const String &cache_base); + void _preview_ready(const String &p_path, int p_hash, const Ref &p_texture, const Ref &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud, const Dictionary &p_metadata); + void _generate_preview(Ref &r_texture, Ref &r_small_texture, const QueueItem &p_item, const String &cache_base, Dictionary &p_metadata); static void _thread_func(void *ud); void _thread(); void _iterate(); + void _write_preview_cache(Ref p_file, int p_thumbnail_size, bool p_has_small_texture, uint64_t p_modified_time, String p_hash, const Dictionary &p_metadata); + void _read_preview_cache(Ref p_file, int *r_thumbnail_size, bool *r_has_small_texture, uint64_t *r_modified_time, String *r_hash, Dictionary *r_metadata); + Vector> preview_generators; protected: @@ -112,6 +116,7 @@ public: // p_preview will be null if there was an error void queue_resource_preview(const String &p_path, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata); void queue_edited_resource_preview(const Ref &p_res, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata); + Variant get_preview_metadata(const String &p_path, const String &p_meta) const; void add_preview_generator(const Ref &p_generator); void remove_preview_generator(const Ref &p_generator); diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index 72cd1da9dd7..7c87f009a61 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -798,7 +798,7 @@ bool CurvePreviewGenerator::handles(const String &p_type) const { return p_type == "Curve"; } -Ref CurvePreviewGenerator::generate(const Ref &p_from, const Size2 &p_size) const { +Ref CurvePreviewGenerator::generate(const Ref &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref curve_ref = p_from; ERR_FAIL_COND_V_MSG(curve_ref.is_null(), Ref(), "It's not a reference to a valid Resource object."); Curve &curve = **curve_ref; diff --git a/editor/plugins/curve_editor_plugin.h b/editor/plugins/curve_editor_plugin.h index b0d666b8472..903f8d593e7 100644 --- a/editor/plugins/curve_editor_plugin.h +++ b/editor/plugins/curve_editor_plugin.h @@ -142,7 +142,7 @@ class CurvePreviewGenerator : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; - virtual Ref generate(const Ref &p_from, const Size2 &p_size) const override; + virtual Ref generate(const Ref &p_from, const Size2 &p_size, Dictionary &p_metadata) const override; }; #endif // CURVE_EDITOR_PLUGIN_H diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 523e7703c43..2b0691b36fa 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -79,7 +79,7 @@ bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const { return true; } -Ref EditorTexturePreviewPlugin::generate(const Ref &p_from, const Size2 &p_size) const { +Ref EditorTexturePreviewPlugin::generate(const Ref &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref img; Ref atex = p_from; if (atex.is_valid()) { @@ -107,6 +107,7 @@ Ref EditorTexturePreviewPlugin::generate(const Ref &p_from, if (img.is_null() || img->is_empty()) { return Ref(); } + p_metadata["dimensions"] = img->get_size(); img->clear_mipmaps(); @@ -141,7 +142,7 @@ bool EditorImagePreviewPlugin::handles(const String &p_type) const { return p_type == "Image"; } -Ref EditorImagePreviewPlugin::generate(const Ref &p_from, const Size2 &p_size) const { +Ref EditorImagePreviewPlugin::generate(const Ref &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref img = p_from; if (img.is_null() || img->is_empty()) { @@ -185,7 +186,7 @@ bool EditorBitmapPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "BitMap"); } -Ref EditorBitmapPreviewPlugin::generate(const Ref &p_from, const Size2 &p_size) const { +Ref EditorBitmapPreviewPlugin::generate(const Ref &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref bm = p_from; if (bm->get_size() == Size2()) { @@ -246,11 +247,11 @@ bool EditorPackedScenePreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "PackedScene"); } -Ref EditorPackedScenePreviewPlugin::generate(const Ref &p_from, const Size2 &p_size) const { - return generate_from_path(p_from->get_path(), p_size); +Ref EditorPackedScenePreviewPlugin::generate(const Ref &p_from, const Size2 &p_size, Dictionary &p_metadata) const { + return generate_from_path(p_from->get_path(), p_size, p_metadata); } -Ref EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size) const { +Ref EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const { String temp_path = EditorPaths::get_singleton()->get_cache_dir(); String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text(); cache_base = temp_path.path_join("resthumb-" + cache_base); @@ -298,7 +299,7 @@ bool EditorMaterialPreviewPlugin::generate_small_preview_automatically() const { return true; } -Ref EditorMaterialPreviewPlugin::generate(const Ref &p_from, const Size2 &p_size) const { +Ref EditorMaterialPreviewPlugin::generate(const Ref &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref material = p_from; ERR_FAIL_COND_V(material.is_null(), Ref()); @@ -455,7 +456,7 @@ bool EditorScriptPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "Script"); } -Ref EditorScriptPreviewPlugin::generate(const Ref &p_from, const Size2 &p_size) const { +Ref EditorScriptPreviewPlugin::generate(const Ref &p_from, const Size2 &p_size, Dictionary &p_metadata) const { Ref