From 3ccfa65245dff7358798cafd63f0675d86fc0556 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Fri, 20 Sep 2024 16:18:12 +0200 Subject: [PATCH] Geometry Nodes: support packing bakes into .blend files Previously, it was only possible to bake to disk with geometry nodes. This patch adds support for storing the baked data directly in the .blend file. By default, new bakes are stored in the .blend file now. Whether a new bake should be packed or stored on disk can be configured in two places: in the properties of the bake node and in the bake panel of the modifier. These settings don't affect existing bakes, only the next bake. To unpack or pack an individual bake, there is a new operator button next to the bake button. The icon and the label below indicate where the bake is currently stored. The label now also contains the size of the bake. To unpack or pack all bakes, the `File > External Data > Pack Resources / Unpack Resources` operators can be used. The unpack operator also has a new title that mentions the number if individual files separate from the number of bakes. This works better than just listing a number of files because a bake can consist of many files. Pull Request: https://projects.blender.org/blender/blender/pulls/124230 --- .../BKE_bake_geometry_nodes_modifier.hh | 25 +- .../BKE_bake_geometry_nodes_modifier_pack.hh | 49 +++ .../blenkernel/BKE_bake_items_paths.hh | 2 +- .../blenkernel/BKE_bake_items_serialize.hh | 51 +++ source/blender/blenkernel/BKE_packedFile.hh | 19 +- source/blender/blenkernel/CMakeLists.txt | 2 + .../intern/bake_geometry_nodes_modifier.cc | 48 +++ .../bake_geometry_nodes_modifier_pack.cc | 256 +++++++++++++ .../blenkernel/intern/bake_items_paths.cc | 13 +- .../blenkernel/intern/bake_items_serialize.cc | 64 +++- .../blender/blenkernel/intern/packedFile.cc | 75 +++- source/blender/blenlib/BLI_index_range.hh | 17 + source/blender/blenlib/BLI_sub_frame.hh | 6 + .../blenloader/intern/versioning_400.cc | 15 + .../editors/object/object_bake_simulation.cc | 350 +++++++++++++++--- .../blender/editors/object/object_intern.hh | 2 + source/blender/editors/object/object_ops.cc | 2 + .../blender/editors/space_info/CMakeLists.txt | 1 + source/blender/editors/space_info/info_ops.cc | 20 +- .../blender/makesdna/DNA_modifier_defaults.h | 4 +- source/blender/makesdna/DNA_modifier_types.h | 46 ++- .../blender/makesrna/intern/rna_modifier.cc | 63 +++- source/blender/modifiers/MOD_nodes.hh | 2 + source/blender/modifiers/intern/MOD_nodes.cc | 172 ++++++++- .../nodes/geometry/include/NOD_geo_bake.hh | 9 +- .../nodes/geometry/nodes/node_geo_bake.cc | 149 ++++++-- .../geometry/nodes/node_geo_simulation.cc | 7 +- 27 files changed, 1325 insertions(+), 144 deletions(-) create mode 100644 source/blender/blenkernel/BKE_bake_geometry_nodes_modifier_pack.hh create mode 100644 source/blender/blenkernel/intern/bake_geometry_nodes_modifier_pack.cc diff --git a/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier.hh b/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier.hh index c1f0982a9d8..a9449c63cd8 100644 --- a/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier.hh +++ b/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier.hh @@ -10,6 +10,8 @@ #include "BKE_bake_items_paths.hh" #include "BKE_bake_items_serialize.hh" +#include "DNA_modifier_types.h" + struct NodesModifierData; struct Main; struct Object; @@ -35,8 +37,11 @@ enum class CacheStatus { struct FrameCache { SubFrame frame; BakeState state; - /** Used when the baked data is loaded lazily. */ - std::optional meta_path; + /** + * Used when the baked data is loaded lazily. The meta data either has to be loaded from a file + * or from an in-memory buffer. + */ + std::optional>> meta_data_source; }; /** @@ -55,8 +60,11 @@ struct NodeBakeCache { /** All cached frames sorted by frame. */ Vector> frames; - /** Where to load blobs from disk when loading the baked data lazily. */ + /** Loads blob data from memory when the bake is packed. */ + std::unique_ptr memory_blob_reader; + /** Where to load blobs from disk when loading the baked data lazily from disk. */ std::optional blobs_dir; + /** Used to avoid reading blobs multiple times for different frames. */ std::unique_ptr blob_sharing; /** Used to avoid checking if a bake exists many times. */ @@ -98,6 +106,8 @@ struct ModifierCache { SimulationNodeCache *get_simulation_node_cache(const int id); BakeNodeCache *get_bake_node_cache(const int id); NodeBakeCache *get_node_bake_cache(const int id); + + void reset_cache(int id); }; /** @@ -106,6 +116,9 @@ struct ModifierCache { */ void scene_simulation_states_reset(Scene &scene); +std::optional get_node_bake_target(const Object &object, + const NodesModifierData &nmd, + int node_id); std::optional get_node_bake_path(const Main &bmain, const Object &object, const NodesModifierData &nmd, @@ -119,10 +132,14 @@ std::optional get_modifier_bake_path(const Main &bmain, const NodesModifierData &nmd); /** - * Get the directory that contains all baked data for the given modifier by default. + * Get default directory for baking modifier to disk. */ std::string get_default_modifier_bake_directory(const Main &bmain, const Object &object, const NodesModifierData &nmd); +std::string get_default_node_bake_directory(const Main &bmain, + const Object &object, + const NodesModifierData &nmd, + int node_id); } // namespace blender::bke::bake diff --git a/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier_pack.hh b/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier_pack.hh new file mode 100644 index 00000000000..b022262ee53 --- /dev/null +++ b/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier_pack.hh @@ -0,0 +1,49 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "DNA_modifier_types.h" + +#include "BKE_bake_items_paths.hh" +#include "BKE_packedFile.hh" + +struct ReportList; +struct Main; + +namespace blender::bke::bake { + +NodesModifierPackedBake *pack_bake_from_disk(const BakePath &bake_path, ReportList *reports); + +[[nodiscard]] bool unpack_bake_to_disk(const NodesModifierPackedBake &packed_bake, + const BakePath &bake_path, + ReportList *reports); + +enum class PackGeometryNodesBakeResult { + NoDataFound, + PackedAlready, + Success, +}; + +PackGeometryNodesBakeResult pack_geometry_nodes_bake(Main &bmain, + ReportList *reports, + Object &object, + NodesModifierData &nmd, + NodesModifierBake &bake); + +enum class UnpackGeometryNodesBakeResult { + BlendFileNotSaved, + NoPackedData, + Error, + Success, +}; + +UnpackGeometryNodesBakeResult unpack_geometry_nodes_bake(Main &bmain, + ReportList *reports, + Object &object, + NodesModifierData &nmd, + NodesModifierBake &bake, + ePF_FileStatus how); + +} // namespace blender::bke::bake diff --git a/source/blender/blenkernel/BKE_bake_items_paths.hh b/source/blender/blenkernel/BKE_bake_items_paths.hh index f93b42fabb1..798cd749fd2 100644 --- a/source/blender/blenkernel/BKE_bake_items_paths.hh +++ b/source/blender/blenkernel/BKE_bake_items_paths.hh @@ -36,7 +36,7 @@ struct BakePath { }; std::string frame_to_file_name(const SubFrame &frame); -std::optional file_name_to_frame(const StringRefNull file_name); +std::optional file_name_to_frame(const StringRef file_name); Vector find_sorted_meta_files(const StringRefNull meta_dir); diff --git a/source/blender/blenkernel/BKE_bake_items_serialize.hh b/source/blender/blenkernel/BKE_bake_items_serialize.hh index 4f8329559a3..8a2144c1060 100644 --- a/source/blender/blenkernel/BKE_bake_items_serialize.hh +++ b/source/blender/blenkernel/BKE_bake_items_serialize.hh @@ -47,6 +47,9 @@ class BlobReader { * Abstract base class for writing binary data. */ class BlobWriter { + protected: + int64_t total_written_size_ = 0; + public: /** * Write the provided binary data. @@ -62,6 +65,11 @@ class BlobWriter { */ virtual BlobSlice write_as_stream(StringRef file_extension, FunctionRef fn); + + int64_t written_size() const + { + return total_written_size_; + } }; /** @@ -184,6 +192,49 @@ class DiskBlobWriter : public BlobWriter { FunctionRef fn) override; }; +/** + * A specific #BlobWriter that keeps all data in memory. + */ +class MemoryBlobWriter : public BlobWriter { + public: + struct OutputStream { + std::unique_ptr stream; + int64_t offset = 0; + }; + + private: + std::string base_name_; + std::string blob_name_; + Map stream_by_name_; + int independent_file_count_ = 0; + + public: + MemoryBlobWriter(std::string base_name); + + BlobSlice write(const void *data, int64_t size) override; + + BlobSlice write_as_stream(StringRef file_extension, + FunctionRef fn) override; + + const Map &get_stream_by_name() const + { + return stream_by_name_; + } +}; + +/** + * A specific #BlobReader that reads data from in-memory buffers. + */ +class MemoryBlobReader : public BlobReader { + private: + Map> blob_by_name_; + + public: + void add(StringRef name, Span blob); + + [[nodiscard]] bool read(const BlobSlice &slice, void *r_data) const override; +}; + void serialize_bake(const BakeState &bake_state, BlobWriter &blob_writer, BlobWriteSharing &blob_sharing, diff --git a/source/blender/blenkernel/BKE_packedFile.hh b/source/blender/blenkernel/BKE_packedFile.hh index 2f1a3ad9643..71abd02fbe3 100644 --- a/source/blender/blenkernel/BKE_packedFile.hh +++ b/source/blender/blenkernel/BKE_packedFile.hh @@ -7,6 +7,7 @@ * \ingroup bke */ +#include "BLI_implicit_sharing.hh" #include "BLI_string_ref.hh" #define RET_OK 0 @@ -46,7 +47,8 @@ PackedFile *BKE_packedfile_duplicate(const PackedFile *pf_src); PackedFile *BKE_packedfile_new(ReportList *reports, const char *filepath_rel, const char *basepath); -PackedFile *BKE_packedfile_new_from_memory(void *mem, int memlen); +PackedFile *BKE_packedfile_new_from_memory( + const void *mem, int memlen, const blender::ImplicitSharingInfo *sharing_info = nullptr); /** * No libraries for now. @@ -108,7 +110,20 @@ void BKE_packedfile_free(PackedFile *pf); /* Info. */ -int BKE_packedfile_count_all(Main *bmain); +struct PackedFileCount { + /** Counts e.g. packed images and sounds. */ + int individual_files = 0; + /** Counts bakes that may consist of multiple files. */ + int bakes = 0; + + int total() const + { + return this->individual_files + this->bakes; + } +}; + +PackedFileCount BKE_packedfile_count_all(Main *bmain); + /** * This function compares a packed file to a 'real' file. * It returns an integer indicating if: diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 7deff006ab7..e2e9dde6da9 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -69,6 +69,7 @@ set(SRC intern/autoexec.cc intern/bake_data_block_map.cc intern/bake_geometry_nodes_modifier.cc + intern/bake_geometry_nodes_modifier_pack.cc intern/bake_items.cc intern/bake_items_paths.cc intern/bake_items_serialize.cc @@ -340,6 +341,7 @@ set(SRC BKE_bake_data_block_id.hh BKE_bake_data_block_map.hh BKE_bake_geometry_nodes_modifier.hh + BKE_bake_geometry_nodes_modifier_pack.hh BKE_bake_items.hh BKE_bake_items_paths.hh BKE_bake_items_serialize.hh diff --git a/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc b/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc index b3114e18412..71c3e760aeb 100644 --- a/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc +++ b/source/blender/blenkernel/intern/bake_geometry_nodes_modifier.cc @@ -71,6 +71,16 @@ NodeBakeCache *ModifierCache::get_node_bake_cache(const int id) return nullptr; } +void ModifierCache::reset_cache(const int id) +{ + if (SimulationNodeCache *cache = this->get_simulation_node_cache(id)) { + cache->reset(); + } + if (BakeNodeCache *cache = this->get_bake_node_cache(id)) { + cache->reset(); + } +} + void scene_simulation_states_reset(Scene &scene) { FOREACH_SCENE_OBJECT_BEGIN (&scene, ob) { @@ -97,6 +107,9 @@ std::optional get_modifier_bake_path(const Main &bmain, if (StringRef(nmd.bake_directory).is_empty()) { return std::nullopt; } + if (!BLI_path_is_rel(nmd.bake_directory)) { + return nmd.bake_directory; + } const char *base_path = ID_BLEND_PATH(&bmain, &object.id); if (StringRef(base_path).is_empty()) { return std::nullopt; @@ -107,6 +120,23 @@ std::optional get_modifier_bake_path(const Main &bmain, return absolute_bake_dir; } +std::optional get_node_bake_target(const Object & /*object*/, + const NodesModifierData &nmd, + int node_id) +{ + const NodesModifierBake *bake = nmd.find_bake(node_id); + if (!bake) { + return std::nullopt; + } + if (bake->bake_target != NODES_MODIFIER_BAKE_TARGET_INHERIT) { + return NodesModifierBakeTarget(bake->bake_target); + } + if (nmd.bake_target != NODES_MODIFIER_BAKE_TARGET_INHERIT) { + return NodesModifierBakeTarget(nmd.bake_target); + } + return NODES_MODIFIER_BAKE_TARGET_PACKED; +} + std::optional get_node_bake_path(const Main &bmain, const Object &object, const NodesModifierData &nmd, @@ -120,6 +150,9 @@ std::optional get_node_bake_path(const Main &bmain, if (StringRef(bake->directory).is_empty()) { return std::nullopt; } + if (!BLI_path_is_rel(bake->directory)) { + return BakePath::from_single_root(bake->directory); + } const char *base_path = ID_BLEND_PATH(&bmain, &object.id); if (StringRef(base_path).is_empty()) { return std::nullopt; @@ -217,4 +250,19 @@ std::string get_default_modifier_bake_directory(const Main &bmain, return dir; } +std::string get_default_node_bake_directory(const Main &bmain, + const Object &object, + const NodesModifierData &nmd, + int node_id) +{ + char dir[FILE_MAX]; + BLI_path_join(dir, + sizeof(dir), + "//", + get_blend_file_name(bmain).c_str(), + get_modifier_directory_name(object, nmd.modifier).c_str(), + std::to_string(node_id).c_str()); + return dir; +} + } // namespace blender::bke::bake diff --git a/source/blender/blenkernel/intern/bake_geometry_nodes_modifier_pack.cc b/source/blender/blenkernel/intern/bake_geometry_nodes_modifier_pack.cc new file mode 100644 index 00000000000..4244795b689 --- /dev/null +++ b/source/blender/blenkernel/intern/bake_geometry_nodes_modifier_pack.cc @@ -0,0 +1,256 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_bake_geometry_nodes_modifier.hh" +#include "BKE_bake_geometry_nodes_modifier_pack.hh" +#include "BKE_main.hh" +#include "BKE_packedFile.hh" +#include "BKE_report.hh" + +#include "DNA_modifier_types.h" + +#include "MOD_nodes.hh" + +#include "BLI_fileops.hh" +#include "BLI_path_util.h" +#include "BLI_string.h" + +#include "DEG_depsgraph.hh" + +namespace blender::bke::bake { + +static Vector pack_files_from_directory(const StringRefNull directory, + ReportList *reports) +{ + if (!BLI_is_dir(directory.c_str())) { + BKE_reportf(reports, RPT_ERROR, "%s is no directory", directory.c_str()); + return {}; + } + + direntry *dir_entries = nullptr; + const int dir_entries_num = BLI_filelist_dir_contents(directory.c_str(), &dir_entries); + BLI_SCOPED_DEFER([&]() { BLI_filelist_free(dir_entries, dir_entries_num); }); + + Vector bake_files; + for (const int i : IndexRange(dir_entries_num)) { + const direntry &dir_entry = dir_entries[i]; + const StringRefNull dir_entry_path = dir_entry.path; + const StringRefNull name = dir_entry.relname; + NodesModifierBakeFile bake_file; + bake_file.name = BLI_strdup_null(name.c_str()); + bake_file.packed_file = BKE_packedfile_new(reports, dir_entry_path.c_str(), ""); + if (bake_file.packed_file) { + bake_files.append(bake_file); + } + } + + return bake_files; +} + +NodesModifierPackedBake *pack_bake_from_disk(const BakePath &bake_path, ReportList *reports) +{ + const Vector meta_bake_files = pack_files_from_directory( + bake_path.meta_dir, reports); + if (meta_bake_files.is_empty()) { + return nullptr; + } + + const Vector blob_bake_files = pack_files_from_directory( + bake_path.blobs_dir, reports); + + NodesModifierPackedBake *packed_bake = MEM_cnew(__func__); + packed_bake->meta_files_num = meta_bake_files.size(); + packed_bake->blob_files_num = blob_bake_files.size(); + + packed_bake->meta_files = MEM_cnew_array(packed_bake->meta_files_num, + __func__); + packed_bake->blob_files = MEM_cnew_array(packed_bake->blob_files_num, + __func__); + + uninitialized_copy_n(meta_bake_files.data(), meta_bake_files.size(), packed_bake->meta_files); + uninitialized_copy_n(blob_bake_files.data(), blob_bake_files.size(), packed_bake->blob_files); + + return packed_bake; +} + +bool unpack_bake_to_disk(const NodesModifierPackedBake &packed_bake, + const BakePath &bake_path, + ReportList *reports) +{ + auto unpack_file = [&](const StringRefNull directory, const NodesModifierBakeFile &bake_file) { + char file_path[FILE_MAX]; + BLI_path_join(file_path, sizeof(file_path), directory.c_str(), bake_file.name); + if (!BLI_file_ensure_parent_dir_exists(file_path)) { + BKE_reportf(reports, RPT_ERROR, "Can't ensure directory: %s", directory.c_str()); + return false; + } + fstream fs(file_path, std::ios::out); + fs.write(static_cast(bake_file.packed_file->data), bake_file.packed_file->size); + if (fs.bad()) { + BKE_reportf(reports, RPT_ERROR, "Can't write file : %s", file_path); + return false; + } + return true; + }; + + for (const NodesModifierBakeFile &bake_file : + Span{packed_bake.meta_files, packed_bake.meta_files_num}) + { + if (!unpack_file(bake_path.meta_dir, bake_file)) { + return false; + } + } + for (const NodesModifierBakeFile &bake_file : + Span{packed_bake.blob_files, packed_bake.blob_files_num}) + { + if (!unpack_file(bake_path.blobs_dir, bake_file)) { + return false; + } + } + return true; +} + +PackGeometryNodesBakeResult pack_geometry_nodes_bake(Main &bmain, + ReportList *reports, + Object &object, + NodesModifierData &nmd, + NodesModifierBake &bake) +{ + if (bake.packed) { + return PackGeometryNodesBakeResult::PackedAlready; + } + const std::optional bake_path = get_node_bake_path(bmain, object, nmd, bake.id); + if (!bake_path) { + return PackGeometryNodesBakeResult::NoDataFound; + } + bake.packed = bake::pack_bake_from_disk(*bake_path, reports); + if (!bake.packed) { + return PackGeometryNodesBakeResult::NoDataFound; + } + nmd.runtime->cache->reset_cache(bake.id); + bake.bake_target = NODES_MODIFIER_BAKE_TARGET_PACKED; + DEG_id_tag_update(&object.id, ID_RECALC_GEOMETRY); + return PackGeometryNodesBakeResult::Success; +} + +static bool directory_is_empty(const blender::StringRefNull path) +{ + direntry *entries = nullptr; + const int entries_num = BLI_filelist_dir_contents(path.c_str(), &entries); + BLI_filelist_free(entries, entries_num); + return entries_num == 0; +} + +static bool disk_bake_exists(const blender::bke::bake::BakePath &path) +{ + return !directory_is_empty(path.meta_dir); +} + +UnpackGeometryNodesBakeResult unpack_geometry_nodes_bake(Main &bmain, + ReportList *reports, + Object &object, + NodesModifierData &nmd, + NodesModifierBake &bake, + ePF_FileStatus how) +{ + if (!bake.packed) { + return UnpackGeometryNodesBakeResult::NoPackedData; + } + if (StringRef(BKE_main_blendfile_path(&bmain)).is_empty()) { + BKE_report(reports, RPT_ERROR, "Can only unpack bake if the current .blend file is saved"); + return UnpackGeometryNodesBakeResult::BlendFileNotSaved; + } + + DEG_id_tag_update(&object.id, ID_RECALC_GEOMETRY); + + auto prepare_local_path = [&]() { + const std::string directory = bake::get_default_node_bake_directory( + bmain, object, nmd, bake.id); + bake.flag |= NODES_MODIFIER_BAKE_CUSTOM_PATH; + MEM_SAFE_FREE(bake.directory); + bake.directory = BLI_strdup(directory.c_str()); + const char *base_path = ID_BLEND_PATH(&bmain, &object.id); + char absolute_dir[FILE_MAX]; + STRNCPY(absolute_dir, directory.c_str()); + BLI_path_abs(absolute_dir, base_path); + return bake::BakePath::from_single_root(absolute_dir); + }; + auto prepare_original_path = [&]() { + if (const std::optional bake_path = bake::get_node_bake_path( + bmain, object, nmd, bake.id)) + { + return *bake_path; + } + return prepare_local_path(); + }; + auto delete_bake_on_disk = [&](const bake::BakePath &bake_path) { + BLI_delete(bake_path.meta_dir.c_str(), true, true); + BLI_delete(bake_path.blobs_dir.c_str(), true, true); + }; + auto free_packed_bake = [&]() { + blender::nodes_modifier_packed_bake_free(bake.packed); + bake.packed = nullptr; + nmd.runtime->cache->reset_cache(bake.id); + }; + auto finalize_on_success = [&]() { + bake.bake_target = NODES_MODIFIER_BAKE_TARGET_DISK; + return UnpackGeometryNodesBakeResult::Success; + }; + + switch (how) { + case PF_USE_ORIGINAL: { + const bake::BakePath bake_path = prepare_original_path(); + if (!disk_bake_exists(bake_path)) { + delete_bake_on_disk(bake_path); + if (!bake::unpack_bake_to_disk(*bake.packed, bake_path, reports)) { + return UnpackGeometryNodesBakeResult::Error; + } + } + free_packed_bake(); + return finalize_on_success(); + } + case PF_WRITE_ORIGINAL: { + const bake::BakePath bake_path = prepare_original_path(); + delete_bake_on_disk(bake_path); + if (!bake::unpack_bake_to_disk(*bake.packed, bake_path, reports)) { + return UnpackGeometryNodesBakeResult::Error; + } + free_packed_bake(); + return finalize_on_success(); + } + case PF_USE_LOCAL: { + const bake::BakePath bake_path = prepare_local_path(); + if (!disk_bake_exists(bake_path)) { + delete_bake_on_disk(bake_path); + if (!bake::unpack_bake_to_disk(*bake.packed, bake_path, reports)) { + return UnpackGeometryNodesBakeResult::Error; + } + } + free_packed_bake(); + return finalize_on_success(); + } + case PF_WRITE_LOCAL: { + const bake::BakePath bake_path = prepare_local_path(); + delete_bake_on_disk(bake_path); + if (!bake::unpack_bake_to_disk(*bake.packed, bake_path, reports)) { + return UnpackGeometryNodesBakeResult::Error; + } + free_packed_bake(); + return finalize_on_success(); + } + case PF_KEEP: { + return finalize_on_success(); + } + case PF_REMOVE: { + free_packed_bake(); + return finalize_on_success(); + } + default: { + break; + } + } + return UnpackGeometryNodesBakeResult::Error; +} + +} // namespace blender::bke::bake diff --git a/source/blender/blenkernel/intern/bake_items_paths.cc b/source/blender/blenkernel/intern/bake_items_paths.cc index 4f7d38dd752..27b74d578d7 100644 --- a/source/blender/blenkernel/intern/bake_items_paths.cc +++ b/source/blender/blenkernel/intern/bake_items_paths.cc @@ -19,13 +19,18 @@ std::string frame_to_file_name(const SubFrame &frame) return file_name_c; } -std::optional file_name_to_frame(const StringRefNull file_name) +std::optional file_name_to_frame(const StringRef file_name) { char modified_file_name[FILE_MAX]; - STRNCPY(modified_file_name, file_name.c_str()); + file_name.copy(modified_file_name); BLI_string_replace_char(modified_file_name, '_', '.'); - const SubFrame frame = std::stof(modified_file_name); - return frame; + try { + const SubFrame frame = std::stof(modified_file_name); + return frame; + } + catch (...) { + return std::nullopt; + } } Vector find_sorted_meta_files(const StringRefNull meta_dir) diff --git a/source/blender/blenkernel/intern/bake_items_serialize.cc b/source/blender/blenkernel/intern/bake_items_serialize.cc index ad44c189e6f..119b127306c 100644 --- a/source/blender/blenkernel/intern/bake_items_serialize.cc +++ b/source/blender/blenkernel/intern/bake_items_serialize.cc @@ -19,6 +19,7 @@ #include "BLI_path_util.h" #include "DNA_material_types.h" +#include "DNA_modifier_types.h" #include "DNA_volume_types.h" #include "RNA_access.hh" @@ -126,16 +127,24 @@ BlobSlice DiskBlobWriter::write(const void *data, const int64_t size) const int64_t old_offset = current_offset_; blob_stream_.write(static_cast(data), size); current_offset_ += size; + total_written_size_ += size; return {blob_name_, {old_offset, size}}; } +static std::string make_independent_file_name(const StringRef base_name, + const int file_index, + const StringRef extension) +{ + return fmt::format("{}_file_{}{}", base_name, file_index, extension); +} + BlobSlice DiskBlobWriter::write_as_stream(const StringRef file_extension, const FunctionRef fn) { BLI_assert(file_extension.startswith(".")); independent_file_count_++; - const std::string file_name = fmt::format( - "{}_file_{}{}", base_name_, independent_file_count_, file_extension); + const std::string file_name = make_independent_file_name( + base_name_, independent_file_count_, file_extension); char path[FILE_MAX]; BLI_path_join(path, sizeof(path), blob_dir_.c_str(), file_name.c_str()); @@ -143,9 +152,60 @@ BlobSlice DiskBlobWriter::write_as_stream(const StringRef file_extension, std::fstream stream{path, std::ios::out | std::ios::binary}; fn(stream); const int64_t written_bytes_num = stream.tellg(); + total_written_size_ += written_bytes_num; return {file_name, {0, written_bytes_num}}; } +void MemoryBlobReader::add(const StringRef name, const Span blob) +{ + blob_by_name_.add(name, blob); +} + +bool MemoryBlobReader::read(const BlobSlice &slice, void *r_data) const +{ + if (slice.range.is_empty()) { + return true; + } + const Span blob_data = blob_by_name_.lookup_default(slice.name, {}); + if (!blob_data.index_range().contains(slice.range)) { + return false; + } + const void *copy_src = blob_data.slice(slice.range).data(); + memcpy(r_data, copy_src, slice.range.size()); + return true; +} + +MemoryBlobWriter::MemoryBlobWriter(std::string base_name) : base_name_(std::move(base_name)) +{ + blob_name_ = base_name_ + ".blob"; + stream_by_name_.add(blob_name_, {std::make_unique(std::ios::binary)}); +} + +BlobSlice MemoryBlobWriter::write(const void *data, int64_t size) +{ + OutputStream &stream = stream_by_name_.lookup(blob_name_); + const int64_t old_offset = stream.offset; + stream.stream->write(static_cast(data), size); + stream.offset += size; + total_written_size_ += size; + return {blob_name_, IndexRange::from_begin_size(old_offset, size)}; +} + +BlobSlice MemoryBlobWriter::write_as_stream(const StringRef file_extension, + const FunctionRef fn) +{ + BLI_assert(file_extension.startswith(".")); + independent_file_count_++; + const std::string name = make_independent_file_name( + base_name_, independent_file_count_, file_extension); + OutputStream stream{std::make_unique(std::ios::binary)}; + fn(*stream.stream); + const int64_t size = stream.stream->tellp(); + stream_by_name_.add_new(name, std::move(stream)); + total_written_size_ += size; + return {base_name_, IndexRange(size)}; +} + BlobWriteSharing::~BlobWriteSharing() { for (const ImplicitSharingInfo *sharing_info : stored_by_runtime_.keys()) { diff --git a/source/blender/blenkernel/intern/packedFile.cc b/source/blender/blenkernel/intern/packedFile.cc index ed5f58ef179..f03f2491ac2 100644 --- a/source/blender/blenkernel/intern/packedFile.cc +++ b/source/blender/blenkernel/intern/packedFile.cc @@ -20,6 +20,7 @@ #include "DNA_ID.h" #include "DNA_image_types.h" +#include "DNA_modifier_types.h" #include "DNA_packedFile_types.h" #include "DNA_sound_types.h" #include "DNA_vfont_types.h" @@ -28,6 +29,8 @@ #include "BLI_blenlib.h" #include "BLI_utildefines.h" +#include "BKE_bake_geometry_nodes_modifier.hh" +#include "BKE_bake_geometry_nodes_modifier_pack.hh" #include "BKE_image.h" #include "BKE_image_format.h" #include "BKE_main.hh" @@ -37,9 +40,13 @@ #include "BKE_vfont.hh" #include "BKE_volume.hh" +#include "DEG_depsgraph.hh" + #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" +#include "MOD_nodes.hh" + #include "BLO_read_write.hh" #include "CLG_log.h" @@ -108,26 +115,27 @@ int BKE_packedfile_read(PackedFile *pf, void *data, int size) return size; } -int BKE_packedfile_count_all(Main *bmain) +PackedFileCount BKE_packedfile_count_all(Main *bmain) { Image *ima; VFont *vf; bSound *sound; Volume *volume; - int count = 0; + + PackedFileCount count; /* let's check if there are packed files... */ for (ima = static_cast(bmain->images.first); ima; ima = static_cast(ima->id.next)) { if (BKE_image_has_packedfile(ima) && !ID_IS_LINKED(ima)) { - count++; + count.individual_files++; } } for (vf = static_cast(bmain->fonts.first); vf; vf = static_cast(vf->id.next)) { if (vf->packedfile && !ID_IS_LINKED(vf)) { - count++; + count.individual_files++; } } @@ -135,7 +143,7 @@ int BKE_packedfile_count_all(Main *bmain) sound = static_cast(sound->id.next)) { if (sound->packedfile && !ID_IS_LINKED(sound)) { - count++; + count.individual_files++; } } @@ -143,7 +151,23 @@ int BKE_packedfile_count_all(Main *bmain) volume = static_cast(volume->id.next)) { if (volume->packedfile && !ID_IS_LINKED(volume)) { - count++; + count.individual_files++; + } + } + + LISTBASE_FOREACH (Object *, object, &bmain->objects) { + if (ID_IS_LINKED(object)) { + continue; + } + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = reinterpret_cast(md); + for (const NodesModifierBake &bake : blender::Span{nmd->bakes, nmd->bakes_num}) { + if (bake.packed) { + count.bakes++; + } + } + } } } @@ -177,14 +201,20 @@ PackedFile *BKE_packedfile_duplicate(const PackedFile *pf_src) return pf_dst; } -PackedFile *BKE_packedfile_new_from_memory(void *mem, int memlen) +PackedFile *BKE_packedfile_new_from_memory(const void *mem, + int memlen, + const blender::ImplicitSharingInfo *sharing_info) { BLI_assert(mem != nullptr); + if (!sharing_info) { + /* Assume we are the only owner of that memory currently. */ + sharing_info = blender::implicit_sharing::info_for_mem_free(const_cast(mem)); + } PackedFile *pf = static_cast(MEM_callocN(sizeof(*pf), "PackedFile")); pf->data = mem; pf->size = memlen; - pf->sharing_info = blender::implicit_sharing::info_for_mem_free(mem); + pf->sharing_info = sharing_info; return pf; } @@ -299,6 +329,20 @@ void BKE_packedfile_pack_all(Main *bmain, ReportList *reports, bool verbose) } } + LISTBASE_FOREACH (Object *, object, &bmain->objects) { + if (ID_IS_LINKED(object)) { + continue; + } + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = reinterpret_cast(md); + for (NodesModifierBake &bake : blender::MutableSpan{nmd->bakes, nmd->bakes_num}) { + blender::bke::bake::pack_geometry_nodes_bake(*bmain, reports, *object, *nmd, bake); + } + } + } + } + if (tot > 0) { BKE_reportf(reports, RPT_INFO, "Packed %d file(s)", tot); } @@ -816,6 +860,21 @@ void BKE_packedfile_unpack_all(Main *bmain, ReportList *reports, enum ePF_FileSt BKE_packedfile_unpack_volume(bmain, reports, volume, how); } } + + LISTBASE_FOREACH (Object *, object, &bmain->objects) { + if (ID_IS_LINKED(object)) { + continue; + } + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type == eModifierType_Nodes) { + NodesModifierData *nmd = reinterpret_cast(md); + for (NodesModifierBake &bake : blender::MutableSpan{nmd->bakes, nmd->bakes_num}) { + blender::bke::bake::unpack_geometry_nodes_bake( + *bmain, reports, *object, *nmd, bake, how); + } + } + } + } } bool BKE_packedfile_id_check(const ID *id) diff --git a/source/blender/blenlib/BLI_index_range.hh b/source/blender/blenlib/BLI_index_range.hh index 933be715f75..bd9e03071ec 100644 --- a/source/blender/blenlib/BLI_index_range.hh +++ b/source/blender/blenlib/BLI_index_range.hh @@ -249,6 +249,23 @@ class IndexRange { return value >= start_ && value < start_ + size_; } + /** + * Returns true when all indices in the given range are also in the current range. + */ + constexpr bool contains(const IndexRange range) const + { + if (range.is_empty()) { + return true; + } + if (range.start_ < start_) { + return false; + } + if (range.start_ + range.size_ > start_ + size_) { + return false; + } + return true; + } + /** * Returns a new range, that contains a sub-interval of the current one. */ diff --git a/source/blender/blenlib/BLI_sub_frame.hh b/source/blender/blenlib/BLI_sub_frame.hh index 6dc399b1e89..00efbaf7977 100644 --- a/source/blender/blenlib/BLI_sub_frame.hh +++ b/source/blender/blenlib/BLI_sub_frame.hh @@ -7,6 +7,7 @@ #include #include "BLI_assert.h" +#include "BLI_hash.hh" #include "BLI_math_base.h" #include "BLI_struct_equality_utils.hh" @@ -59,6 +60,11 @@ struct SubFrame { return {INT32_MAX, std::nexttowardf(1.0f, 0.0)}; } + uint64_t hash() const + { + return get_default_hash(frame_, subframe_); + } + BLI_STRUCT_EQUALITY_OPERATORS_2(SubFrame, frame_, subframe_) friend bool operator<(const SubFrame &a, const SubFrame &b) diff --git a/source/blender/blenloader/intern/versioning_400.cc b/source/blender/blenloader/intern/versioning_400.cc index 4810ee947e3..bdc2cc68454 100644 --- a/source/blender/blenloader/intern/versioning_400.cc +++ b/source/blender/blenloader/intern/versioning_400.cc @@ -4625,6 +4625,21 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain) add_bevel_modifier_attribute_name_defaults(*bmain); } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 403, 23)) { + LISTBASE_FOREACH (Object *, object, &bmain->objects) { + LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) { + if (md->type != eModifierType_Nodes) { + continue; + } + NodesModifierData &nmd = *reinterpret_cast(md); + if (nmd.bake_target == NODES_MODIFIER_BAKE_TARGET_INHERIT) { + /* Use disk target for existing modifiers to avoid changing behavior. */ + nmd.bake_target = NODES_MODIFIER_BAKE_TARGET_DISK; + } + } + } + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/editors/object/object_bake_simulation.cc b/source/blender/editors/object/object_bake_simulation.cc index 46239fbe9b9..08c6fbc1cc6 100644 --- a/source/blender/editors/object/object_bake_simulation.cc +++ b/source/blender/editors/object/object_bake_simulation.cc @@ -25,12 +25,14 @@ #include "DNA_windowmanager_types.h" #include "BKE_bake_geometry_nodes_modifier.hh" +#include "BKE_bake_geometry_nodes_modifier_pack.hh" #include "BKE_context.hh" #include "BKE_global.hh" #include "BKE_lib_id.hh" #include "BKE_main.hh" #include "BKE_modifier.hh" #include "BKE_node_runtime.hh" +#include "BKE_packedFile.hh" #include "BKE_report.hh" #include "BKE_scene.hh" @@ -196,12 +198,6 @@ static bool bake_simulation_poll(bContext *C) if (!ED_operator_object_active(C)) { return false; } - Main *bmain = CTX_data_main(C); - const StringRefNull path = BKE_main_blendfile_path(bmain); - if (path.is_empty()) { - CTX_wm_operator_poll_msg_set(C, "File must be saved before baking"); - return false; - } Object *ob = context_active_object(C); const bool use_frame_cache = ob->flag & OB_FLAG_USE_SIMULATION_CACHE; if (!use_frame_cache) { @@ -217,7 +213,8 @@ struct NodeBakeRequest { int bake_id; int node_type; - bake::BakePath path; + /** Store bake in this location if available, otherwise pack the baked data. */ + std::optional path; int frame_start; int frame_end; std::unique_ptr blob_sharing; @@ -272,6 +269,19 @@ static void bake_geometry_nodes_startjob(void *customdata, wmJobWorkerStatus *wo const float progress_per_frame = frame_step_size / frames_to_bake; const int old_frame = job.scene->r.cfra; + struct MemoryBakeFile { + std::string name; + std::string data; + }; + + struct PackedBake { + Vector meta_files; + Vector blob_files; + }; + + Map packed_data_by_bake; + Map size_by_bake; + for (float frame_f = global_bake_start_frame; frame_f <= global_bake_end_frame; frame_f += frame_step_size) { @@ -307,23 +317,99 @@ static void bake_geometry_nodes_startjob(void *customdata, wmJobWorkerStatus *wo continue; } - const bake::BakePath path = request.path; + int64_t &written_size = size_by_bake.lookup_or_add(&request, 0); - char meta_path[FILE_MAX]; - BLI_path_join(meta_path, - sizeof(meta_path), - path.meta_dir.c_str(), - (frame_file_name + ".json").c_str()); - BLI_file_ensure_parent_dir_exists(meta_path); - bake::DiskBlobWriter blob_writer{path.blobs_dir, frame_file_name}; - fstream meta_file{meta_path, std::ios::out}; - bake::serialize_bake(frame_cache.state, blob_writer, *request.blob_sharing, meta_file); + if (request.path.has_value()) { + char meta_path[FILE_MAX]; + BLI_path_join(meta_path, + sizeof(meta_path), + request.path->meta_dir.c_str(), + (frame_file_name + ".json").c_str()); + BLI_file_ensure_parent_dir_exists(meta_path); + bake::DiskBlobWriter blob_writer{request.path->blobs_dir, frame_file_name}; + fstream meta_file{meta_path, std::ios::out}; + bake::serialize_bake(frame_cache.state, blob_writer, *request.blob_sharing, meta_file); + written_size += blob_writer.written_size(); + written_size += meta_file.tellp(); + } + else { + PackedBake &packed_data = packed_data_by_bake.lookup_or_add_default(&request); + + bake::MemoryBlobWriter blob_writer{frame_file_name}; + std::ostringstream meta_file{std::ios::binary}; + bake::serialize_bake(frame_cache.state, blob_writer, *request.blob_sharing, meta_file); + + packed_data.meta_files.append({frame_file_name + ".json", meta_file.str()}); + const Map &blob_stream_by_name = + blob_writer.get_stream_by_name(); + for (auto &&item : blob_stream_by_name.items()) { + std::string data = item.value.stream->str(); + if (data.empty()) { + continue; + } + packed_data.blob_files.append({item.key, std::move(data)}); + } + written_size += blob_writer.written_size(); + written_size += meta_file.tellp(); + } } worker_status->progress += progress_per_frame; worker_status->do_update = true; } + /* Update bake sizes. */ + for (NodeBakeRequest &request : job.bake_requests) { + NodesModifierBake *bake = request.nmd->find_bake(request.bake_id); + bake->bake_size = size_by_bake.lookup_default(&request, 0); + } + + /* Store gathered data as packed data. */ + for (NodeBakeRequest &request : job.bake_requests) { + NodesModifierBake *bake = request.nmd->find_bake(request.bake_id); + + PackedBake *packed_data = packed_data_by_bake.lookup_ptr(&request); + if (!packed_data) { + continue; + } + + NodesModifierPackedBake *packed_bake = MEM_cnew(__func__); + + packed_bake->meta_files_num = packed_data->meta_files.size(); + packed_bake->blob_files_num = packed_data->blob_files.size(); + + packed_bake->meta_files = MEM_cnew_array(packed_bake->meta_files_num, + __func__); + packed_bake->blob_files = MEM_cnew_array(packed_bake->blob_files_num, + __func__); + + auto transfer_to_bake = + [&](NodesModifierBakeFile *bake_files, MemoryBakeFile *memory_bake_files, const int num) { + for (const int i : IndexRange(num)) { + NodesModifierBakeFile &bake_file = bake_files[i]; + MemoryBakeFile &memory = memory_bake_files[i]; + bake_file.name = BLI_strdup_null(memory.name.c_str()); + const int64_t data_size = memory.data.size(); + if (data_size == 0) { + continue; + } + const auto *sharing_info = new blender::ImplicitSharedValue( + std::move(memory.data)); + const void *data = sharing_info->data.data(); + bake_file.packed_file = BKE_packedfile_new_from_memory(data, data_size, sharing_info); + } + }; + + transfer_to_bake( + packed_bake->meta_files, packed_data->meta_files.data(), packed_bake->meta_files_num); + transfer_to_bake( + packed_bake->blob_files, packed_data->blob_files.data(), packed_bake->blob_files_num); + + /* Should have been freed before. */ + BLI_assert(bake->packed == nullptr); + bake->packed = packed_bake; + } + /* Tag simulations as being baked. */ for (NodeBakeRequest &request : job.bake_requests) { if (request.node_type != GEO_NODE_SIMULATION_OUTPUT) { @@ -411,6 +497,11 @@ static void try_delete_bake( } clear_data_block_references(*bake); + if (bake->packed) { + nodes_modifier_packed_bake_free(bake->packed); + bake->packed = nullptr; + } + const std::optional bake_path = bake::get_node_bake_path( *bmain, object, nmd, bake_id); if (!bake_path) { @@ -534,16 +625,14 @@ static Vector collect_simulations_to_bake(Main &bmain, request.bake_id = id; request.node_type = node->type; request.blob_sharing = std::make_unique(); - std::optional path = bake::get_node_bake_path(bmain, *object, *nmd, id); - if (!path) { - continue; + if (bake::get_node_bake_target(*object, *nmd, id) == NODES_MODIFIER_BAKE_TARGET_DISK) { + request.path = bake::get_node_bake_path(bmain, *object, *nmd, id); } std::optional frame_range = bake::get_node_bake_frame_range( scene, *object, *nmd, id); if (!frame_range) { continue; } - request.path = std::move(*path); request.frame_start = frame_range->first(); request.frame_end = frame_range->last(); @@ -620,6 +709,44 @@ static bool bake_directory_has_data(const StringRefNull absolute_bake_dir) return true; } +static bool may_have_disk_bake(const NodesModifierData &nmd) +{ + if (nmd.bake_target == NODES_MODIFIER_BAKE_TARGET_DISK) { + return true; + } + for (const NodesModifierBake &bake : Span{nmd.bakes, nmd.bakes_num}) { + if (bake.bake_target == NODES_MODIFIER_BAKE_TARGET_DISK) { + return true; + } + } + return false; +} + +static void initialize_modifier_bake_directory_if_necessary(bContext *C, + Object &object, + NodesModifierData &nmd, + wmOperator *op) +{ + const bool bake_directory_set = !StringRef(nmd.bake_directory).is_empty(); + if (bake_directory_set) { + return; + } + if (!may_have_disk_bake(nmd)) { + return; + } + + Main *bmain = CTX_data_main(C); + + BKE_reportf(op->reports, + RPT_INFO, + "Bake directory of object %s, modifier %s is empty, setting default path", + object.id.name + 2, + nmd.modifier.name); + + nmd.bake_directory = BLI_strdup( + bake::get_default_modifier_bake_directory(*bmain, object, nmd).c_str()); +} + static void bake_simulation_validate_paths(bContext *C, wmOperator *op, const Span objects) @@ -635,18 +762,8 @@ static void bake_simulation_validate_paths(bContext *C, if (md->type != eModifierType_Nodes) { continue; } - NodesModifierData *nmd = reinterpret_cast(md); - if (StringRef(nmd->bake_directory).is_empty()) { - BKE_reportf(op->reports, - RPT_INFO, - "Bake directory of object %s, modifier %s is empty, setting default path", - object->id.name + 2, - md->name); - - nmd->bake_directory = BLI_strdup( - bake::get_default_modifier_bake_directory(*bmain, *object, *nmd).c_str()); - } + initialize_modifier_bake_directory_if_necessary(C, *object, *nmd, op); } } } @@ -702,7 +819,7 @@ static int bake_simulation_invoke(bContext *C, wmOperator *op, const wmEvent * / } } - /* Set empty paths to default. */ + /* Set empty paths to default if necessary. */ bake_simulation_validate_paths(C, op, objects); PathUsersMap path_users = bake_simulation_get_path_users(C, objects); @@ -815,10 +932,7 @@ static Vector bake_single_node_gather_bake_request(bContext *C, return {}; } - if (StringRef(nmd.bake_directory).is_empty()) { - const std::string directory = bake::get_default_modifier_bake_directory(*bmain, *object, nmd); - nmd.bake_directory = BLI_strdup(directory.c_str()); - } + initialize_modifier_bake_directory_if_necessary(C, *object, nmd, op); const int bake_id = RNA_int_get(op->ptr, "bake_id"); const bNode *node = nmd.node_group->find_nested_node(bake_id); @@ -840,13 +954,14 @@ static Vector bake_single_node_gather_bake_request(bContext *C, if (!bake) { return {}; } - const std::optional bake_path = bake::get_node_bake_path( - *bmain, *object, nmd, bake_id); - if (!bake_path.has_value()) { - BKE_report(op->reports, RPT_ERROR, "Cannot determine bake location on disk"); - return {}; + if (bake::get_node_bake_target(*object, nmd, bake_id) == NODES_MODIFIER_BAKE_TARGET_DISK) { + request.path = bake::get_node_bake_path(*bmain, *object, nmd, bake_id); + if (!request.path) { + BKE_report(op->reports, + RPT_INFO, + "Can't determine bake location on disk. Falling back to packed bake."); + } } - request.path = std::move(*bake_path); if (node->type == GEO_NODE_BAKE && bake->bake_mode == NODES_MODIFIER_BAKE_MODE_STILL) { const int current_frame = scene->r.cfra; @@ -929,25 +1044,101 @@ static int delete_single_bake_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } -static bool bake_poll(bContext *C) +static int pack_single_bake_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); - if (BKE_main_blendfile_path(bmain)[0] == '\0') { - /* Saving the .blend file is not technically necessary in all cases but only when the bake path - * depends on the .blend file path (which is the case by default). */ - CTX_wm_operator_poll_msg_set(C, "File must be saved before baking"); - return false; + Object *object = reinterpret_cast( + WM_operator_properties_id_lookup_from_name_or_session_uid(bmain, op->ptr, ID_OB)); + if (object == nullptr) { + return OPERATOR_CANCELLED; } - return true; + char *modifier_name = RNA_string_get_alloc(op->ptr, "modifier_name", nullptr, 0, nullptr); + if (modifier_name == nullptr) { + return OPERATOR_CANCELLED; + } + BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(modifier_name); }); + + ModifierData *md = BKE_modifiers_findby_name(object, modifier_name); + if (md == nullptr) { + return OPERATOR_CANCELLED; + } + NodesModifierData &nmd = *reinterpret_cast(md); + const int bake_id = RNA_int_get(op->ptr, "bake_id"); + + const std::optional bake_path = bake::get_node_bake_path( + *bmain, *object, nmd, bake_id); + if (!bake_path) { + return OPERATOR_CANCELLED; + } + NodesModifierBake *bake = nmd.find_bake(bake_id); + if (!bake) { + return OPERATOR_CANCELLED; + } + + bake::pack_geometry_nodes_bake(*bmain, op->reports, *object, nmd, *bake); + + WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, nullptr); + WM_main_add_notifier(NC_NODE, nullptr); + return OPERATOR_FINISHED; } -static bool bake_delete_poll(bContext *C) +static int unpack_single_bake_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) +{ + uiPopupMenu *pup; + uiLayout *layout; + + pup = UI_popup_menu_begin(C, IFACE_("Unpack"), ICON_NONE); + layout = UI_popup_menu_layout(pup); + + uiLayoutSetOperatorContext(layout, WM_OP_EXEC_DEFAULT); + uiItemsFullEnumO(layout, + op->type->idname, + "method", + static_cast(op->ptr->data), + WM_OP_EXEC_REGION_WIN, + UI_ITEM_NONE); + + UI_popup_menu_end(C, pup); + + return OPERATOR_INTERFACE; +} + +static int unpack_single_bake_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); - if (BKE_main_blendfile_path(bmain)[0] == '\0') { - return false; + Object *object = reinterpret_cast( + WM_operator_properties_id_lookup_from_name_or_session_uid(bmain, op->ptr, ID_OB)); + if (object == nullptr) { + return OPERATOR_CANCELLED; } - return true; + char *modifier_name = RNA_string_get_alloc(op->ptr, "modifier_name", nullptr, 0, nullptr); + if (modifier_name == nullptr) { + return OPERATOR_CANCELLED; + } + BLI_SCOPED_DEFER([&]() { MEM_SAFE_FREE(modifier_name); }); + + ModifierData *md = BKE_modifiers_findby_name(object, modifier_name); + if (md == nullptr) { + return OPERATOR_CANCELLED; + } + NodesModifierData &nmd = *reinterpret_cast(md); + const int bake_id = RNA_int_get(op->ptr, "bake_id"); + NodesModifierBake *bake = nmd.find_bake(bake_id); + if (!bake) { + return OPERATOR_CANCELLED; + } + + const ePF_FileStatus method = ePF_FileStatus(RNA_enum_get(op->ptr, "method")); + + bake::UnpackGeometryNodesBakeResult result = bake::unpack_geometry_nodes_bake( + *bmain, op->reports, *object, nmd, *bake, method); + if (result != bake::UnpackGeometryNodesBakeResult::Success) { + return OPERATOR_CANCELLED; + } + + WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, nullptr); + WM_main_add_notifier(NC_NODE, nullptr); + return OPERATOR_FINISHED; } void OBJECT_OT_simulation_nodes_cache_calculate_to_frame(wmOperatorType *ot) @@ -1014,7 +1205,6 @@ void OBJECT_OT_geometry_node_bake_single(wmOperatorType *ot) ot->description = "Bake a single bake node or simulation"; ot->idname = "OBJECT_OT_geometry_node_bake_single"; - ot->poll = bake_poll; ot->invoke = bake_single_node_invoke; ot->exec = bake_single_node_exec; ot->modal = bake_single_node_modal; @@ -1028,10 +1218,58 @@ void OBJECT_OT_geometry_node_bake_delete_single(wmOperatorType *ot) ot->description = "Delete baked data of a single bake node or simulation"; ot->idname = "OBJECT_OT_geometry_node_bake_delete_single"; - ot->poll = bake_delete_poll; ot->exec = delete_single_bake_exec; single_bake_operator_props(ot); } +void OBJECT_OT_geometry_node_bake_pack_single(wmOperatorType *ot) +{ + ot->name = "Pack Geometry Node Bake"; + ot->description = "Pack baked data from disk into the .blend file"; + ot->idname = "OBJECT_OT_geometry_node_bake_pack_single"; + + ot->exec = pack_single_bake_exec; + + single_bake_operator_props(ot); +} + +void OBJECT_OT_geometry_node_bake_unpack_single(wmOperatorType *ot) +{ + ot->name = "Unpack Geometry Node Bake"; + ot->description = "Unpack baked data from the .blend file to disk"; + ot->idname = "OBJECT_OT_geometry_node_bake_unpack_single"; + + ot->exec = unpack_single_bake_exec; + ot->invoke = unpack_single_bake_invoke; + + single_bake_operator_props(ot); + + static const EnumPropertyItem method_items[] = { + {PF_USE_LOCAL, + "USE_LOCAL", + 0, + "Use bake from current directory (create when necessary)", + ""}, + {PF_WRITE_LOCAL, + "WRITE_LOCAL", + 0, + "Write bake to current directory (overwrite existing bake)", + ""}, + {PF_USE_ORIGINAL, + "USE_ORIGINAL", + 0, + "Use bake in original location (create when necessary)", + ""}, + {PF_WRITE_ORIGINAL, + "WRITE_ORIGINAL", + 0, + "Write bake to original location (overwrite existing file)", + ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + RNA_def_enum(ot->srna, "method", method_items, PF_USE_LOCAL, "Method", "How to unpack"); +} + } // namespace blender::ed::object::bake_simulation diff --git a/source/blender/editors/object/object_intern.hh b/source/blender/editors/object/object_intern.hh index 82a804de8b8..cb3e915fde8 100644 --- a/source/blender/editors/object/object_intern.hh +++ b/source/blender/editors/object/object_intern.hh @@ -342,6 +342,8 @@ void OBJECT_OT_simulation_nodes_cache_bake(wmOperatorType *ot); void OBJECT_OT_simulation_nodes_cache_delete(wmOperatorType *ot); void OBJECT_OT_geometry_node_bake_single(wmOperatorType *ot); void OBJECT_OT_geometry_node_bake_delete_single(wmOperatorType *ot); +void OBJECT_OT_geometry_node_bake_pack_single(wmOperatorType *ot); +void OBJECT_OT_geometry_node_bake_unpack_single(wmOperatorType *ot); } // namespace bake_simulation diff --git a/source/blender/editors/object/object_ops.cc b/source/blender/editors/object/object_ops.cc index a757817f3b0..be245985756 100644 --- a/source/blender/editors/object/object_ops.cc +++ b/source/blender/editors/object/object_ops.cc @@ -250,6 +250,8 @@ void operatortypes_object() WM_operatortype_append(bake_simulation::OBJECT_OT_simulation_nodes_cache_delete); WM_operatortype_append(bake_simulation::OBJECT_OT_geometry_node_bake_single); WM_operatortype_append(bake_simulation::OBJECT_OT_geometry_node_bake_delete_single); + WM_operatortype_append(bake_simulation::OBJECT_OT_geometry_node_bake_pack_single); + WM_operatortype_append(bake_simulation::OBJECT_OT_geometry_node_bake_unpack_single); WM_operatortype_append(OBJECT_OT_drop_named_material); WM_operatortype_append(OBJECT_OT_drop_geometry_nodes); WM_operatortype_append(OBJECT_OT_unlink_data); diff --git a/source/blender/editors/space_info/CMakeLists.txt b/source/blender/editors/space_info/CMakeLists.txt index e02632a6ff3..0097d41a761 100644 --- a/source/blender/editors/space_info/CMakeLists.txt +++ b/source/blender/editors/space_info/CMakeLists.txt @@ -38,6 +38,7 @@ set(LIB PRIVATE bf::depsgraph PRIVATE bf::dna PRIVATE bf::intern::guardedalloc + PRIVATE bf::extern::fmtlib ) diff --git a/source/blender/editors/space_info/info_ops.cc b/source/blender/editors/space_info/info_ops.cc index f67cc4c679a..952f3454ff4 100644 --- a/source/blender/editors/space_info/info_ops.cc +++ b/source/blender/editors/space_info/info_ops.cc @@ -8,6 +8,7 @@ #include #include +#include #include "DNA_space_types.h" #include "DNA_windowmanager_types.h" @@ -158,6 +159,8 @@ static int pack_all_exec(bContext *C, wmOperator *op) BKE_packedfile_pack_all(bmain, op->reports, true); + WM_main_add_notifier(NC_WINDOW, nullptr); + return OPERATOR_FINISHED; } @@ -244,6 +247,7 @@ static int unpack_all_exec(bContext *C, wmOperator *op) WM_cursor_wait(false); } G.fileflags &= ~G_FILE_AUTOPACK; + WM_main_add_notifier(NC_WINDOW, nullptr); return OPERATOR_FINISHED; } @@ -253,25 +257,19 @@ static int unpack_all_invoke(bContext *C, wmOperator *op, const wmEvent * /*even Main *bmain = CTX_data_main(C); uiPopupMenu *pup; uiLayout *layout; - char title[64]; - int count = 0; - count = BKE_packedfile_count_all(bmain); + const PackedFileCount count = BKE_packedfile_count_all(bmain); - if (!count) { + if (count.total() == 0) { BKE_report(op->reports, RPT_WARNING, "No packed files to unpack"); G.fileflags &= ~G_FILE_AUTOPACK; return OPERATOR_CANCELLED; } - if (count == 1) { - STRNCPY_UTF8(title, IFACE_("Unpack 1 File")); - } - else { - SNPRINTF(title, IFACE_("Unpack %d Files"), count); - } + const std::string title = fmt::format( + IFACE_("Unpack - Files: {}, Bakes: {}"), count.individual_files, count.bakes); - pup = UI_popup_menu_begin(C, title, ICON_NONE); + pup = UI_popup_menu_begin(C, title.c_str(), ICON_NONE); layout = UI_popup_menu_layout(pup); uiLayoutSetOperatorContext(layout, WM_OP_EXEC_DEFAULT); diff --git a/source/blender/makesdna/DNA_modifier_defaults.h b/source/blender/makesdna/DNA_modifier_defaults.h index 5b0eaf955b3..a65e5359d45 100644 --- a/source/blender/makesdna/DNA_modifier_defaults.h +++ b/source/blender/makesdna/DNA_modifier_defaults.h @@ -563,7 +563,9 @@ } #define _DNA_DEFAULT_NodesModifierData \ - { 0 } + { \ + .bake_target = NODES_MODIFIER_BAKE_TARGET_PACKED, \ + } #define _DNA_DEFAULT_SkinModifierData \ { \ diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index cdeba00cf26..f484dda4032 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -12,6 +12,7 @@ #include "DNA_defs.h" #include "DNA_listBase.h" +#include "DNA_packedFile_types.h" #include "DNA_session_uid_types.h" #ifdef __cplusplus @@ -2403,6 +2404,33 @@ typedef struct NodesModifierDataBlock { char _pad[4]; } NodesModifierDataBlock; +typedef struct NodesModifierBakeFile { + const char *name; + /* May be null if the file is empty. */ + PackedFile *packed_file; + +#ifdef __cplusplus + blender::Span data() const + { + if (this->packed_file) { + return blender::Span{static_cast(this->packed_file->data), + this->packed_file->size}; + } + return {}; + } +#endif +} NodesModifierBakeFile; + +/** + * A packed bake. The format is the same as if the bake was stored on disk. + */ +typedef struct NodesModifierPackedBake { + int meta_files_num; + int blob_files_num; + NodesModifierBakeFile *meta_files; + NodesModifierBakeFile *blob_files; +} NodesModifierPackedBake; + typedef struct NodesModifierBake { /** An id that references a nested node in the node tree. Also see #bNestedNodeRef. */ int id; @@ -2410,7 +2438,9 @@ typedef struct NodesModifierBake { uint32_t flag; /** #NodesModifierBakeMode. */ uint8_t bake_mode; - char _pad[7]; + /** #NodesModifierBakeTarget. */ + int8_t bake_target; + char _pad[6]; /** * Directory where the baked data should be stored. This is only used when * `NODES_MODIFIER_BAKE_CUSTOM_PATH` is set. @@ -2433,6 +2463,10 @@ typedef struct NodesModifierBake { int data_blocks_num; int active_data_block; NodesModifierDataBlock *data_blocks; + NodesModifierPackedBake *packed; + + void *_pad2; + int64_t bake_size; } NodesModifierBake; typedef struct NodesModifierPanel { @@ -2451,6 +2485,12 @@ typedef enum NodesModifierBakeFlag { NODES_MODIFIER_BAKE_CUSTOM_PATH = 1 << 1, } NodesModifierBakeFlag; +typedef enum NodesModifierBakeTarget { + NODES_MODIFIER_BAKE_TARGET_INHERIT = 0, + NODES_MODIFIER_BAKE_TARGET_PACKED = 1, + NODES_MODIFIER_BAKE_TARGET_DISK = 2, +} NodesModifierBakeTarget; + typedef enum NodesModifierBakeMode { NODES_MODIFIER_BAKE_MODE_ANIMATION = 0, NODES_MODIFIER_BAKE_MODE_STILL = 1, @@ -2466,8 +2506,10 @@ typedef struct NodesModifierData { char *bake_directory; /** NodesModifierFlag. */ int8_t flag; + /** #NodesModifierBakeTarget. */ + int8_t bake_target; - char _pad[3]; + char _pad[2]; int bakes_num; NodesModifierBake *bakes; diff --git a/source/blender/makesrna/intern/rna_modifier.cc b/source/blender/makesrna/intern/rna_modifier.cc index a6e36660fc5..f58259e0530 100644 --- a/source/blender/makesrna/intern/rna_modifier.cc +++ b/source/blender/makesrna/intern/rna_modifier.cc @@ -941,6 +941,12 @@ static void rna_Modifier_dependency_update(Main *bmain, Scene *scene, PointerRNA DEG_relations_tag_update(bmain); } +static void rna_NodesModifier_bake_update(Main *bmain, Scene *scene, PointerRNA *ptr) +{ + rna_Modifier_update(bmain, scene, ptr); + WM_main_add_notifier(NC_NODE | NA_EDITED, ptr->owner_id); +} + static void rna_Modifier_is_active_set(PointerRNA *ptr, bool value) { ModifierData *md = static_cast(ptr->data); @@ -7868,6 +7874,39 @@ static void rna_def_modifier_nodes_bake_data_blocks(BlenderRNA *brna) RNA_def_property_int_sdna(prop, nullptr, "active_data_block"); } +static EnumPropertyItem bake_target_in_node_items[] = { + {NODES_MODIFIER_BAKE_TARGET_INHERIT, + "INHERIT", + 0, + "Inherit from Modifier", + "Use setting from the modifier"}, + {NODES_MODIFIER_BAKE_TARGET_PACKED, + "PACKED", + 0, + "Packed", + "Pack the baked data into the .blend file"}, + {NODES_MODIFIER_BAKE_TARGET_DISK, + "DISK", + 0, + "Disk", + "Store the baked data in a directory on disk"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static EnumPropertyItem bake_target_in_modifier_items[] = { + {NODES_MODIFIER_BAKE_TARGET_PACKED, + "PACKED", + 0, + "Packed", + "Pack the baked data into the .blend file"}, + {NODES_MODIFIER_BAKE_TARGET_DISK, + "DISK", + 0, + "Disk", + "Store the baked data in a directory on disk"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + static void rna_def_modifier_nodes_bake(BlenderRNA *brna) { rna_def_modifier_nodes_bake_data_blocks(brna); @@ -7888,33 +7927,38 @@ static void rna_def_modifier_nodes_bake(BlenderRNA *brna) prop = RNA_def_property(srna, "directory", PROP_STRING, PROP_DIRPATH); RNA_def_property_ui_text(prop, "Directory", "Location on disk where the bake data is stored"); - RNA_def_property_update(prop, 0, "rna_Modifier_update"); + RNA_def_property_update(prop, 0, "rna_NodesModifier_bake_update"); prop = RNA_def_property(srna, "frame_start", PROP_INT, PROP_TIME); RNA_def_property_ui_text(prop, "Start Frame", "Frame where the baking starts"); - RNA_def_property_update(prop, 0, "rna_Modifier_update"); + RNA_def_property_update(prop, 0, "rna_NodesModifier_bake_update"); prop = RNA_def_property(srna, "frame_end", PROP_INT, PROP_TIME); RNA_def_property_ui_text(prop, "End Frame", "Frame where the baking ends"); - RNA_def_property_update(prop, 0, "rna_Modifier_update"); + RNA_def_property_update(prop, 0, "rna_NodesModifier_bake_update"); prop = RNA_def_property(srna, "use_custom_simulation_frame_range", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna( prop, nullptr, "flag", NODES_MODIFIER_BAKE_CUSTOM_SIMULATION_FRAME_RANGE); RNA_def_property_ui_text( prop, "Custom Simulation Frame Range", "Override the simulation frame range from the scene"); - RNA_def_property_update(prop, 0, "rna_Modifier_update"); + RNA_def_property_update(prop, 0, "rna_NodesModifier_bake_update"); prop = RNA_def_property(srna, "use_custom_path", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "flag", NODES_MODIFIER_BAKE_CUSTOM_PATH); RNA_def_property_ui_text( prop, "Custom Path", "Specify a path where the baked data should be stored manually"); - RNA_def_property_update(prop, 0, "rna_Modifier_update"); + RNA_def_property_update(prop, 0, "rna_NodesModifier_bake_update"); + + prop = RNA_def_property(srna, "bake_target", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, bake_target_in_node_items); + RNA_def_property_ui_text(prop, "Bake Target", "Where to store the baked data"); + RNA_def_property_update(prop, 0, "rna_NodesModifier_bake_update"); prop = RNA_def_property(srna, "bake_mode", PROP_ENUM, PROP_NONE); RNA_def_property_enum_items(prop, bake_mode_items); RNA_def_property_ui_text(prop, "Bake Mode", ""); - RNA_def_property_update(prop, 0, "rna_Modifier_update"); + RNA_def_property_update(prop, 0, "rna_NodesModifier_bake_update"); prop = RNA_def_property(srna, "bake_id", PROP_INT, PROP_NONE); RNA_def_property_ui_text(prop, @@ -8034,7 +8078,12 @@ static void rna_def_modifier_nodes(BlenderRNA *brna) prop = RNA_def_property(srna, "bake_directory", PROP_STRING, PROP_DIRPATH); RNA_def_property_ui_text( prop, "Simulation Bake Directory", "Location on disk where the bake data is stored"); - RNA_def_property_update(prop, 0, nullptr); + RNA_def_property_update(prop, 0, "rna_NodesModifier_bake_update"); + + prop = RNA_def_property(srna, "bake_target", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, bake_target_in_modifier_items); + RNA_def_property_ui_text(prop, "Bake Target", "Where to store the baked data"); + RNA_def_property_update(prop, 0, "rna_NodesModifier_bake_update"); prop = RNA_def_property(srna, "bakes", PROP_COLLECTION, PROP_NONE); RNA_def_property_struct_type(prop, "NodesModifierBake"); diff --git a/source/blender/modifiers/MOD_nodes.hh b/source/blender/modifiers/MOD_nodes.hh index 40caff19aa0..08e60a02255 100644 --- a/source/blender/modifiers/MOD_nodes.hh +++ b/source/blender/modifiers/MOD_nodes.hh @@ -6,6 +6,7 @@ struct NodesModifierData; struct Object; +struct NodesModifierPackedBake; namespace blender::bke::bake { struct ModifierCache; @@ -40,5 +41,6 @@ struct NodesModifierRuntime { }; void nodes_modifier_data_block_destruct(NodesModifierDataBlock *data_block, bool do_id_user); +void nodes_modifier_packed_bake_free(NodesModifierPackedBake *packed_bake); } // namespace blender diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 6e6cbb55956..cc60dcbee43 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -57,6 +57,7 @@ #include "BKE_node_runtime.hh" #include "BKE_node_tree_update.hh" #include "BKE_object.hh" +#include "BKE_packedFile.hh" #include "BKE_pointcloud.hh" #include "BKE_screen.hh" #include "BKE_workspace.hh" @@ -968,15 +969,33 @@ static void ensure_bake_loaded(bake::NodeBakeCache &bake_cache, bake::FrameCache if (!frame_cache.state.items_by_id.is_empty()) { return; } + if (!frame_cache.meta_data_source.has_value()) { + return; + } + if (bake_cache.memory_blob_reader) { + if (const auto *meta_buffer = std::get_if>(&*frame_cache.meta_data_source)) { + const std::string meta_str{reinterpret_cast(meta_buffer->data()), + size_t(meta_buffer->size())}; + std::istringstream meta_stream{meta_str}; + std::optional bake_state = bake::deserialize_bake( + meta_stream, *bake_cache.memory_blob_reader, *bake_cache.blob_sharing); + if (!bake_state.has_value()) { + return; + } + frame_cache.state = std::move(*bake_state); + return; + } + } if (!bake_cache.blobs_dir) { return; } - if (!frame_cache.meta_path) { + const auto *meta_path = std::get_if(&*frame_cache.meta_data_source); + if (!meta_path) { return; } - bke::bake::DiskBlobReader blob_reader{*bake_cache.blobs_dir}; - fstream meta_file{*frame_cache.meta_path}; - std::optional bake_state = bke::bake::deserialize_bake( + bake::DiskBlobReader blob_reader{*bake_cache.blobs_dir}; + fstream meta_file{*meta_path}; + std::optional bake_state = bake::deserialize_bake( meta_file, blob_reader, *bake_cache.blob_sharing); if (!bake_state.has_value()) { return; @@ -984,12 +1003,53 @@ static void ensure_bake_loaded(bake::NodeBakeCache &bake_cache, bake::FrameCache frame_cache.state = std::move(*bake_state); } -static bool try_find_baked_data(bake::NodeBakeCache &bake, +static bool try_find_baked_data(const NodesModifierBake &bake, + bake::NodeBakeCache &bake_cache, const Main &bmain, const Object &object, const NodesModifierData &nmd, const int id) { + if (bake.packed) { + if (bake.packed->meta_files_num == 0) { + return false; + } + bake_cache.reset(); + Map file_by_frame; + for (const NodesModifierBakeFile &meta_file : + Span{bake.packed->meta_files, bake.packed->meta_files_num}) + { + const std::optional frame = bake::file_name_to_frame(meta_file.name); + if (!frame) { + return false; + } + if (!file_by_frame.add(*frame, &meta_file)) { + /* Can only have on file per (sub)frame. */ + return false; + } + } + /* Make sure frames processed in the right order. */ + Vector frames; + frames.extend(file_by_frame.keys().begin(), file_by_frame.keys().end()); + + for (const SubFrame &frame : frames) { + const NodesModifierBakeFile &meta_file = *file_by_frame.lookup(frame); + auto frame_cache = std::make_unique(); + frame_cache->frame = frame; + frame_cache->meta_data_source = meta_file.data(); + bake_cache.frames.append(std::move(frame_cache)); + } + + bake_cache.memory_blob_reader = std::make_unique(); + for (const NodesModifierBakeFile &blob_file : + Span{bake.packed->blob_files, bake.packed->blob_files_num}) + { + bake_cache.memory_blob_reader->add(blob_file.name, blob_file.data()); + } + bake_cache.blob_sharing = std::make_unique(); + return true; + } + std::optional bake_path = bake::get_node_bake_path(bmain, object, nmd, id); if (!bake_path) { return false; @@ -998,15 +1058,15 @@ static bool try_find_baked_data(bake::NodeBakeCache &bake, if (meta_files.is_empty()) { return false; } - bake.reset(); + bake_cache.reset(); for (const bake::MetaFile &meta_file : meta_files) { auto frame_cache = std::make_unique(); frame_cache->frame = meta_file.frame; - frame_cache->meta_path = meta_file.path; - bake.frames.append(std::move(frame_cache)); + frame_cache->meta_data_source = meta_file.path; + bake_cache.frames.append(std::move(frame_cache)); } - bake.blobs_dir = bake_path->blobs_dir; - bake.blob_sharing = std::make_unique(); + bake_cache.blobs_dir = bake_path->blobs_dir; + bake_cache.blob_sharing = std::make_unique(); return true; } @@ -1141,7 +1201,7 @@ class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams { /* Try load baked data. */ if (!node_cache.bake.failed_finding_bake) { if (node_cache.cache_status != bake::CacheStatus::Baked) { - if (try_find_baked_data(node_cache.bake, *bmain_, *ctx_.object, nmd_, zone_id)) { + if (try_find_baked_data(bake, node_cache.bake, *bmain_, *ctx_.object, nmd_, zone_id)) { node_cache.cache_status = bake::CacheStatus::Baked; } else { @@ -1434,7 +1494,7 @@ class NodesModifierBakeParams : public nodes::GeoNodesBakeParams { /* Try load baked data. */ if (node_cache.bake.frames.is_empty()) { if (!node_cache.bake.failed_finding_bake) { - if (!try_find_baked_data(node_cache.bake, *bmain_, *ctx_.object, nmd_, id)) { + if (!try_find_baked_data(bake, node_cache.bake, *bmain_, *ctx_.object, nmd_, id)) { node_cache.bake.failed_finding_bake = true; } } @@ -1503,7 +1563,7 @@ class NodesModifierBakeParams : public nodes::GeoNodesBakeParams { [[nodiscard]] bool check_read_error(const bake::FrameCache &frame_cache, nodes::BakeNodeBehavior &behavior) const { - if (frame_cache.meta_path && frame_cache.state.items_by_id.is_empty()) { + if (frame_cache.meta_data_source && frame_cache.state.items_by_id.is_empty()) { auto &read_error_info = behavior.behavior.emplace(); read_error_info.message = RPT_("Cannot load the baked data"); return true; @@ -2229,6 +2289,7 @@ static void draw_bake_panel(uiLayout *layout, PointerRNA *modifier_ptr) uiLayout *col = uiLayoutColumn(layout, false); uiLayoutSetPropSep(col, true); uiLayoutSetPropDecorate(col, false); + uiItemR(col, modifier_ptr, "bake_target", UI_ITEM_NONE, nullptr, ICON_NONE); uiItemR(col, modifier_ptr, "bake_directory", UI_ITEM_NONE, IFACE_("Bake Path"), ICON_NONE); } @@ -2411,6 +2472,29 @@ static void blend_write(BlendWriter *writer, const ID * /*id_owner*/, const Modi BLO_write_string(writer, item.id_name); BLO_write_string(writer, item.lib_name); } + if (bake.packed) { + BLO_write_struct(writer, NodesModifierPackedBake, bake.packed); + BLO_write_struct_array( + writer, NodesModifierBakeFile, bake.packed->meta_files_num, bake.packed->meta_files); + BLO_write_struct_array( + writer, NodesModifierBakeFile, bake.packed->blob_files_num, bake.packed->blob_files); + const auto write_bake_file = [&](const NodesModifierBakeFile &bake_file) { + BLO_write_string(writer, bake_file.name); + if (bake_file.packed_file) { + BKE_packedfile_blend_write(writer, bake_file.packed_file); + } + }; + for (const NodesModifierBakeFile &meta_file : + Span{bake.packed->meta_files, bake.packed->meta_files_num}) + { + write_bake_file(meta_file); + } + for (const NodesModifierBakeFile &blob_file : + Span{bake.packed->blob_files, bake.packed->blob_files_num}) + { + write_bake_file(blob_file); + } + } } BLO_write_struct_array(writer, NodesModifierPanel, nmd->panels_num, nmd->panels); @@ -2461,6 +2545,30 @@ static void blend_read(BlendDataReader *reader, ModifierData *md) BLO_read_string(reader, &data_block.id_name); BLO_read_string(reader, &data_block.lib_name); } + + BLO_read_struct(reader, NodesModifierPackedBake, &bake.packed); + if (bake.packed) { + BLO_read_struct_array( + reader, NodesModifierBakeFile, bake.packed->meta_files_num, &bake.packed->meta_files); + BLO_read_struct_array( + reader, NodesModifierBakeFile, bake.packed->blob_files_num, &bake.packed->blob_files); + const auto read_bake_file = [&](NodesModifierBakeFile &bake_file) { + BLO_read_string(reader, &bake_file.name); + if (bake_file.packed_file) { + BKE_packedfile_blend_read(reader, &bake_file.packed_file, ""); + } + }; + for (NodesModifierBakeFile &meta_file : + MutableSpan{bake.packed->meta_files, bake.packed->meta_files_num}) + { + read_bake_file(meta_file); + } + for (NodesModifierBakeFile &blob_file : + MutableSpan{bake.packed->blob_files, bake.packed->blob_files_num}) + { + read_bake_file(blob_file); + } + } } BLO_read_struct_array(reader, NodesModifierPanel, nmd->panels_num, &nmd->panels); @@ -2494,6 +2602,24 @@ static void copy_data(const ModifierData *md, ModifierData *target, const int fl } } } + if (bake.packed) { + bake.packed = static_cast(MEM_dupallocN(bake.packed)); + const auto copy_bake_files_inplace = [](NodesModifierBakeFile **bake_files, + const int bake_files_num) { + if (!*bake_files) { + return; + } + *bake_files = static_cast(MEM_dupallocN(*bake_files)); + for (NodesModifierBakeFile &bake_file : MutableSpan{*bake_files, bake_files_num}) { + bake_file.name = BLI_strdup_null(bake_file.name); + if (bake_file.packed_file) { + bake_file.packed_file = BKE_packedfile_duplicate(bake_file.packed_file); + } + } + }; + copy_bake_files_inplace(&bake.packed->meta_files, bake.packed->meta_files_num); + copy_bake_files_inplace(&bake.packed->blob_files, bake.packed->blob_files_num); + } } } @@ -2520,6 +2646,22 @@ static void copy_data(const ModifierData *md, ModifierData *target, const int fl } } +void nodes_modifier_packed_bake_free(NodesModifierPackedBake *packed_bake) +{ + const auto free_packed_files = [](NodesModifierBakeFile *files, const int files_num) { + for (NodesModifierBakeFile &file : MutableSpan{files, files_num}) { + MEM_SAFE_FREE(file.name); + if (file.packed_file) { + BKE_packedfile_free(file.packed_file); + } + } + MEM_SAFE_FREE(files); + }; + free_packed_files(packed_bake->meta_files, packed_bake->meta_files_num); + free_packed_files(packed_bake->blob_files, packed_bake->blob_files_num); + MEM_SAFE_FREE(packed_bake); +} + static void free_data(ModifierData *md) { NodesModifierData *nmd = reinterpret_cast(md); @@ -2537,6 +2679,10 @@ static void free_data(ModifierData *md) MEM_SAFE_FREE(data_block.lib_name); } MEM_SAFE_FREE(bake.data_blocks); + + if (bake.packed) { + nodes_modifier_packed_bake_free(bake.packed); + } } MEM_SAFE_FREE(nmd->bakes); diff --git a/source/blender/nodes/geometry/include/NOD_geo_bake.hh b/source/blender/nodes/geometry/include/NOD_geo_bake.hh index 731ac8bfd63..ed8090056cc 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_bake.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_bake.hh @@ -6,6 +6,7 @@ #include +#include "DNA_modifier_types.h" #include "DNA_node_types.h" #include "NOD_geo_simulation.hh" @@ -90,6 +91,7 @@ struct BakeDrawContext { std::optional frame_range; bool bake_still; bool is_baked; + std::optional bake_target; }; [[nodiscard]] bool get_bake_draw_context(const bContext *C, @@ -97,8 +99,11 @@ struct BakeDrawContext { BakeDrawContext &r_ctx); std::string get_baked_string(const BakeDrawContext &ctx); + std::optional get_bake_state_string(const BakeDrawContext &ctx); -void draw_common_bake_settings(BakeDrawContext &ctx, uiLayout *layout); -void draw_bake_button(const BakeDrawContext &ctx, uiLayout *layout); +void draw_common_bake_settings(bContext *C, BakeDrawContext &ctx, uiLayout *layout); +void draw_bake_button_row(const BakeDrawContext &ctx, + uiLayout *layout, + bool is_in_sidebar = false); } // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_bake.cc b/source/blender/nodes/geometry/nodes/node_geo_bake.cc index 8871e3630f4..a12d6bff0aa 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_bake.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_bake.cc @@ -13,12 +13,15 @@ #include "UI_interface.hh" #include "UI_resources.hh" +#include "BLI_path_util.h" #include "BLI_string.h" #include "BKE_anonymous_attribute_make.hh" #include "BKE_bake_geometry_nodes_modifier.hh" #include "BKE_bake_items_socket.hh" #include "BKE_context.hh" +#include "BKE_global.hh" +#include "BKE_main.hh" #include "BKE_screen.hh" #include "ED_node.hh" @@ -578,7 +581,7 @@ static void node_layout(uiLayout *layout, bContext *C, PointerRNA *ptr) uiLayoutSetEnabled(row, !ctx.is_baked); uiItemR(row, &ctx.bake_rna, "bake_mode", UI_ITEM_R_EXPAND, IFACE_("Mode"), ICON_NONE); } - draw_bake_button(ctx, col); + draw_bake_button_row(ctx, col); } static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) @@ -601,13 +604,14 @@ static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) uiItemR(row, &ctx.bake_rna, "bake_mode", UI_ITEM_R_EXPAND, IFACE_("Mode"), ICON_NONE); } - draw_bake_button(ctx, col); + draw_bake_button_row(ctx, col, true); if (const std::optional bake_state_str = get_bake_state_string(ctx)) { - uiItemL(col, bake_state_str->c_str(), ICON_NONE); + uiLayout *row = uiLayoutRow(col, true); + uiItemL(row, bake_state_str->c_str(), ICON_NONE); } } - draw_common_bake_settings(ctx, layout); + draw_common_bake_settings(C, ctx, layout); draw_data_blocks(C, layout, ctx.bake_rna); } @@ -721,6 +725,7 @@ bool get_bake_draw_context(const bContext *C, const bNode &node, BakeDrawContext r_ctx.bake_still = node.type == GEO_NODE_BAKE && r_ctx.bake->bake_mode == NODES_MODIFIER_BAKE_MODE_STILL; r_ctx.is_baked = r_ctx.baked_range.has_value(); + r_ctx.bake_target = bke::bake::get_node_bake_target(*r_ctx.object, *r_ctx.nmd, r_ctx.bake->id); return true; } @@ -735,11 +740,18 @@ std::string get_baked_string(const BakeDrawContext &ctx) std::optional get_bake_state_string(const BakeDrawContext &ctx) { + if (G.is_rendering) { + /* Avoid accessing data that is generated while baking. */ + return std::nullopt; + } if (ctx.is_baked) { - if (ctx.bake_still) { - return fmt::format(RPT_("Baked Frame {}"), ctx.baked_range->first()); + const std::string baked_str = get_baked_string(ctx); + char size_str[BLI_STR_FORMAT_INT64_BYTE_UNIT_SIZE]; + BLI_str_format_byte_unit(size_str, ctx.bake->bake_size, true); + if (ctx.bake->packed) { + return fmt::format(RPT_("{} ({} packed)"), baked_str, size_str); } - return fmt::format(RPT_("Baked {} - {}"), ctx.baked_range->first(), ctx.baked_range->last()); + return fmt::format(RPT_("{} ({} on disk)"), baked_str, size_str); } if (ctx.frame_range.has_value()) { if (!ctx.bake_still) { @@ -750,15 +762,21 @@ std::optional get_bake_state_string(const BakeDrawContext &ctx) return std::nullopt; } -void draw_bake_button(const BakeDrawContext &ctx, uiLayout *layout) +void draw_bake_button_row(const BakeDrawContext &ctx, uiLayout *layout, const bool is_in_sidebar) { uiLayout *col = uiLayoutColumn(layout, true); uiLayout *row = uiLayoutRow(col, true); { + const char *bake_label = IFACE_("Bake"); + if (is_in_sidebar) { + bake_label = ctx.bake_target == NODES_MODIFIER_BAKE_TARGET_DISK ? IFACE_("Bake to Disk") : + IFACE_("Bake Packed"); + } + PointerRNA ptr; uiItemFullO(row, "OBJECT_OT_geometry_node_bake_single", - IFACE_("Bake"), + bake_label, ICON_NONE, nullptr, WM_OP_INVOKE_DEFAULT, @@ -771,34 +789,109 @@ void draw_bake_button(const BakeDrawContext &ctx, uiLayout *layout) { uiLayout *subrow = uiLayoutRow(row, true); uiLayoutSetActive(subrow, ctx.is_baked); - PointerRNA ptr; - uiItemFullO(subrow, - "OBJECT_OT_geometry_node_bake_delete_single", - "", - ICON_TRASH, - nullptr, - WM_OP_INVOKE_DEFAULT, - UI_ITEM_NONE, - &ptr); - WM_operator_properties_id_lookup_set_from_id(&ptr, &ctx.object->id); - RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name); - RNA_int_set(&ptr, "bake_id", ctx.bake->id); + if (is_in_sidebar) { + if (ctx.is_baked && !G.is_rendering) { + if (ctx.bake->packed) { + PointerRNA ptr; + uiItemFullO(subrow, + "OBJECT_OT_geometry_node_bake_unpack_single", + "", + ICON_PACKAGE, + nullptr, + WM_OP_INVOKE_DEFAULT, + UI_ITEM_NONE, + &ptr); + WM_operator_properties_id_lookup_set_from_id(&ptr, &ctx.object->id); + RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name); + RNA_int_set(&ptr, "bake_id", ctx.bake->id); + } + else { + PointerRNA ptr; + uiItemFullO(subrow, + "OBJECT_OT_geometry_node_bake_pack_single", + "", + ICON_UGLYPACKAGE, + nullptr, + WM_OP_INVOKE_DEFAULT, + UI_ITEM_NONE, + &ptr); + WM_operator_properties_id_lookup_set_from_id(&ptr, &ctx.object->id); + RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name); + RNA_int_set(&ptr, "bake_id", ctx.bake->id); + } + } + else { + /* If the data is not yet baked, still show the icon based on the derived bake target.*/ + const int icon = ctx.bake_target == NODES_MODIFIER_BAKE_TARGET_DISK ? ICON_UGLYPACKAGE : + ICON_PACKAGE; + PointerRNA ptr; + uiItemFullO(subrow, + "OBJECT_OT_geometry_node_bake_pack_single", + "", + icon, + nullptr, + WM_OP_INVOKE_DEFAULT, + UI_ITEM_NONE, + &ptr); + } + } + { + PointerRNA ptr; + uiItemFullO(subrow, + "OBJECT_OT_geometry_node_bake_delete_single", + "", + ICON_TRASH, + nullptr, + WM_OP_INVOKE_DEFAULT, + UI_ITEM_NONE, + &ptr); + WM_operator_properties_id_lookup_set_from_id(&ptr, &ctx.object->id); + RNA_string_set(&ptr, "modifier_name", ctx.nmd->modifier.name); + RNA_int_set(&ptr, "bake_id", ctx.bake->id); + } } } -void draw_common_bake_settings(BakeDrawContext &ctx, uiLayout *layout) +void draw_common_bake_settings(bContext *C, BakeDrawContext &ctx, uiLayout *layout) { + uiLayoutSetPropSep(layout, true); + uiLayoutSetPropDecorate(layout, false); + uiLayout *settings_col = uiLayoutColumn(layout, false); - uiLayoutSetPropSep(settings_col, true); - uiLayoutSetPropDecorate(settings_col, false); uiLayoutSetActive(settings_col, !ctx.is_baked); { uiLayout *col = uiLayoutColumn(settings_col, true); - uiLayoutSetActive(col, !ctx.is_baked); - uiItemR(col, &ctx.bake_rna, "use_custom_path", UI_ITEM_NONE, IFACE_("Custom Path"), ICON_NONE); + uiItemR(col, &ctx.bake_rna, "bake_target", UI_ITEM_NONE, nullptr, ICON_NONE); uiLayout *subcol = uiLayoutColumn(col, true); - uiLayoutSetActive(subcol, ctx.bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH); - uiItemR(subcol, &ctx.bake_rna, "directory", UI_ITEM_NONE, IFACE_("Path"), ICON_NONE); + uiLayoutSetActive(subcol, ctx.bake_target == NODES_MODIFIER_BAKE_TARGET_DISK); + uiItemR( + subcol, &ctx.bake_rna, "use_custom_path", UI_ITEM_NONE, IFACE_("Custom Path"), ICON_NONE); + uiLayout *subsubcol = uiLayoutColumn(subcol, true); + const bool use_custom_path = ctx.bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH; + uiLayoutSetActive(subsubcol, use_custom_path); + Main *bmain = CTX_data_main(C); + auto bake_path = bke::bake::get_node_bake_path(*bmain, *ctx.object, *ctx.nmd, ctx.bake->id); + + char placeholder_path[FILE_MAX] = ""; + if (StringRef(ctx.bake->directory).is_empty() && + !(ctx.bake->flag & NODES_MODIFIER_BAKE_CUSTOM_PATH) && bake_path.has_value() && + bake_path->bake_dir.has_value()) + { + STRNCPY(placeholder_path, bake_path->bake_dir->c_str()); + if (BLI_path_is_rel(ctx.nmd->bake_directory)) { + BLI_path_rel(placeholder_path, BKE_main_blendfile_path(bmain)); + } + } + + uiItemFullR(subsubcol, + &ctx.bake_rna, + RNA_struct_find_property(&ctx.bake_rna, "directory"), + -1, + 0, + UI_ITEM_NONE, + IFACE_("Path"), + ICON_NONE, + placeholder_path); } { uiLayout *col = uiLayoutColumn(settings_col, true); diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation.cc index 0e6efde9956..3988e084312 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation.cc @@ -327,12 +327,13 @@ static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_no { uiLayout *col = uiLayoutColumn(layout, false); - draw_bake_button(ctx, col); + draw_bake_button_row(ctx, col, true); if (const std::optional bake_state_str = get_bake_state_string(ctx)) { - uiItemL(col, bake_state_str->c_str(), ICON_NONE); + uiLayout *row = uiLayoutRow(col, true); + uiItemL(row, bake_state_str->c_str(), ICON_NONE); } } - draw_common_bake_settings(ctx, layout); + draw_common_bake_settings(C, ctx, layout); draw_data_blocks(C, layout, ctx.bake_rna); }