Geometry Nodes: refactor simulation storage and how simulation nodes access it

Goals of the refactor:
* Internal support for baking individual simulation zones (not exposed in the UI yet).
* More well-defined access to simulation data in geometry nodes. Especially, it
  should be more obvious where data is modified. A similar approach should also
  work for the Bake node.

Previously, there were a bunch of simulation specific properties in `GeoNodesModifierData`
and then the simulation input and output nodes would have to figure out what to do with that
data. Now, there is a new `GeoNodesSimulationParams` which controls the behavior of
simulation zones. Contrary to before, different simulation zones can now be handled
independently, even if that is not really used yet. `GeoNodesSimulationParams` has to be
subclassed by a user of the geometry nodes API. The subclass controls what each simulation
input and output node does. This some of the logic that was part of the node before, into
the modifier.

The way we store simulation data is "transposed". Previously, we stored zone data per
frame, but now we store frame data per zone. This allows different zones to be more
independent. Consequently, the way the simulation cache is accessed changed. I kept
things simpler for now, avoiding many of the methods we had before, and directly
accessing the data more often which is often simple enough. This change also makes
it theoretically possible to store baked data for separate zones independently.
A downside of this is, that existing baked data can't be read anymore. We don't really
have compatibility guarantees for this format yet, so it's ok. Users will have to bake again.
The bake folder for the modifier now contains an extra subfolder for every zone.

Drawing the cached/baked frames in the timeline is less straight forward now. Currently,
it just draws the state of one of the zones, which usually is identical to that of all other
zones. This will change in the future though, and then the timeline drawing also needs
some new UI work.

Pull Request: https://projects.blender.org/blender/blender/pulls/111623
This commit is contained in:
Jacques Lucke
2023-08-31 16:28:03 +02:00
parent daa4c0f91e
commit e92c59bc9b
16 changed files with 1013 additions and 822 deletions

View File

@@ -0,0 +1,43 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <optional>
#include <string>
#include "BLI_string_ref.hh"
#include "BLI_sub_frame.hh"
#include "BLI_vector.hh"
namespace blender::bke::bake_paths {
struct MetaFile {
SubFrame frame;
std::string path;
};
struct BakePath {
/** Path to the directory containing the meta data per frame. */
std::string meta_dir;
/**
* Path to the directory that contains the binary data. Could be shared between multiple bakes
* to reduce memory consumption.
*/
std::string bdata_dir;
/**
* Folder that is allowed to be deleted when the bake is deleted and it doesn't contain anything
* else. Typically, this contains the meta and bdata directories.
*/
std::optional<std::string> bake_dir;
static BakePath from_single_root(StringRefNull root_dir);
};
std::string frame_to_file_name(const SubFrame &frame);
std::optional<SubFrame> file_name_to_frame(const StringRefNull file_name);
Vector<MetaFile> find_sorted_meta_files(const StringRefNull meta_dir);
} // namespace blender::bke::bake_paths

View File

