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 a7af1a197db..8cf90d54a9f 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); }