@@ -4,6 +4,7 @@
#pragma once
#include "BKE_bake_items_paths.hh"
#include "BKE_bake_items_serialize.hh"
#include "BKE_geometry_set.hh"
@@ -11,65 +12,11 @@
#include "BLI_sub_frame.hh"
struct bNodeTree;
struct NodesModifierData;
struct Main;
namespace blender::bke::sim {
class ModifierSimulationCache;
/**
* Storage of values for a single simulation input and output node pair.
* Used as a cache to allow random access in time, and as an intermediate form before data is
* baked.
*/
class SimulationZoneState {
public:
Map<int, std::unique_ptr<BakeItem>> item_by_identifier;
};
/** Identifies a simulation zone (input and output node pair) used by a modifier. */
struct SimulationZoneID {
/** ID of the #bNestedNodeRef that references the output node of the zone. */
int32_t nested_node_id;
uint64_t hash() const
{
return this->nested_node_id;
}
friend bool operator==(const SimulationZoneID &a, const SimulationZoneID &b)
{
return a.nested_node_id == b.nested_node_id;
}
};
/**
* Stores a single frame of simulation states for all simulation zones in a modifier's node
* hierarchy.
*/
class ModifierSimulationState {
private:
mutable bool bake_loaded_;
public:
ModifierSimulationCache *owner_;
mutable std::mutex mutex_;
Map<SimulationZoneID, std::unique_ptr<SimulationZoneState>> zone_states_;
/** File path to folder containing baked meta-data. */
std::optional<std::string> meta_path_;
/** File path to folder containing baked data. */
std::optional<std::string> bdata_dir_;
SimulationZoneState *get_zone_state(const SimulationZoneID &zone_id);
const SimulationZoneState *get_zone_state(const SimulationZoneID &zone_id) const;
SimulationZoneState &get_zone_state_for_write(const SimulationZoneID &zone_id);
void ensure_bake_loaded(const bNodeTree &ntree) const;
};
struct ModifierSimulationStateAtFrame {
SubFrame frame;
ModifierSimulationState state;
};
enum class CacheState {
/** The cache is up-to-date with the inputs. */
Valid,
@@ -82,56 +29,34 @@ enum class CacheState {
Baked,
};
struct StatesAroundFrame {
const ModifierSimulationStateAtFrame *prev = nullptr;
const ModifierSimulationStateAtFrame *current = nullptr;
const ModifierSimulationStateAtFrame *next = nullptr;
struct SimulationZoneFrameCache {
SubFrame frame;
Map<int, std::unique_ptr<BakeItem>> items;
/** Used when the baked data is loaded lazily. */
std::optional<std::string> meta_path;
};
struct ModifierSimulationCacheRealtime {
std::unique_ptr<ModifierSimulationState> prev_state;
std::unique_ptr<ModifierSimulationState> current_state;
SubFrame prev_frame;
SubFrame current_frame;
struct SimulationZonePrevState {
Map<int, std::unique_ptr<BakeItem>> items;
SubFrame frame;
};
struct SimulationZoneCache {
Vector<std::unique_ptr<SimulationZoneFrameCache>> frame_caches;
std::optional<SimulationZonePrevState> prev_state;
std::optional<std::string> bdata_dir;
std::unique_ptr<BDataSharing> bdata_sharing;
bool failed_finding_bake = false;
CacheState cache_state = CacheState::Valid;
void reset();
};
class ModifierSimulationCache {
private:
mutable std::mutex states_at_frames_mutex_;
/**
* All simulation states, sorted by frame.
*/
Vector<std::unique_ptr<ModifierSimulationStateAtFrame>> states_at_frames_;
/**
* Used for baking to deduplicate arrays when writing and writing from storage. Sharing info
* must be kept alive for multiple frames to detect if each data array's version has changed.
*/
std::unique_ptr<BDataSharing> bdata_sharing_;
friend ModifierSimulationState;
bool failed_finding_bake_ = false;
public:
CacheState cache_state = CacheState::Valid;
/** A non-persistent cache used only to pass simulation state data from one frame to the next. */
ModifierSimulationCacheRealtime realtime_cache;
void try_discover_bake(StringRefNull absolute_bake_dir);
bool has_state_at_frame(const SubFrame &frame) const;
bool has_states() const;
const ModifierSimulationState *get_state_at_exact_frame(const SubFrame &frame) const;
ModifierSimulationState &get_state_at_frame_for_write(const SubFrame &frame);
StatesAroundFrame get_states_around_frame(const SubFrame &frame) const;
void invalidate()
{
this->cache_state = CacheState::Invalid;
}
void reset();
mutable std::mutex mutex;
Map<int, std::unique_ptr<SimulationZoneCache>> cache_by_zone_id;
};
/**
@@ -140,4 +65,12 @@ class ModifierSimulationCache {
*/
void scene_simulation_states_reset(Scene &scene);
std::optional<bake_paths::BakePath> get_simulation_zone_bake_path(const Main &bmain,
const Object &object,
const NodesModifierData &nmd,
int zone_id);
std::optional<std::string> get_modifier_simulation_bake_path(const Main &bmain,
const Object &object,
const NodesModifierData &nmd);
} // namespace blender::bke::sim

View File

@@ -29,22 +29,6 @@ std::string get_default_modifier_bake_directory(const Main &bmain,
const Object &object,
const ModifierData &md);
/**
* Encode the simulation state in a #DictionaryValue which also contains references to external
* binary data that has been written using #bdata_writer.
*/
void serialize_modifier_simulation_state(const ModifierSimulationState &state,
BDataWriter &bdata_writer,
BDataSharing &bdata_sharing,
DictionaryValue &r_io_root);
/**
* Fill the simulation state by parsing the provided #DictionaryValue which also contains
* references to external binary data that is read using #bdata_reader.
*/
void deserialize_modifier_simulation_state(const bNodeTree &ntree,
const DictionaryValue &io_root,
const BDataReader &bdata_reader,
const BDataSharing &bdata_sharing,
ModifierSimulationState &r_state);
constexpr int simulation_file_storage_version = 3;
} // namespace blender::bke::sim

View File

@@ -73,6 +73,7 @@ set(SRC
intern/attribute_math.cc
intern/autoexec.cc
intern/bake_items.cc
intern/bake_items_paths.cc
intern/bake_items_serialize.cc
intern/bake_items_socket.cc
intern/blender.cc
@@ -333,6 +334,7 @@ set(SRC
BKE_attribute_math.hh
BKE_autoexec.h
BKE_bake_items.hh
BKE_bake_items_paths.hh
BKE_bake_items_serialize.hh
BKE_bake_items_socket.hh
BKE_blender.h

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_bake_items_paths.hh"
#include "BLI_fileops.hh"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_string_utils.h"
namespace blender::bke::bake_paths {
std::string frame_to_file_name(const SubFrame &frame)
{
char file_name_c[FILE_MAX];
SNPRINTF(file_name_c, "%011.5f", double(frame));
BLI_string_replace_char(file_name_c, '.', '_');
return file_name_c;
}
std::optional<SubFrame> file_name_to_frame(const StringRefNull file_name)
{
char modified_file_name[FILE_MAX];
STRNCPY(modified_file_name, file_name.c_str());
BLI_string_replace_char(modified_file_name, '_', '.');
const SubFrame frame = std::stof(modified_file_name);
return frame;
}
Vector<MetaFile> find_sorted_meta_files(const StringRefNull meta_dir)
{
if (!BLI_is_dir(meta_dir.c_str())) {
return {};
}
direntry *dir_entries = nullptr;
const int dir_entries_num = BLI_filelist_dir_contents(meta_dir.c_str(), &dir_entries);
BLI_SCOPED_DEFER([&]() { BLI_filelist_free(dir_entries, dir_entries_num); });
Vector<MetaFile> meta_files;
for (const int i : IndexRange(dir_entries_num)) {
const direntry &dir_entry = dir_entries[i];
const StringRefNull dir_entry_path = dir_entry.path;
if (!dir_entry_path.endswith(".json")) {
continue;
}
const std::optional<SubFrame> frame = file_name_to_frame(dir_entry.relname);
if (!frame) {
continue;
}
meta_files.append({*frame, dir_entry_path});
}
std::sort(meta_files.begin(), meta_files.end(), [](const MetaFile &a, const MetaFile &b) {
return a.frame < b.frame;
});
return meta_files;
}
BakePath BakePath::from_single_root(StringRefNull root_dir)
{
char meta_dir[FILE_MAX];
BLI_path_join(meta_dir, sizeof(meta_dir), root_dir.c_str(), "meta");
char bdata_dir[FILE_MAX];
BLI_path_join(bdata_dir, sizeof(bdata_dir), root_dir.c_str(), "bdata");
BakePath bake_path;
bake_path.meta_dir = meta_dir;
bake_path.bdata_dir = bdata_dir;
bake_path.bake_dir = root_dir;
return bake_path;
}
} // namespace blender::bke::bake_paths

View File

@@ -4,6 +4,7 @@
#include "BKE_collection.h"
#include "BKE_curves.hh"
#include "BKE_main.h"
#include "BKE_simulation_state.hh"
#include "BKE_simulation_state_serialize.hh"
@@ -21,213 +22,13 @@
namespace blender::bke::sim {
void ModifierSimulationCache::try_discover_bake(const StringRefNull absolute_bake_dir)
void SimulationZoneCache::reset()
{
if (failed_finding_bake_) {
return;
}
char meta_dir[FILE_MAX];
BLI_path_join(meta_dir, sizeof(meta_dir), absolute_bake_dir.c_str(), "meta");
char bdata_dir[FILE_MAX];
BLI_path_join(bdata_dir, sizeof(bdata_dir), absolute_bake_dir.c_str(), "bdata");
if (!BLI_is_dir(meta_dir) || !BLI_is_dir(bdata_dir)) {
failed_finding_bake_ = true;
return;
}
direntry *dir_entries = nullptr;
const int dir_entries_num = BLI_filelist_dir_contents(meta_dir, &dir_entries);
BLI_SCOPED_DEFER([&]() { BLI_filelist_free(dir_entries, dir_entries_num); });
if (dir_entries_num == 0) {
failed_finding_bake_ = true;
return;
}
this->reset();
{
std::lock_guard lock(states_at_frames_mutex_);
for (const int i : IndexRange(dir_entries_num)) {
const direntry &dir_entry = dir_entries[i];
const StringRefNull dir_entry_path = dir_entry.path;
if (!dir_entry_path.endswith(".json")) {
continue;
}
char modified_file_name[FILE_MAX];
STRNCPY(modified_file_name, dir_entry.relname);
BLI_string_replace_char(modified_file_name, '_', '.');
const SubFrame frame = std::stof(modified_file_name);
auto new_state_at_frame = std::make_unique<ModifierSimulationStateAtFrame>();
new_state_at_frame->frame = frame;
new_state_at_frame->state.bdata_dir_ = bdata_dir;
new_state_at_frame->state.meta_path_ = dir_entry.path;
new_state_at_frame->state.owner_ = this;
states_at_frames_.append(std::move(new_state_at_frame));
}
bdata_sharing_ = std::make_unique<BDataSharing>();
this->cache_state = CacheState::Baked;
}
}
static int64_t find_state_at_frame(
const Span<std::unique_ptr<ModifierSimulationStateAtFrame>> states, const SubFrame &frame)
{
const int64_t i = binary_search::find_predicate_begin(
states, [&](const auto &item) { return item->frame >= frame; });
if (i == states.size()) {
return -1;
}
return i;
}
static int64_t find_state_at_frame_exact(
const Span<std::unique_ptr<ModifierSimulationStateAtFrame>> states, const SubFrame &frame)
{
const int64_t i = find_state_at_frame(states, frame);
if (i == -1) {
return -1;
}
if (states[i]->frame != frame) {
return -1;
}
return i;
}
bool ModifierSimulationCache::has_state_at_frame(const SubFrame &frame) const
{
std::lock_guard lock(states_at_frames_mutex_);
return find_state_at_frame_exact(states_at_frames_, frame) != -1;
}
bool ModifierSimulationCache::has_states() const
{
std::lock_guard lock(states_at_frames_mutex_);
return !states_at_frames_.is_empty();
}
const ModifierSimulationState *ModifierSimulationCache::get_state_at_exact_frame(
const SubFrame &frame) const
{
std::lock_guard lock(states_at_frames_mutex_);
const int64_t i = find_state_at_frame_exact(states_at_frames_, frame);
if (i == -1) {
return nullptr;
}
return &states_at_frames_[i]->state;
}
ModifierSimulationState &ModifierSimulationCache::get_state_at_frame_for_write(
const SubFrame &frame)
{
std::lock_guard lock(states_at_frames_mutex_);
const int64_t i = find_state_at_frame_exact(states_at_frames_, frame);
if (i != -1) {
return states_at_frames_[i]->state;
}
if (!states_at_frames_.is_empty()) {
BLI_assert(frame > states_at_frames_.last()->frame);
}
states_at_frames_.append(std::make_unique<ModifierSimulationStateAtFrame>());
states_at_frames_.last()->frame = frame;
states_at_frames_.last()->state.owner_ = this;
return states_at_frames_.last()->state;
}
StatesAroundFrame ModifierSimulationCache::get_states_around_frame(const SubFrame &frame) const
{
std::lock_guard lock(states_at_frames_mutex_);
const int64_t i = find_state_at_frame(states_at_frames_, frame);
StatesAroundFrame states_around_frame{};
if (i == -1) {
if (!states_at_frames_.is_empty() && states_at_frames_.last()->frame < frame) {
states_around_frame.prev = states_at_frames_.last().get();
}
return states_around_frame;
}
if (states_at_frames_[i]->frame == frame) {
states_around_frame.current = states_at_frames_[i].get();
}
if (i > 0) {
states_around_frame.prev = states_at_frames_[i - 1].get();
}
if (i < states_at_frames_.size() - 2) {
states_around_frame.next = states_at_frames_[i + 1].get();
}
return states_around_frame;
}
SimulationZoneState *ModifierSimulationState::get_zone_state(const SimulationZoneID &zone_id)
{
std::lock_guard lock{mutex_};
if (auto *ptr = zone_states_.lookup_ptr(zone_id)) {
return ptr->get();
}
return nullptr;
}
const SimulationZoneState *ModifierSimulationState::get_zone_state(
const SimulationZoneID &zone_id) const
{
std::lock_guard lock{mutex_};
if (auto *ptr = zone_states_.lookup_ptr(zone_id)) {
return ptr->get();
}
return nullptr;
}
SimulationZoneState &ModifierSimulationState::get_zone_state_for_write(
const SimulationZoneID &zone_id)
{
std::lock_guard lock{mutex_};
return *zone_states_.lookup_or_add_cb(zone_id,
[]() { return std::make_unique<SimulationZoneState>(); });
}
void ModifierSimulationState::ensure_bake_loaded(const bNodeTree &ntree) const
{
std::scoped_lock lock{mutex_};
if (bake_loaded_) {
return;
}
if (!meta_path_ || !bdata_dir_) {
return;
}
const std::shared_ptr<io::serialize::Value> io_root_value = io::serialize::read_json_file(
*meta_path_);
if (!io_root_value) {
return;
}
const DictionaryValue *io_root = io_root_value->as_dictionary_value();
if (!io_root) {
return;
}
const DiskBDataReader bdata_reader{*bdata_dir_};
deserialize_modifier_simulation_state(ntree,
*io_root,
bdata_reader,
*owner_->bdata_sharing_,
const_cast<ModifierSimulationState &>(*this));
bake_loaded_ = true;
}
void ModifierSimulationCache::reset()
{
std::lock_guard lock(states_at_frames_mutex_);
states_at_frames_.clear();
bdata_sharing_.reset();
this->realtime_cache.current_state.reset();
this->realtime_cache.prev_state.reset();
this->frame_caches.clear();
this->prev_state.reset();
this->bdata_dir.reset();
this->bdata_sharing.reset();
this->failed_finding_bake = false;
this->cache_state = CacheState::Valid;
}
@@ -239,10 +40,52 @@ void scene_simulation_states_reset(Scene &scene)
continue;
}
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
nmd->runtime->simulation_cache->reset();
if (!nmd->runtime->simulation_cache) {
continue;
}
for (auto item : nmd->runtime->simulation_cache->cache_by_zone_id.items()) {
item.value->reset();
}
}
}
FOREACH_SCENE_OBJECT_END;
}
std::optional<std::string> get_modifier_simulation_bake_path(const Main &bmain,
const Object &object,
const NodesModifierData &nmd)
{
const StringRefNull bmain_path = BKE_main_blendfile_path(&bmain);
if (bmain_path.is_empty()) {
return std::nullopt;
}
if (StringRef(nmd.simulation_bake_directory).is_empty()) {
return std::nullopt;
}
const char *base_path = ID_BLEND_PATH(&bmain, &object.id);
char absolute_bake_dir[FILE_MAX];
STRNCPY(absolute_bake_dir, nmd.simulation_bake_directory);
BLI_path_abs(absolute_bake_dir, base_path);
return absolute_bake_dir;
}
std::optional<bake_paths::BakePath> get_simulation_zone_bake_path(const Main &bmain,
const Object &object,
const NodesModifierData &nmd,
int zone_id)
{
const std::optional<std::string> modifier_bake_path = get_modifier_simulation_bake_path(
bmain, object, nmd);
if (!modifier_bake_path) {
return std::nullopt;
}
char zone_bake_dir[FILE_MAX];
BLI_path_join(zone_bake_dir,
sizeof(zone_bake_dir),
modifier_bake_path->c_str(),
std::to_string(zone_id).c_str());
return bake_paths::BakePath::from_single_root(zone_bake_dir);
}
} // namespace blender::bke::sim

View File

@@ -83,111 +83,4 @@ std::string get_default_modifier_bake_directory(const Main &bmain,
return dir;
}
/**
* Version written to the baked data.
*/
static constexpr int serialize_format_version = 2;
void serialize_modifier_simulation_state(const ModifierSimulationState &state,
BDataWriter &bdata_writer,
BDataSharing &bdata_sharing,
DictionaryValue &r_io_root)
{
r_io_root.append_int("version", serialize_format_version);
auto io_zones = r_io_root.append_array("zones");
for (const auto item : state.zone_states_.items()) {
const SimulationZoneID &zone_id = item.key;
const SimulationZoneState &zone_state = *item.value;
auto io_zone = io_zones->append_dict();
io_zone->append_int("state_id", zone_id.nested_node_id);
auto io_state_items = io_zone->append_array("state_items");
for (const MapItem<int, std::unique_ptr<BakeItem>> &state_item_with_id :
zone_state.item_by_identifier.items())
{
auto io_state_item = io_state_items->append_dict();
io_state_item->append_int("id", state_item_with_id.key);
serialize_bake_item(*state_item_with_id.value, bdata_writer, bdata_sharing, *io_state_item);
}
}
}
void deserialize_modifier_simulation_state(const bNodeTree &ntree,
const DictionaryValue &io_root,
const BDataReader &bdata_reader,
const BDataSharing &bdata_sharing,
ModifierSimulationState &r_state)
{
io::serialize::JsonFormatter formatter;
const std::optional<int> version = io_root.lookup_int("version");
if (!version) {
return;
}
if (*version > serialize_format_version) {
return;
}
const io::serialize::ArrayValue *io_zones = io_root.lookup_array("zones");
if (!io_zones) {
return;
}
for (const auto &io_zone_value : io_zones->elements()) {
const DictionaryValue *io_zone = io_zone_value->as_dictionary_value();
if (!io_zone) {
continue;
}
bke::sim::SimulationZoneID zone_id;
if (const std::optional<int> state_id = io_zone->lookup_int("state_id")) {
zone_id.nested_node_id = *state_id;
}
else if (const io::serialize::ArrayValue *io_zone_id = io_zone->lookup_array("zone_id")) {
/* In the initial release of simulation nodes, the entire node id path was written to the
* baked data. For backward compatibility the node ids are read here and then the nested node
* id is looked up. */
Vector<int> node_ids;
for (const auto &io_zone_id_element : io_zone_id->elements()) {
const io::serialize::IntValue *io_node_id = io_zone_id_element->as_int_value();
if (!io_node_id) {
continue;
}
node_ids.append(io_node_id->value());
}
const bNestedNodeRef *nested_node_ref = ntree.nested_node_ref_from_node_id_path(node_ids);
if (!nested_node_ref) {
continue;
}
zone_id.nested_node_id = nested_node_ref->id;
}
const io::serialize::ArrayValue *io_state_items = io_zone->lookup_array("state_items");
if (!io_state_items) {
continue;
}
auto zone_state = std::make_unique<bke::sim::SimulationZoneState>();
for (const auto &io_state_item_value : io_state_items->elements()) {
const DictionaryValue *io_state_item = io_state_item_value->as_dictionary_value();
if (!io_state_item) {
continue;
}
const std::optional<int> state_item_id = io_state_item->lookup_int("id");
if (!state_item_id) {
continue;
}
std::unique_ptr<BakeItem> new_state_item = deserialize_bake_item(
*io_state_item, bdata_reader, bdata_sharing);
BLI_assert(new_state_item);
zone_state->item_by_identifier.add(*state_item_id, std::move(new_state_item));
}
r_state.zone_states_.add_overwrite(zone_id, std::move(zone_state));
}
}
} // namespace blender::bke::sim

View File

@@ -85,11 +85,6 @@ struct SubFrame {
{
return a.frame_ >= b.frame_ || (a.frame_ == b.frame_ && a.subframe_ >= b.subframe_);
}
friend std::ostream &operator<<(std::ostream &stream, const SubFrame &a)
{
return stream << float(a);
}
};
} // namespace blender

View File

@@ -36,6 +36,7 @@
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_mesh.hh"
#include "BKE_node_runtime.hh"
#include "BKE_object.h"
#include "BKE_pointcloud.h"
#include "BKE_report.h"
@@ -98,7 +99,14 @@ static void calculate_simulation_job_startjob(void *customdata,
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
nmd->runtime->simulation_cache->reset();
if (!nmd->runtime->simulation_cache) {
continue;
}
for (auto item : nmd->runtime->simulation_cache->cache_by_zone_id.items()) {
if (item.value->cache_state != CacheState::Baked) {
item.value->reset();
}
}
}
}
objects_to_calc.append(object);
@@ -214,10 +222,15 @@ static bool bake_simulation_poll(bContext *C)
return true;
}
struct ZoneBakeData {
int zone_id;
bke::bake_paths::BakePath path;
std::unique_ptr<bke::BDataSharing> bdata_sharing;
};
struct ModifierBakeData {
NodesModifierData *nmd;
std::string absolute_bake_dir;
std::unique_ptr<bke::BDataSharing> bdata_sharing;
Vector<ZoneBakeData> zones;
};
struct ObjectBakeData {
@@ -251,19 +264,38 @@ static void bake_simulation_job_startjob(void *customdata,
continue;
}
const char *base_path = ID_BLEND_PATH(job.bmain, &object->id);
ObjectBakeData bake_data;
bake_data.object = object;
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
nmd->runtime->simulation_cache->reset();
char absolute_bake_dir[FILE_MAX];
STRNCPY(absolute_bake_dir, nmd->simulation_bake_directory);
BLI_path_abs(absolute_bake_dir, base_path);
bake_data.modifiers.append(
{nmd, absolute_bake_dir, std::make_unique<bke::BDataSharing>()});
if (!nmd->node_group) {
continue;
}
if (!nmd->runtime->simulation_cache) {
continue;
}
ModifierBakeData modifier_bake_data;
modifier_bake_data.nmd = nmd;
for (auto item : nmd->runtime->simulation_cache->cache_by_zone_id.items()) {
item.value->reset();
}
for (const bNestedNodeRef &nested_node_ref : nmd->node_group->nested_node_refs_span()) {
ZoneBakeData zone_bake_data;
zone_bake_data.zone_id = nested_node_ref.id;
zone_bake_data.bdata_sharing = std::make_unique<bke::BDataSharing>();
if (std::optional<bke::bake_paths::BakePath> path =
bke::sim::get_simulation_zone_bake_path(
*job.bmain, *object, *nmd, nested_node_ref.id))
{
zone_bake_data.path = std::move(*path);
modifier_bake_data.zones.append(std::move(zone_bake_data));
}
}
bake_data.modifiers.append(std::move(modifier_bake_data));
}
}
objects_to_bake.append(std::move(bake_data));
@@ -288,51 +320,56 @@ static void bake_simulation_job_startjob(void *customdata,
job.scene->r.cfra = frame.frame();
job.scene->r.subframe = frame.subframe();
char frame_file_c_str[64];
SNPRINTF(frame_file_c_str, "%011.5f", double(frame));
BLI_string_replace_char(frame_file_c_str, '.', '_');
const StringRefNull frame_file_str = frame_file_c_str;
BKE_scene_graph_update_for_newframe(job.depsgraph);
const std::string frame_file_name = bke::bake_paths::frame_to_file_name(frame);
for (ObjectBakeData &object_bake_data : objects_to_bake) {
for (ModifierBakeData &modifier_bake_data : object_bake_data.modifiers) {
NodesModifierData &nmd = *modifier_bake_data.nmd;
if (!nmd.runtime->simulation_cache) {
continue;
const ModifierSimulationCache &simulation_cache = *nmd.runtime->simulation_cache;
for (ZoneBakeData &zone_bake_data : modifier_bake_data.zones) {
if (!simulation_cache.cache_by_zone_id.contains(zone_bake_data.zone_id)) {
continue;
}
const SimulationZoneCache &zone_cache = *simulation_cache.cache_by_zone_id.lookup(
zone_bake_data.zone_id);
if (zone_cache.frame_caches.is_empty()) {
continue;
}
const SimulationZoneFrameCache &frame_cache = *zone_cache.frame_caches.last();
if (frame_cache.frame != frame) {
continue;
}
const bke::bake_paths::BakePath path = zone_bake_data.path;
const std::string bdata_file_name = frame_file_name + ".bdata";
char bdata_path[FILE_MAX];
BLI_path_join(
bdata_path, sizeof(bdata_path), path.bdata_dir.c_str(), bdata_file_name.c_str());
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);
BLI_file_ensure_parent_dir_exists(bdata_path);
fstream bdata_file{bdata_path, std::ios::out | std::ios::binary};
bke::DiskBDataWriter bdata_writer{bdata_file_name, bdata_file, 0};
io::serialize::DictionaryValue io_root;
io_root.append_int("version", simulation_file_storage_version);
io::serialize::DictionaryValue &io_items = *io_root.append_dict("items");
for (auto item : frame_cache.items.items()) {
io::serialize::DictionaryValue &io_item = *io_items.append_dict(
std::to_string(item.key));
bke::serialize_bake_item(
*item.value, bdata_writer, *zone_bake_data.bdata_sharing, io_item);
}
io::serialize::write_json_file(meta_path, io_root);
}
ModifierSimulationCache &sim_cache = *nmd.runtime->simulation_cache;
const ModifierSimulationState *sim_state = sim_cache.get_state_at_exact_frame(frame);
if (sim_state == nullptr || sim_state->zone_states_.is_empty()) {
continue;
}
const std::string bdata_file_name = frame_file_str + ".bdata";
const std::string meta_file_name = frame_file_str + ".json";
char bdata_path[FILE_MAX];
BLI_path_join(bdata_path,
sizeof(bdata_path),
modifier_bake_data.absolute_bake_dir.c_str(),
"bdata",
bdata_file_name.c_str());
char meta_path[FILE_MAX];
BLI_path_join(meta_path,
sizeof(meta_path),
modifier_bake_data.absolute_bake_dir.c_str(),
"meta",
meta_file_name.c_str());
BLI_file_ensure_parent_dir_exists(bdata_path);
fstream bdata_file{bdata_path, std::ios::out | std::ios::binary};
bke::DiskBDataWriter bdata_writer{bdata_file_name, bdata_file, 0};
io::serialize::DictionaryValue io_root;
bke::sim::serialize_modifier_simulation_state(
*sim_state, bdata_writer, *modifier_bake_data.bdata_sharing, io_root);
BLI_file_ensure_parent_dir_exists(meta_path);
io::serialize::write_json_file(meta_path, io_root);
}
}
@@ -343,9 +380,13 @@ static void bake_simulation_job_startjob(void *customdata,
for (ObjectBakeData &object_bake_data : objects_to_bake) {
for (ModifierBakeData &modifier_bake_data : object_bake_data.modifiers) {
NodesModifierData &nmd = *modifier_bake_data.nmd;
if (nmd.runtime->simulation_cache) {
/* Tag the caches as being baked so that they are not changed anymore. */
nmd.runtime->simulation_cache->cache_state = CacheState::Baked;
for (ZoneBakeData &zone_bake_data : modifier_bake_data.zones) {
if (std::unique_ptr<SimulationZoneCache> &zone_cache =
nmd.runtime->simulation_cache->cache_by_zone_id.lookup(zone_bake_data.zone_id))
{
/* Tag the caches as being baked so that they are not changed anymore. */
zone_cache->cache_state = CacheState::Baked;
}
}
}
DEG_id_tag_update(&object_bake_data.object->id, ID_RECALC_GEOMETRY);
@@ -593,40 +634,45 @@ static int delete_baked_simulation_exec(bContext *C, wmOperator *op)
}
for (Object *object : objects) {
const char *base_path = ID_BLEND_PATH(bmain, &object->id);
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
nmd->runtime->simulation_cache->reset();
if (StringRef(nmd->simulation_bake_directory).is_empty()) {
if (!nmd->runtime->simulation_cache) {
continue;
}
char absolute_bake_dir[FILE_MAX];
STRNCPY(absolute_bake_dir, nmd->simulation_bake_directory);
BLI_path_abs(absolute_bake_dir, base_path);
for (auto item : nmd->runtime->simulation_cache->cache_by_zone_id.items()) {
item.value->reset();
char meta_dir[FILE_MAX];
BLI_path_join(meta_dir, sizeof(meta_dir), absolute_bake_dir, "meta");
char bdata_dir[FILE_MAX];
BLI_path_join(bdata_dir, sizeof(bdata_dir), absolute_bake_dir, "bdata");
const std::optional<bke::bake_paths::BakePath> bake_path =
bke::sim::get_simulation_zone_bake_path(*bmain, *object, *nmd, item.key);
if (!bake_path) {
continue;
}
if (BLI_exists(absolute_bake_dir)) {
const char *meta_dir = bake_path->meta_dir.c_str();
if (BLI_exists(meta_dir)) {
if (BLI_delete(meta_dir, true, true)) {
BKE_reportf(op->reports, RPT_ERROR, "Failed to remove meta directory %s", meta_dir);
}
}
const char *bdata_dir = bake_path->bdata_dir.c_str();
if (BLI_exists(bdata_dir)) {
if (BLI_delete(bdata_dir, true, true)) {
BKE_reportf(
op->reports, RPT_ERROR, "Failed to remove bdata directory %s", bdata_dir);
}
}
/* Delete the folder if it's empty. */
BLI_delete(absolute_bake_dir, true, false);
if (bake_path->bake_dir.has_value()) {
const char *zone_bake_dir = bake_path->bake_dir->c_str();
/* Try to delete zone bake directory if it is empty. */
BLI_delete(zone_bake_dir, true, false);
}
}
else {
BKE_reportf(op->reports, RPT_ERROR, "Bake directory %s not found", absolute_bake_dir);
if (const std::optional<std::string> modifier_bake_dir =
bke::sim::get_modifier_simulation_bake_path(*bmain, *object, *nmd))
{
/* Try to delete modifier bake directory if it is empty. */
BLI_delete(modifier_bake_dir->c_str(), true, false);
}
}
}

View File

@@ -748,19 +748,30 @@ static void timeline_cache_draw_single(PTCacheID *pid, float y_offset, float hei
}
static void timeline_cache_draw_simulation_nodes(
const Scene &scene,
const blender::bke::sim::ModifierSimulationCache &cache,
const float y_offset,
const float height,
const uint pos_id)
{
std::lock_guard lock{cache.mutex};
if (cache.cache_by_zone_id.is_empty()) {
return;
}
/* Draw the state if one of the simulation zones. This is fine for now, because there is no ui
* that allows caching zones independently. */
const blender::bke::sim::SimulationZoneCache &zone_cache =
**cache.cache_by_zone_id.values().begin();
if (zone_cache.frame_caches.is_empty()) {
return;
}
GPU_matrix_push();
GPU_matrix_translate_2f(0.0, float(V2D_SCROLL_HANDLE_HEIGHT) + y_offset);
GPU_matrix_scale_2f(1.0, height);
float color[4];
UI_GetThemeColor4fv(TH_SIMULATED_FRAMES, color);
switch (cache.cache_state) {
switch (zone_cache.cache_state) {
case blender::bke::sim::CacheState::Invalid: {
color[3] = 0.4f;
break;
@@ -777,16 +788,13 @@ static void timeline_cache_draw_simulation_nodes(
immUniformColor4fv(color);
const int start_frame = scene.r.sfra;
const int end_frame = scene.r.efra;
const int frames_num = end_frame - start_frame + 1;
const blender::IndexRange frames_range(start_frame, frames_num);
immBeginAtMost(GPU_PRIM_TRIS, zone_cache.frame_caches.size() * 6);
immBeginAtMost(GPU_PRIM_TRIS, frames_num * 6);
for (const int frame : frames_range) {
if (cache.has_state_at_frame(frame)) {
immRectf_fast(pos_id, frame - 0.5f, 0, frame + 0.5f, 1.0f);
}
for (const std::unique_ptr<blender::bke::sim::SimulationZoneFrameCache> &frame_cache :
zone_cache.frame_caches.as_span())
{
const int frame = frame_cache->frame.frame();
immRectf_fast(pos_id, frame - 0.5f, 0, frame + 0.5f, 1.0f);
}
immEnd();
@@ -840,7 +848,7 @@ void timeline_draw_cache(const SpaceAction *saction, const Object *ob, const Sce
continue;
}
timeline_cache_draw_simulation_nodes(
*scene, *nmd->runtime->simulation_cache, y_offset, cache_draw_height, pos_id);
*nmd->runtime->simulation_cache, y_offset, cache_draw_height, pos_id);
y_offset += cache_draw_height;
}
}

View File

@@ -660,154 +660,400 @@ static void check_property_socket_sync(const Object *ob, ModifierData *md)
}
}
static void prepare_simulation_states_for_evaluation(const NodesModifierData &nmd,
const ModifierEvalContext &ctx,
nodes::GeoNodesModifierData &exec_data)
{
if (!nmd.runtime->simulation_cache) {
return;
}
const Main *bmain = DEG_get_bmain(ctx.depsgraph);
const SubFrame current_frame = DEG_get_ctime(ctx.depsgraph);
const Scene *scene = DEG_get_input_scene(ctx.depsgraph);
const SubFrame start_frame = scene->r.sfra;
const bool is_start_frame = current_frame == start_frame;
namespace sim_input = nodes::sim_input;
namespace sim_output = nodes::sim_output;
/* This cache may be shared between original and evaluated modifiers. */
blender::bke::sim::ModifierSimulationCache &simulation_cache = *nmd.runtime->simulation_cache;
class NodesModifierSimulationParams : public nodes::GeoNodesSimulationParams {
private:
static constexpr float max_delta_frames = 1.0f;
mutable Map<int, std::unique_ptr<nodes::SimulationZoneBehavior>> behavior_by_zone_id_;
const NodesModifierData &nmd_;
const ModifierEvalContext &ctx_;
const Main *bmain_;
SubFrame current_frame_;
SubFrame start_frame_;
bool is_start_frame_;
bool use_frame_cache_;
bool depsgraph_is_active_;
bke::sim::ModifierSimulationCache *simulation_cache_;
float fps_;
public:
NodesModifierSimulationParams(NodesModifierData &nmd, const ModifierEvalContext &ctx)
: nmd_(nmd), ctx_(ctx)
{
/* Try to use baked data. */
const StringRefNull bmain_path = BKE_main_blendfile_path(bmain);
if (simulation_cache.cache_state != bke::sim::CacheState::Baked && !bmain_path.is_empty()) {
if (!StringRef(nmd.simulation_bake_directory).is_empty()) {
if (const char *base_path = ID_BLEND_PATH(bmain, &ctx.object->id)) {
char absolute_bake_dir[FILE_MAX];
STRNCPY(absolute_bake_dir, nmd.simulation_bake_directory);
BLI_path_abs(absolute_bake_dir, base_path);
simulation_cache.try_discover_bake(absolute_bake_dir);
}
}
const Depsgraph *depsgraph = ctx_.depsgraph;
bmain_ = DEG_get_bmain(depsgraph);
current_frame_ = DEG_get_ctime(depsgraph);
const Scene *scene = DEG_get_input_scene(depsgraph);
start_frame_ = scene->r.sfra;
is_start_frame_ = current_frame_ == start_frame_;
use_frame_cache_ = ctx_.object->flag & OB_FLAG_USE_SIMULATION_CACHE;
depsgraph_is_active_ = DEG_is_active(depsgraph);
simulation_cache_ = nmd.runtime->simulation_cache.get();
fps_ = FPS;
if (!simulation_cache_) {
return;
}
}
if (ctx.object->flag & OB_FLAG_USE_SIMULATION_CACHE) {
if (DEG_is_active(ctx.depsgraph)) {
{
/* Invalidate cached data on user edits. */
if (nmd.modifier.flag & eModifierFlag_UserModified) {
if (simulation_cache.cache_state != bke::sim::CacheState::Baked) {
simulation_cache.invalidate();
}
}
}
{
/* Reset cached data if necessary. */
const bke::sim::StatesAroundFrame sim_states = simulation_cache.get_states_around_frame(
current_frame);
if (simulation_cache.cache_state == bke::sim::CacheState::Invalid &&
(current_frame == start_frame ||
(sim_states.current == nullptr && sim_states.prev == nullptr &&
sim_states.next != nullptr)))
std::lock_guard lock{simulation_cache_->mutex};
if (depsgraph_is_active_) {
/* Invalidate data on user edits. */
if (nmd.modifier.flag & eModifierFlag_UserModified) {
for (std::unique_ptr<bke::sim::SimulationZoneCache> &zone_cache :
simulation_cache_->cache_by_zone_id.values())
{
simulation_cache.reset();
}
}
/* Decide if a new simulation state should be created in this evaluation. */
const bke::sim::StatesAroundFrame sim_states = simulation_cache.get_states_around_frame(
current_frame);
if (simulation_cache.cache_state != bke::sim::CacheState::Baked) {
if (sim_states.current == nullptr) {
if (is_start_frame || !simulation_cache.has_states()) {
bke::sim::ModifierSimulationState &current_sim_state =
simulation_cache.get_state_at_frame_for_write(current_frame);
exec_data.current_simulation_state_for_write = &current_sim_state;
exec_data.simulation_time_delta = 0.0f;
if (!is_start_frame) {
/* When starting a new simulation at another frame than the start frame,
* it can't match what would be baked, so invalidate it immediately. */
simulation_cache.invalidate();
}
}
else if (sim_states.prev != nullptr && sim_states.next == nullptr) {
const float max_delta_frames = 1.0f;
const float scene_delta_frames = float(current_frame) - float(sim_states.prev->frame);
const float delta_frames = std::min(max_delta_frames, scene_delta_frames);
if (delta_frames != scene_delta_frames) {
simulation_cache.invalidate();
}
bke::sim::ModifierSimulationState &current_sim_state =
simulation_cache.get_state_at_frame_for_write(current_frame);
exec_data.current_simulation_state_for_write = &current_sim_state;
const float delta_seconds = delta_frames / FPS;
exec_data.simulation_time_delta = delta_seconds;
if (zone_cache->cache_state != bke::sim::CacheState::Baked) {
zone_cache->cache_state = bke::sim::CacheState::Invalid;
}
}
}
}
/* Load read-only states to give nodes access to cached data. */
const bke::sim::StatesAroundFrame sim_states = simulation_cache.get_states_around_frame(
current_frame);
if (sim_states.current) {
sim_states.current->state.ensure_bake_loaded(*nmd.node_group);
exec_data.current_simulation_state = &sim_states.current->state;
}
if (sim_states.prev) {
sim_states.prev->state.ensure_bake_loaded(*nmd.node_group);
exec_data.prev_simulation_state = &sim_states.prev->state;
if (sim_states.next) {
sim_states.next->state.ensure_bake_loaded(*nmd.node_group);
exec_data.next_simulation_state = &sim_states.next->state;
exec_data.simulation_state_mix_factor =
(float(current_frame) - float(sim_states.prev->frame)) /
(float(sim_states.next->frame) - float(sim_states.prev->frame));
/* Reset cached data if necessary. */
if (is_start_frame_) {
for (std::unique_ptr<bke::sim::SimulationZoneCache> &zone_cache :
simulation_cache_->cache_by_zone_id.values())
{
if (zone_cache->cache_state == bke::sim::CacheState::Invalid) {
zone_cache->reset();
}
}
}
}
}
else {
if (DEG_is_active(ctx.depsgraph)) {
bke::sim::ModifierSimulationCacheRealtime &realtime_cache = simulation_cache.realtime_cache;
if (current_frame < realtime_cache.prev_frame) {
/* Reset the cache when going backwards in time. */
simulation_cache.reset();
nodes::SimulationZoneBehavior *get(const int zone_id) const override
{
if (!simulation_cache_) {
return nullptr;
}
std::lock_guard lock{simulation_cache_->mutex};
return behavior_by_zone_id_
.lookup_or_add_cb(zone_id,
[&]() {
auto info = std::make_unique<nodes::SimulationZoneBehavior>();
this->init_simulation_info(zone_id, *info);
return info;
})
.get();
}
struct FrameIndices {
std::optional<int> prev;
std::optional<int> current;
std::optional<int> next;
};
void init_simulation_info(const int zone_id, nodes::SimulationZoneBehavior &zone_behavior) const
{
using namespace bke::sim;
SimulationZoneCache &zone_cache = *simulation_cache_->cache_by_zone_id.lookup_or_add_cb(
zone_id, []() { return std::make_unique<SimulationZoneCache>(); });
/* Try load baked data. */
if (!zone_cache.failed_finding_bake) {
if (zone_cache.cache_state != CacheState::Baked) {
if (std::optional<bke::bake_paths::BakePath> zone_bake_path =
get_simulation_zone_bake_path(*bmain_, *ctx_.object, nmd_, zone_id))
{
Vector<bke::bake_paths::MetaFile> meta_files = bke::bake_paths::find_sorted_meta_files(
zone_bake_path->meta_dir);
if (!meta_files.is_empty()) {
zone_cache.reset();
for (const bke::bake_paths::MetaFile &meta_file : meta_files) {
auto frame_cache = std::make_unique<SimulationZoneFrameCache>();
frame_cache->frame = meta_file.frame;
frame_cache->meta_path = meta_file.path;
zone_cache.frame_caches.append(std::move(frame_cache));
}
zone_cache.bdata_dir = zone_bake_path->bdata_dir;
zone_cache.bdata_sharing = std::make_unique<bke::BDataSharing>();
zone_cache.cache_state = CacheState::Baked;
}
}
}
if (realtime_cache.current_frame == current_frame && realtime_cache.current_state) {
/* Don't simulate in the same frame again. */
exec_data.current_simulation_state = realtime_cache.current_state.get();
if (zone_cache.cache_state != CacheState::Baked) {
zone_cache.failed_finding_bake = true;
}
}
const FrameIndices frame_indices = this->get_frame_indices(zone_cache);
if (zone_cache.cache_state == CacheState::Baked) {
this->read_from_cache(frame_indices, zone_cache, zone_behavior);
return;
}
if (use_frame_cache_) {
/* If the depsgraph is active, we allow creating new simulation states. Otherwise, the access
* is read-only. */
if (depsgraph_is_active_) {
if (zone_cache.frame_caches.is_empty()) {
/* Initialize the simulation. */
this->input_pass_through(zone_behavior);
this->output_store_frame_cache(zone_cache, zone_behavior);
if (!is_start_frame_) {
/* If we initialize at a frame that is not the start frame, the simulation is not
* valid. */
zone_cache.cache_state = CacheState::Invalid;
}
return;
}
if (frame_indices.prev && !frame_indices.current && !frame_indices.next) {
/* Read the previous frame's data and store the newly computed simulation state. */
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
const bke::sim::SimulationZoneFrameCache &prev_frame_cache =
*zone_cache.frame_caches[*frame_indices.prev];
const float delta_frames = std::min(
max_delta_frames, float(current_frame_) - float(prev_frame_cache.frame));
if (delta_frames != 1) {
zone_cache.cache_state = CacheState::Invalid;
}
output_copy_info.delta_time = delta_frames / fps_;
output_copy_info.items_by_id = this->to_readonly_items_map(prev_frame_cache.items);
this->output_store_frame_cache(zone_cache, zone_behavior);
return;
}
}
this->read_from_cache(frame_indices, zone_cache, zone_behavior);
return;
}
/* When there is no per-frame cache, check if there is a previous state. */
if (zone_cache.prev_state) {
if (zone_cache.prev_state->frame < current_frame_) {
/* Do a simulation step. */
const float delta_frames = std::min(
max_delta_frames, float(zone_cache.prev_state->frame) - float(current_frame_));
auto &output_move_info = zone_behavior.input.emplace<sim_input::OutputMove>();
output_move_info.delta_time = delta_frames / fps_;
output_move_info.items_by_id = std::move(zone_cache.prev_state->items);
this->store_as_prev_items(zone_cache, zone_behavior);
return;
}
/* Advance in time, making the last "current" state the new "previous" state. */
realtime_cache.prev_frame = realtime_cache.current_frame;
realtime_cache.prev_state = std::move(realtime_cache.current_state);
if (realtime_cache.prev_state) {
exec_data.prev_simulation_state_mutable = realtime_cache.prev_state.get();
if (zone_cache.prev_state->frame == current_frame_) {
/* Just read from the previous state if the frame has not changed. */
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
output_copy_info.delta_time = 0.0f;
output_copy_info.items_by_id = this->to_readonly_items_map(zone_cache.prev_state->items);
auto &read_single_info = zone_behavior.output.emplace<sim_output::ReadSingle>();
read_single_info.items_by_id = this->to_readonly_items_map(zone_cache.prev_state->items);
return;
}
if (!depsgraph_is_active_) {
/* There is no previous state, and it's not possible to initialize the simulation because
* the depsgraph is not active. */
zone_behavior.input.emplace<sim_input::PassThrough>();
zone_behavior.output.emplace<sim_output::PassThrough>();
return;
}
/* Reset the simulation when the scene time moved backwards. */
zone_cache.prev_state.reset();
}
zone_behavior.input.emplace<sim_input::PassThrough>();
if (depsgraph_is_active_) {
/* Initialize the simulation. */
this->store_as_prev_items(zone_cache, zone_behavior);
}
else {
zone_behavior.output.emplace<sim_output::PassThrough>();
}
}
/* Create a new current state used to pass the data to the next frame. */
realtime_cache.current_state = std::make_unique<bke::sim::ModifierSimulationState>();
realtime_cache.current_frame = current_frame;
exec_data.current_simulation_state_for_write = realtime_cache.current_state.get();
exec_data.current_simulation_state = exec_data.current_simulation_state_for_write;
FrameIndices get_frame_indices(const bke::sim::SimulationZoneCache &zone_cache) const
{
FrameIndices frame_indices;
if (!zone_cache.frame_caches.is_empty()) {
const int first_future_frame_index = binary_search::find_predicate_begin(
zone_cache.frame_caches,
[&](const std::unique_ptr<bke::sim::SimulationZoneFrameCache> &value) {
return value->frame > current_frame_;
});
frame_indices.next = (first_future_frame_index == zone_cache.frame_caches.size()) ?
std::nullopt :
std::optional<int>(first_future_frame_index);
if (first_future_frame_index > 0) {
const int index = first_future_frame_index - 1;
if (zone_cache.frame_caches[index]->frame < current_frame_) {
frame_indices.prev = index;
}
else {
BLI_assert(zone_cache.frame_caches[index]->frame == current_frame_);
frame_indices.current = index;
if (index > 0) {
frame_indices.prev = index - 1;
}
}
}
}
return frame_indices;
}
/* Calculate the delta time. */
if (realtime_cache.prev_state) {
const float max_delta_frames = 1.0f;
const float scene_delta_frames = float(current_frame) - float(realtime_cache.prev_frame);
const float delta_frames = std::min(max_delta_frames, scene_delta_frames);
const float delta_seconds = delta_frames / FPS;
exec_data.simulation_time_delta = delta_seconds;
void input_pass_through(nodes::SimulationZoneBehavior &zone_behavior) const
{
zone_behavior.input.emplace<sim_input::PassThrough>();
}
void output_store_frame_cache(bke::sim::SimulationZoneCache &zone_cache,
nodes::SimulationZoneBehavior &zone_behavior) const
{
auto &store_and_pass_through_info =
zone_behavior.output.emplace<sim_output::StoreAndPassThrough>();
store_and_pass_through_info.store_fn =
[simulation_cache = simulation_cache_,
zone_cache = &zone_cache,
current_frame = current_frame_](Map<int, std::unique_ptr<bke::BakeItem>> items) {
std::lock_guard lock{simulation_cache->mutex};
auto frame_cache = std::make_unique<bke::sim::SimulationZoneFrameCache>();
frame_cache->frame = current_frame;
frame_cache->items = std::move(items);
zone_cache->frame_caches.append(std::move(frame_cache));
};
}
void store_as_prev_items(bke::sim::SimulationZoneCache &zone_cache,
nodes::SimulationZoneBehavior &zone_behavior) const
{
auto &store_and_pass_through_info =
zone_behavior.output.emplace<sim_output::StoreAndPassThrough>();
store_and_pass_through_info.store_fn =
[simulation_cache = simulation_cache_,
zone_cache = &zone_cache,
current_frame = current_frame_](Map<int, std::unique_ptr<bke::BakeItem>> items) {
std::lock_guard lock{simulation_cache->mutex};
if (!zone_cache->prev_state) {
zone_cache->prev_state.emplace();
}
zone_cache->prev_state->items = std::move(items);
zone_cache->prev_state->frame = current_frame;
};
}
void read_from_cache(const FrameIndices &frame_indices,
bke::sim::SimulationZoneCache &zone_cache,
nodes::SimulationZoneBehavior &zone_behavior) const
{
if (frame_indices.prev) {
auto &output_copy_info = zone_behavior.input.emplace<sim_input::OutputCopy>();
bke::sim::SimulationZoneFrameCache &frame_cache =
*zone_cache.frame_caches[*frame_indices.prev];
const float delta_frames = std::min(max_delta_frames,
float(current_frame_) - float(frame_cache.frame));
output_copy_info.delta_time = delta_frames / fps_;
for (auto item : frame_cache.items.items()) {
output_copy_info.items_by_id.add_new(item.key, item.value.get());
}
}
else {
zone_behavior.input.emplace<sim_input::PassThrough>();
}
if (frame_indices.current) {
this->read_single(*frame_indices.current, zone_cache, zone_behavior);
}
else if (frame_indices.next) {
if (frame_indices.prev) {
this->read_interpolated(
*frame_indices.prev, *frame_indices.next, zone_cache, zone_behavior);
}
else {
exec_data.simulation_time_delta = 0.0f;
this->read_empty(zone_behavior);
}
}
else if (frame_indices.prev) {
this->read_single(*frame_indices.prev, zone_cache, zone_behavior);
}
else {
this->read_empty(zone_behavior);
}
}
void read_empty(nodes::SimulationZoneBehavior &zone_behavior) const
{
zone_behavior.output.emplace<sim_output::ReadSingle>();
}
void read_single(const int frame_index,
bke::sim::SimulationZoneCache &zone_cache,
nodes::SimulationZoneBehavior &zone_behavior) const
{
bke::sim::SimulationZoneFrameCache &frame_cache = *zone_cache.frame_caches[frame_index];
this->ensure_bake_loaded(zone_cache, frame_cache);
auto &read_single_info = zone_behavior.output.emplace<sim_output::ReadSingle>();
read_single_info.items_by_id = this->to_readonly_items_map(frame_cache.items);
}
void read_interpolated(const int prev_frame_index,
const int next_frame_index,
bke::sim::SimulationZoneCache &zone_cache,
nodes::SimulationZoneBehavior &zone_behavior) const
{
bke::sim::SimulationZoneFrameCache &prev_frame_cache =
*zone_cache.frame_caches[prev_frame_index];
bke::sim::SimulationZoneFrameCache &next_frame_cache =
*zone_cache.frame_caches[next_frame_index];
this->ensure_bake_loaded(zone_cache, prev_frame_cache);
this->ensure_bake_loaded(zone_cache, next_frame_cache);
auto &read_interpolated_info = zone_behavior.output.emplace<sim_output::ReadInterpolated>();
read_interpolated_info.mix_factor = (float(current_frame_) - float(prev_frame_cache.frame)) /
(float(next_frame_cache.frame) -
float(prev_frame_cache.frame));
read_interpolated_info.prev_items_by_id = this->to_readonly_items_map(prev_frame_cache.items);
read_interpolated_info.next_items_by_id = this->to_readonly_items_map(next_frame_cache.items);
}
Map<int, const bke::BakeItem *> to_readonly_items_map(
const Map<int, std::unique_ptr<bke::BakeItem>> &items) const
{
Map<int, const bke::BakeItem *> map;
for (auto item : items.items()) {
map.add_new(item.key, item.value.get());
}
return map;
}
void ensure_bake_loaded(bke::sim::SimulationZoneCache &zone_cache,
bke::sim::SimulationZoneFrameCache &frame_cache) const
{
if (!frame_cache.items.is_empty()) {
return;
}
if (!zone_cache.bdata_dir) {
return;
}
if (!frame_cache.meta_path) {
return;
}
std::shared_ptr<io::serialize::Value> io_root_value = io::serialize::read_json_file(
*frame_cache.meta_path);
const io::serialize::DictionaryValue *io_root = io_root_value->as_dictionary_value();
if (!io_root) {
return;
}
if (io_root->lookup_int("version").value_or(0) != bke::sim::simulation_file_storage_version) {
return;
}
const io::serialize::DictionaryValue *io_items = io_root->lookup_dict("items");
if (!io_items) {
return;
}
bke::DiskBDataReader bdata_reader{*zone_cache.bdata_dir};
for (const auto &io_item_value : io_items->elements()) {
const io::serialize::DictionaryValue *io_item = io_item_value.second->as_dictionary_value();
if (!io_item) {
continue;
}
const int zone_id = std::stoi(io_item_value.first);
std::unique_ptr<bke::BakeItem> item = bke::deserialize_bake_item(
*io_item, bdata_reader, *zone_cache.bdata_sharing);
if (item) {
frame_cache.items.add(zone_id, std::move(item));
}
}
}
}
};
static void modifyGeometry(ModifierData *md,
const ModifierEvalContext *ctx,
@@ -868,7 +1114,8 @@ static void modifyGeometry(ModifierData *md,
modifier_eval_data.self_object = ctx->object;
auto eval_log = std::make_unique<geo_log::GeoModifierLog>();
prepare_simulation_states_for_evaluation(*nmd, *ctx, modifier_eval_data);
NodesModifierSimulationParams simulation_params(*nmd, *ctx);
modifier_eval_data.simulation_params = &simulation_params;
Set<ComputeContextHash> socket_log_contexts;
if (logging_enabled(ctx)) {

View File

@@ -20,6 +20,8 @@
* #lazy_function::Graph is build that can be used when evaluating the graph (e.g. for logging).
*/
#include <variant>
#include "FN_lazy_function_graph.hh"
#include "FN_lazy_function_graph_executor.hh"
@@ -40,6 +42,92 @@ namespace blender::nodes {
using lf::LazyFunction;
using mf::MultiFunction;
/** The structs in here describe the different possible behaviors of a simulation input node. */
namespace sim_input {
/**
* The data is just passed through the node. Data that is incompatible with simulations (like
* anonymous attributes), is removed though.
*/
struct PassThrough {
};
/**
* The input is not evaluated, instead the values provided here are output by the node.
*/
struct OutputCopy {
float delta_time;
Map<int, const bke::BakeItem *> items_by_id;
};
/**
* Same as above, but the values can be output by move, instead of copy. This can reduce the amount
* of unnecessary copies, when the old simulation state is not needed anymore.
*/
struct OutputMove {
float delta_time;
Map<int, std::unique_ptr<bke::BakeItem>> items_by_id;
};
using Behavior = std::variant<PassThrough, OutputCopy, OutputMove>;
} // namespace sim_input
/** The structs in here describe the different possible behaviors of a simulation output node. */
namespace sim_output {
/**
* The data is just passed through the node. Data that is incompatible with simulations (like
* anonymous attributes), is removed though.
*/
struct PassThrough {
};
/**
* Same as above, but also calls the given function with the data that is passed through the node.
* This allows the caller of geometry nodes (e.g. the modifier), to cache the new simulation state.
*/
struct StoreAndPassThrough {
std::function<void(Map<int, std::unique_ptr<bke::BakeItem>> items_by_id)> store_fn;
};
/**
* The inputs are not evaluated, instead the given cached items are output directly.
*/
struct ReadSingle {
Map<int, const bke::BakeItem *> items_by_id;
};
/**
* The inputs are not evaluated, instead of a mix of the two given states is output.
*/
struct ReadInterpolated {
/** Factor between 0 and 1 that determines the influence of the two simulation states. */
float mix_factor;
Map<int, const bke::BakeItem *> prev_items_by_id;
Map<int, const bke::BakeItem *> next_items_by_id;
};
using Behavior = std::variant<PassThrough, StoreAndPassThrough, ReadSingle, ReadInterpolated>;
} // namespace sim_output
/** Controls the behavior of one simulation zone. */
struct SimulationZoneBehavior {
sim_input::Behavior input;
sim_output::Behavior output;
};
class GeoNodesSimulationParams {
public:
/**
* Get the expected behavior for the simulation zone with the given id (see #bNestedNodeRef).
* It's possible that this method called multiple times for the same id. In this case, the same
* pointer should be returned in each call.
*/
virtual SimulationZoneBehavior *get(const int zone_id) const = 0;
};
/**
* Data that is passed into geometry nodes evaluation from the modifier.
*/
@@ -51,20 +139,7 @@ struct GeoNodesModifierData {
/** Optional logger. */
geo_eval_log::GeoModifierLog *eval_log = nullptr;
/** Read-only simulation states around the current frame. */
const bke::sim::ModifierSimulationState *current_simulation_state = nullptr;
const bke::sim::ModifierSimulationState *prev_simulation_state = nullptr;
const bke::sim::ModifierSimulationState *next_simulation_state = nullptr;
float simulation_state_mix_factor = 0.0f;
/** Used when the evaluation should create a new simulation state. */
bke::sim::ModifierSimulationState *current_simulation_state_for_write = nullptr;
float simulation_time_delta = 0.0f;
/**
* The same as #prev_simulation_state, but the cached values can be moved from,
* to keep data managed by implicit sharing mutable.
*/
bke::sim::ModifierSimulationState *prev_simulation_state_mutable = nullptr;
GeoNodesSimulationParams *simulation_params = nullptr;
/**
* Some nodes should be executed even when their output is not used (e.g. active viewer nodes and
@@ -264,8 +339,14 @@ std::unique_ptr<LazyFunction> get_simulation_input_lazy_function(
GeometryNodesLazyFunctionGraphInfo &own_lf_graph_info);
std::unique_ptr<LazyFunction> get_switch_node_lazy_function(const bNode &node);
std::optional<bke::sim::SimulationZoneID> get_simulation_zone_id(
const GeoNodesLFUserData &user_data, const int output_node_id);
struct FoundNestedNodeID {
int id;
bool is_in_simulation = false;
bool is_in_loop = false;
};
std::optional<FoundNestedNodeID> find_nested_node_id(const GeoNodesLFUserData &user_data,
const int node_id);
/**
* An anonymous attribute created by a node.

View File

@@ -136,17 +136,17 @@ void socket_declarations_for_simulation_items(Span<NodeSimulationItem> items,
NodeDeclaration &r_declaration);
const CPPType &get_simulation_item_cpp_type(eNodeSocketDatatype socket_type);
const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item);
void move_values_to_simulation_state(const Span<NodeSimulationItem> node_simulation_items,
const Span<void *> input_values,
bke::sim::SimulationZoneState &r_zone_state);
Map<int, std::unique_ptr<bke::BakeItem>> move_values_to_simulation_state(
const Span<NodeSimulationItem> node_simulation_items, const Span<void *> input_values);
void move_simulation_state_to_values(const Span<NodeSimulationItem> node_simulation_items,
bke::sim::SimulationZoneState &zone_state,
Map<int, std::unique_ptr<bke::BakeItem>> zone_state,
const Object &self_object,
const ComputeContext &compute_context,
const bNode &sim_output_node,
Span<void *> r_output_values);
void copy_simulation_state_to_values(const Span<NodeSimulationItem> node_simulation_items,
const bke::sim::SimulationZoneState &zone_state,
const Map<int, const bke::BakeItem *> &zone_state,
const Object &self_object,
const ComputeContext &compute_context,
const bNode &sim_output_node,

View File

@@ -64,54 +64,56 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction {
return;
}
const GeoNodesModifierData &modifier_data = *user_data.modifier_data;
if (!params.output_was_set(0)) {
const float delta_time = modifier_data.simulation_time_delta;
params.set_output(0, fn::ValueOrField<float>(delta_time));
}
if (modifier_data.current_simulation_state == nullptr) {
this->pass_through(params, user_data);
return;
}
const std::optional<bke::sim::SimulationZoneID> zone_id = get_simulation_zone_id(
user_data, output_node_id_);
if (!zone_id) {
if (!modifier_data.simulation_params) {
params.set_default_remaining_outputs();
return;
}
/* When caching is turned off and the old state doesn't need to persist, moving data
* from the last state instead of copying it can avoid copies of geometry data arrays. */
if (auto *state = modifier_data.prev_simulation_state_mutable) {
if (bke::sim::SimulationZoneState *zone = state->get_zone_state(*zone_id)) {
this->output_simulation_state_move(params, user_data, *zone);
return;
}
std::optional<FoundNestedNodeID> found_id = find_nested_node_id(user_data, output_node_id_);
if (!found_id) {
params.set_default_remaining_outputs();
return;
}
/* If there is a read-only state from the last frame, output that directly. */
if (const auto *state = modifier_data.prev_simulation_state) {
if (const bke::sim::SimulationZoneState *zone = state->get_zone_state(*zone_id)) {
this->output_simulation_state_copy(params, user_data, *zone);
return;
}
if (found_id->is_in_loop) {
params.set_default_remaining_outputs();
return;
}
SimulationZoneBehavior *zone_behavior = modifier_data.simulation_params->get(found_id->id);
if (!zone_behavior) {
params.set_default_remaining_outputs();
return;
}
sim_input::Behavior &input_behavior = zone_behavior->input;
float delta_time = 0.0f;
if (auto *info = std::get_if<sim_input::OutputCopy>(&input_behavior)) {
delta_time = info->delta_time;
this->output_simulation_state_copy(params, user_data, info->items_by_id);
}
else if (auto *info = std::get_if<sim_input::OutputMove>(&input_behavior)) {
delta_time = info->delta_time;
this->output_simulation_state_move(params, user_data, std::move(info->items_by_id));
}
else if (std::get_if<sim_input::PassThrough>(&input_behavior)) {
delta_time = 0.0f;
this->pass_through(params, user_data);
}
else {
BLI_assert_unreachable();
}
if (!params.output_was_set(0)) {
params.set_output(0, fn::ValueOrField<float>(delta_time));
}
this->pass_through(params, user_data);
}
void output_simulation_state_copy(lf::Params &params,
const GeoNodesLFUserData &user_data,
const bke::sim::SimulationZoneState &state) const
const Map<int, const bke::BakeItem *> &zone_state) const
{
Array<void *> outputs(simulation_items_.size());
for (const int i : simulation_items_.index_range()) {
outputs[i] = params.get_output_data_ptr(i + 1);
}
copy_simulation_state_to_values(simulation_items_,
state,
std::move(zone_state),
*user_data.modifier_data->self_object,
*user_data.compute_context,
node_,
@@ -123,14 +125,14 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction {
void output_simulation_state_move(lf::Params &params,
const GeoNodesLFUserData &user_data,
bke::sim::SimulationZoneState &state) const
Map<int, std::unique_ptr<bke::BakeItem>> zone_state) const
{
Array<void *> outputs(simulation_items_.size());
for (const int i : simulation_items_.index_range()) {
outputs[i] = params.get_output_data_ptr(i + 1);
}
move_simulation_state_to_values(simulation_items_,
state,
std::move(zone_state),
*user_data.modifier_data->self_object,
*user_data.compute_context,
node_,
@@ -153,9 +155,9 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction {
/* Instead of outputting the initial values directly, convert them to a simulation state and
* then back. This ensures that some geometry processing happens on the data consistently (e.g.
* removing anonymous attributes). */
bke::sim::SimulationZoneState state;
move_values_to_simulation_state(simulation_items_, input_values, state);
this->output_simulation_state_move(params, user_data, state);
Map<int, std::unique_ptr<bke::BakeItem>> bake_items = move_values_to_simulation_state(
simulation_items_, input_values);
this->output_simulation_state_move(params, user_data, std::move(bake_items));
}
};

View File

@@ -185,7 +185,7 @@ static std::shared_ptr<AnonymousAttributeFieldInput> make_attribute_field(
}
void move_simulation_state_to_values(const Span<NodeSimulationItem> node_simulation_items,
bke::sim::SimulationZoneState &zone_state,
Map<int, std::unique_ptr<bke::BakeItem>> zone_state,
const Object &self_object,
const ComputeContext &compute_context,
const bNode &node,
@@ -194,7 +194,7 @@ void move_simulation_state_to_values(const Span<NodeSimulationItem> node_simulat
const bke::BakeSocketConfig config = make_bake_socket_config(node_simulation_items);
Vector<bke::BakeItem *> bake_items;
for (const NodeSimulationItem &item : node_simulation_items) {
auto *bake_item = zone_state.item_by_identifier.lookup_ptr(item.identifier);
auto *bake_item = zone_state.lookup_ptr(item.identifier);
bake_items.append(bake_item ? bake_item->get() : nullptr);
}
@@ -209,17 +209,17 @@ void move_simulation_state_to_values(const Span<NodeSimulationItem> node_simulat
}
void copy_simulation_state_to_values(const Span<NodeSimulationItem> node_simulation_items,
const bke::sim::SimulationZoneState &zone_state,
const Map<int, const bke::BakeItem *> &zone_state,
const Object &self_object,
const ComputeContext &compute_context,
const bNode &node,
Span<void *> r_output_values)
{
const bke::BakeSocketConfig config = make_bake_socket_config(node_simulation_items);
Vector<bke::BakeItem *> bake_items;
Vector<const bke::BakeItem *> bake_items;
for (const NodeSimulationItem &item : node_simulation_items) {
auto *bake_item = zone_state.item_by_identifier.lookup_ptr(item.identifier);
bake_items.append(bake_item ? bake_item->get() : nullptr);
const bke::BakeItem *const *bake_item = zone_state.lookup_ptr(item.identifier);
bake_items.append(bake_item ? *bake_item : nullptr);
}
bke::copy_bake_items_to_socket_values(
@@ -232,22 +232,23 @@ void copy_simulation_state_to_values(const Span<NodeSimulationItem> node_simulat
r_output_values);
}
void move_values_to_simulation_state(const Span<NodeSimulationItem> node_simulation_items,
const Span<void *> input_values,
bke::sim::SimulationZoneState &r_zone_state)
Map<int, std::unique_ptr<bke::BakeItem>> move_values_to_simulation_state(
const Span<NodeSimulationItem> node_simulation_items, const Span<void *> input_values)
{
const bke::BakeSocketConfig config = make_bake_socket_config(node_simulation_items);
Array<std::unique_ptr<bke::BakeItem>> bake_items = bke::move_socket_values_to_bake_items(
input_values, config);
Map<int, std::unique_ptr<bke::BakeItem>> bake_items_map;
for (const int i : node_simulation_items.index_range()) {
const NodeSimulationItem &item = node_simulation_items[i];
std::unique_ptr<bke::BakeItem> &bake_item = bake_items[i];
if (bake_item) {
r_zone_state.item_by_identifier.add_new(item.identifier, std::move(bake_item));
bake_items_map.add_new(item.identifier, std::move(bake_item));
}
}
return bake_items_map;
}
} // namespace blender::nodes
@@ -256,10 +257,6 @@ namespace blender::nodes::node_geo_simulation_output_cc {
NODE_STORAGE_FUNCS(NodeGeometrySimulationOutput);
struct EvalData {
bool is_first_evaluation = true;
};
static bool sharing_info_equal(const ImplicitSharingInfo *a, const ImplicitSharingInfo *b)
{
if (!a || !b) {
@@ -542,16 +539,6 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
}
}
void *init_storage(LinearAllocator<> &allocator) const
{
return allocator.construct<EvalData>().release();
}
void destruct_storage(void *storage) const
{
std::destroy_at(static_cast<EvalData *>(storage));
}
void execute_impl(lf::Params &params, const lf::Context &context) const final
{
GeoNodesLFUserData &user_data = *static_cast<GeoNodesLFUserData *>(context.user_data);
@@ -559,78 +546,51 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
params.set_default_remaining_outputs();
return;
}
GeoNodesModifierData &modifier_data = *user_data.modifier_data;
EvalData &eval_data = *static_cast<EvalData *>(context.storage);
BLI_SCOPED_DEFER([&]() { eval_data.is_first_evaluation = false; });
const std::optional<bke::sim::SimulationZoneID> zone_id = get_simulation_zone_id(
user_data, node_.identifier);
if (!zone_id) {
const GeoNodesModifierData &modifier_data = *user_data.modifier_data;
if (!modifier_data.simulation_params) {
params.set_default_remaining_outputs();
return;
}
const bke::sim::SimulationZoneState *current_zone_state =
modifier_data.current_simulation_state ?
modifier_data.current_simulation_state->get_zone_state(*zone_id) :
nullptr;
if (eval_data.is_first_evaluation && current_zone_state != nullptr) {
/* Common case when data is cached already. */
this->output_cached_state(params, user_data, *current_zone_state);
std::optional<FoundNestedNodeID> found_id = find_nested_node_id(user_data, node_.identifier);
if (!found_id) {
params.set_default_remaining_outputs();
return;
}
if (modifier_data.current_simulation_state_for_write == nullptr) {
const bke::sim::SimulationZoneState *prev_zone_state =
modifier_data.prev_simulation_state ?
modifier_data.prev_simulation_state->get_zone_state(*zone_id) :
nullptr;
if (prev_zone_state == nullptr) {
/* There is no previous simulation state and we also don't create a new one, so just pass
* the data through. */
this->pass_through(params, user_data);
return;
}
const bke::sim::SimulationZoneState *next_zone_state =
modifier_data.next_simulation_state ?
modifier_data.next_simulation_state->get_zone_state(*zone_id) :
nullptr;
if (next_zone_state == nullptr) {
/* Output the last cached simulation state. */
this->output_cached_state(params, user_data, *prev_zone_state);
return;
}
/* A previous and next frame is cached already, but the current frame is not. */
if (found_id->is_in_loop) {
params.set_default_remaining_outputs();
return;
}
SimulationZoneBehavior *zone_behavior = modifier_data.simulation_params->get(found_id->id);
if (!zone_behavior) {
params.set_default_remaining_outputs();
return;
}
sim_output::Behavior &output_behavior = zone_behavior->output;
if (auto *info = std::get_if<sim_output::ReadSingle>(&output_behavior)) {
this->output_cached_state(params, user_data, info->items_by_id);
}
else if (auto *info = std::get_if<sim_output::ReadInterpolated>(&output_behavior)) {
this->output_mixed_cached_state(params,
*modifier_data.self_object,
*user_data.compute_context,
*prev_zone_state,
*next_zone_state,
modifier_data.simulation_state_mix_factor);
return;
info->prev_items_by_id,
info->next_items_by_id,
info->mix_factor);
}
bke::sim::SimulationZoneState &new_zone_state =
modifier_data.current_simulation_state_for_write->get_zone_state_for_write(*zone_id);
if (eval_data.is_first_evaluation) {
new_zone_state.item_by_identifier.clear();
else if (std::get_if<sim_output::PassThrough>(&output_behavior)) {
this->pass_through(params, user_data);
}
Array<void *> input_values(simulation_items_.size(), nullptr);
for (const int i : simulation_items_.index_range()) {
input_values[i] = params.try_get_input_data_ptr_or_request(i);
else if (auto *info = std::get_if<sim_output::StoreAndPassThrough>(&output_behavior)) {
this->store_and_pass_through(params, user_data, *info);
}
if (input_values.as_span().contains(nullptr)) {
/* Wait until all inputs are available. */
return;
else {
BLI_assert_unreachable();
}
move_values_to_simulation_state(simulation_items_, input_values, new_zone_state);
this->output_cached_state(params, user_data, new_zone_state);
}
void output_cached_state(lf::Params &params,
GeoNodesLFUserData &user_data,
const bke::sim::SimulationZoneState &state) const
const Map<int, const bke::BakeItem *> &state) const
{
Array<void *> output_values(simulation_items_.size());
for (const int i : simulation_items_.index_range()) {
@@ -650,8 +610,8 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
void output_mixed_cached_state(lf::Params &params,
const Object &self_object,
const ComputeContext &compute_context,
const bke::sim::SimulationZoneState &prev_state,
const bke::sim::SimulationZoneState &next_state,
const Map<int, const bke::BakeItem *> &prev_state,
const Map<int, const bke::BakeItem *> &next_state,
const float mix_factor) const
{
Array<void *> output_values(simulation_items_.size());
@@ -685,6 +645,52 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
}
void pass_through(lf::Params &params, GeoNodesLFUserData &user_data) const
{
/* Instead of outputting the initial values directly, convert them to a simulation state and
* then back. This ensures that some geometry processing happens on the data consistently (e.g.
* removing anonymous attributes). */
std::optional<Map<int, std::unique_ptr<bke::BakeItem>>> bake_items =
this->get_bake_items_from_inputs(params);
if (!bake_items) {
/* Wait for inputs to be computed. */
return;
}
Array<void *> output_values(simulation_items_.size());
for (const int i : simulation_items_.index_range()) {
output_values[i] = params.get_output_data_ptr(i);
}
move_simulation_state_to_values(simulation_items_,
std::move(*bake_items),
*user_data.modifier_data->self_object,
*user_data.compute_context,
node_,
output_values);
for (const int i : simulation_items_.index_range()) {
params.output_set(i);
}
}
void store_and_pass_through(lf::Params &params,
GeoNodesLFUserData &user_data,
const sim_output::StoreAndPassThrough &info) const
{
std::optional<Map<int, std::unique_ptr<bke::BakeItem>>> bake_items =
this->get_bake_items_from_inputs(params);
if (!bake_items) {
/* Wait for inputs to be computed. */
return;
}
Map<int, const bke::BakeItem *> bake_item_pointers;
for (const auto item : bake_items->items()) {
bake_item_pointers.add_new(item.key, item.value.get());
}
this->output_cached_state(params, user_data, bake_item_pointers);
info.store_fn(std::move(*bake_items));
}
std::optional<Map<int, std::unique_ptr<bke::BakeItem>>> get_bake_items_from_inputs(
lf::Params &params) const
{
Array<void *> input_values(inputs_.size());
for (const int i : inputs_.index_range()) {
@@ -692,14 +698,10 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
}
if (input_values.as_span().contains(nullptr)) {
/* Wait for inputs to be computed. */
return;
return std::nullopt;
}
/* Instead of outputting the initial values directly, convert them to a simulation state and
* then back. This ensures that some geometry processing happens on the data consistently (e.g.
* removing anonymous attributes). */
bke::sim::SimulationZoneState state;
move_values_to_simulation_state(simulation_items_, input_values, state);
this->output_cached_state(params, user_data, state);
return move_values_to_simulation_state(simulation_items_, input_values);
}
};
@@ -715,30 +717,6 @@ std::unique_ptr<LazyFunction> get_simulation_output_lazy_function(
return std::make_unique<file_ns::LazyFunctionForSimulationOutputNode>(node, own_lf_graph_info);
}
std::optional<bke::sim::SimulationZoneID> get_simulation_zone_id(
const GeoNodesLFUserData &user_data, const int output_node_id)
{
Vector<int> node_ids;
for (const ComputeContext *context = user_data.compute_context; context != nullptr;
context = context->parent())
{
if (const auto *node_context = dynamic_cast<const bke::NodeGroupComputeContext *>(context)) {
node_ids.append(node_context->node_id());
}
else if (dynamic_cast<const bke::RepeatZoneComputeContext *>(context) != nullptr) {
/* Simulation can't be used in a repeat zone. */
return std::nullopt;
}
}
std::reverse(node_ids.begin(), node_ids.end());
node_ids.append(output_node_id);
const bNestedNodeRef *nested_node_ref = user_data.root_ntree->nested_node_ref_from_node_id_path(
node_ids);
bke::sim::SimulationZoneID zone_id;
zone_id.nested_node_id = nested_node_ref->id;
return zone_id;
}
} // namespace blender::nodes
namespace blender::nodes::node_geo_simulation_output_cc {

View File

@@ -866,13 +866,15 @@ class LazyFunctionForViewerInputUsage : public LazyFunction {
};
class LazyFunctionForSimulationInputsUsage : public LazyFunction {
private:
const bNode *output_bnode_;
public:
LazyFunctionForSimulationInputsUsage()
LazyFunctionForSimulationInputsUsage(const bNode &output_bnode) : output_bnode_(&output_bnode)
{
debug_name_ = "Simulation Inputs Usage";
outputs_.append_as("Is Initialization", CPPType::get<bool>());
outputs_.append_as("Do Simulation Step", CPPType::get<bool>());
outputs_.append_as("Need Input Inputs", CPPType::get<bool>());
outputs_.append_as("Need Output Inputs", CPPType::get<bool>());
}
void execute_impl(lf::Params &params, const lf::Context &context) const override
@@ -883,11 +885,39 @@ class LazyFunctionForSimulationInputsUsage : public LazyFunction {
return;
}
const GeoNodesModifierData &modifier_data = *user_data.modifier_data;
if (!modifier_data.simulation_params) {
params.set_default_remaining_outputs();
return;
}
const std::optional<FoundNestedNodeID> found_id = find_nested_node_id(
user_data, output_bnode_->identifier);
if (!found_id) {
params.set_default_remaining_outputs();
return;
}
if (found_id->is_in_loop) {
params.set_default_remaining_outputs();
return;
}
SimulationZoneBehavior *zone_behavior = modifier_data.simulation_params->get(found_id->id);
if (!zone_behavior) {
params.set_default_remaining_outputs();
return;
}
params.set_output(0,
modifier_data.current_simulation_state_for_write != nullptr &&
modifier_data.prev_simulation_state == nullptr);
params.set_output(1, modifier_data.current_simulation_state_for_write != nullptr);
bool solve_contains_side_effect = false;
if (modifier_data.side_effect_nodes) {
const Span<const lf::FunctionNode *> side_effect_nodes =
modifier_data.side_effect_nodes->lookup(user_data.compute_context->hash());
solve_contains_side_effect = !side_effect_nodes.is_empty();
}
params.set_output(0, std::holds_alternative<sim_input::PassThrough>(zone_behavior->input));
params.set_output(
1,
solve_contains_side_effect ||
std::holds_alternative<sim_output::PassThrough>(zone_behavior->output) ||
std::holds_alternative<sim_output::StoreAndPassThrough>(zone_behavior->output));
}
};
@@ -1869,7 +1899,8 @@ struct GeometryNodesLazyFunctionGraphBuilder {
lf::Node &lf_border_link_usage_node = this->build_border_link_input_usage_node(zone, lf_graph);
lf::Node &lf_simulation_usage_node = [&]() -> lf::Node & {
auto &lazy_function = scope_.construct<LazyFunctionForSimulationInputsUsage>();
auto &lazy_function = scope_.construct<LazyFunctionForSimulationInputsUsage>(
*zone.output_node);
lf::Node &lf_node = lf_graph.add_function(lazy_function);
if (lf_main_input_usage_node) {
@@ -4006,4 +4037,33 @@ GeoNodesLFLocalUserData::GeoNodesLFLocalUserData(GeoNodesLFUserData &user_data)
}
}
std::optional<FoundNestedNodeID> find_nested_node_id(const GeoNodesLFUserData &user_data,
const int node_id)
{
FoundNestedNodeID found;
Vector<int> node_ids;
for (const ComputeContext *context = user_data.compute_context; context != nullptr;
context = context->parent())
{
if (const auto *node_context = dynamic_cast<const bke::NodeGroupComputeContext *>(context)) {
node_ids.append(node_context->node_id());
}
else if (dynamic_cast<const bke::RepeatZoneComputeContext *>(context) != nullptr) {
found.is_in_loop = true;
}
else if (dynamic_cast<const bke::SimulationZoneComputeContext *>(context) != nullptr) {
found.is_in_simulation = true;
}
}
std::reverse(node_ids.begin(), node_ids.end());
node_ids.append(node_id);
const bNestedNodeRef *nested_node_ref = user_data.root_ntree->nested_node_ref_from_node_id_path(
node_ids);
if (nested_node_ref == nullptr) {
return std::nullopt;
}
found.id = nested_node_ref->id;
return found;
}
} // namespace blender::nodes