This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenkernel/intern/volume.cc
Bastien Montagne f5a019ed43 LibOverride: Do not store some heavy data from override IDs.
This commit removes geometry from meshes and shapekeys, and embedded
files, from liboverride IDs.

This data is never overrideable, there is no reason to store extra
useless copies of it in production files.

See T78944.

Note that we may add more data to be skipped on write for liboverrides
in the future, but this commit should address all the most important
cases already.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D9810
2020-12-14 11:37:01 +01:00

1545 lines
42 KiB
C++

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/** \file
* \ingroup bke
*/
#include "MEM_guardedalloc.h"
#include "DNA_defaults.h"
#include "DNA_material_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_volume_types.h"
#include "BLI_compiler_compat.h"
#include "BLI_fileops.h"
#include "BLI_ghash.h"
#include "BLI_map.hh"
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BKE_anim_data.h"
#include "BKE_global.h"
#include "BKE_idtype.h"
#include "BKE_lib_id.h"
#include "BKE_lib_query.h"
#include "BKE_lib_remap.h"
#include "BKE_main.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_packedFile.h"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "BKE_volume.h"
#include "BLT_translation.h"
#include "DEG_depsgraph_query.h"
#include "BLO_read_write.h"
#include "CLG_log.h"
#ifdef WITH_OPENVDB
static CLG_LogRef LOG = {"bke.volume"};
#endif
#define VOLUME_FRAME_NONE INT_MAX
#ifdef WITH_OPENVDB
# include <atomic>
# include <list>
# include <mutex>
# include <unordered_set>
# include <openvdb/openvdb.h>
# include <openvdb/points/PointDataGrid.h>
# include <openvdb/tools/GridTransformer.h>
/* Global Volume File Cache
*
* Global cache of grids read from VDB files. This is used for sharing grids
* between multiple volume datablocks with the same filepath, and sharing grids
* between original and copy-on-write datablocks created by the depsgraph.
*
* There are two types of users. Some datablocks only need the grid metadata,
* example an original datablock volume showing the list of grids in the
* properties editor. Other datablocks also need the tree and voxel data, for
* rendering for example. So, depending on the users the grid in the cache may
* have a tree or not.
*
* When the number of users drops to zero, the grid data is immediately deleted.
*
* TODO: also add a cache for OpenVDB files rather than individual grids,
* so getting the list of grids is also cached.
* TODO: Further, we could cache openvdb::io::File so that loading a grid
* does not re-open it every time. But then we have to take care not to run
* out of file descriptors or prevent other applications from writing to it.
*/
static struct VolumeFileCache {
/* Cache Entry */
struct Entry {
Entry(const std::string &filepath, const openvdb::GridBase::Ptr &grid)
: filepath(filepath),
grid_name(grid->getName()),
grid(grid),
is_loaded(false),
num_metadata_users(0),
num_tree_users(0)
{
}
Entry(const Entry &other)
: filepath(other.filepath),
grid_name(other.grid_name),
grid(other.grid),
is_loaded(other.is_loaded),
num_metadata_users(0),
num_tree_users(0)
{
}
/* Returns the original grid or a simplified version depending on the given #simplify_level. */
openvdb::GridBase::Ptr simplified_grid(const int simplify_level)
{
BLI_assert(simplify_level >= 0);
if (simplify_level == 0 || !is_loaded) {
return grid;
}
std::lock_guard<std::mutex> lock(mutex);
return simplified_grids.lookup_or_add_cb(simplify_level, [&]() {
const float resolution_factor = 1.0f / (1 << simplify_level);
const VolumeGridType grid_type = BKE_volume_grid_type_openvdb(grid);
return BKE_volume_grid_create_with_changed_resolution(grid_type, *grid, resolution_factor);
});
}
/* Unique key: filename + grid name. */
std::string filepath;
std::string grid_name;
/* OpenVDB grid. */
openvdb::GridBase::Ptr grid;
/* Simplified versions of #grid. The integer key is the simplification level. */
blender::Map<int, openvdb::GridBase::Ptr> simplified_grids;
/* Has the grid tree been loaded? */
bool is_loaded;
/* Error message if an error occured during loading. */
std::string error_msg;
/* User counting. */
int num_metadata_users;
int num_tree_users;
/* Mutex for on-demand reading of tree. */
std::mutex mutex;
};
struct EntryHasher {
std::size_t operator()(const Entry &entry) const
{
std::hash<std::string> string_hasher;
return BLI_ghashutil_combine_hash(string_hasher(entry.filepath),
string_hasher(entry.grid_name));
}
};
struct EntryEqual {
bool operator()(const Entry &a, const Entry &b) const
{
return a.filepath == b.filepath && a.grid_name == b.grid_name;
}
};
/* Cache */
VolumeFileCache()
{
}
~VolumeFileCache()
{
BLI_assert(cache.empty());
}
Entry *add_metadata_user(const Entry &template_entry)
{
std::lock_guard<std::mutex> lock(mutex);
EntrySet::iterator it = cache.find(template_entry);
if (it == cache.end()) {
it = cache.emplace(template_entry).first;
}
/* Casting const away is weak, but it's convenient having key and value in one. */
Entry &entry = (Entry &)*it;
entry.num_metadata_users++;
/* Note: pointers to unordered_set values are not invalidated when adding
* or removing other values. */
return &entry;
}
void copy_user(Entry &entry, const bool tree_user)
{
std::lock_guard<std::mutex> lock(mutex);
if (tree_user) {
entry.num_tree_users++;
}
else {
entry.num_metadata_users++;
}
}
void remove_user(Entry &entry, const bool tree_user)
{
std::lock_guard<std::mutex> lock(mutex);
if (tree_user) {
entry.num_tree_users--;
}
else {
entry.num_metadata_users--;
}
update_for_remove_user(entry);
}
void change_to_tree_user(Entry &entry)
{
std::lock_guard<std::mutex> lock(mutex);
entry.num_tree_users++;
entry.num_metadata_users--;
update_for_remove_user(entry);
}
void change_to_metadata_user(Entry &entry)
{
std::lock_guard<std::mutex> lock(mutex);
entry.num_metadata_users++;
entry.num_tree_users--;
update_for_remove_user(entry);
}
protected:
void update_for_remove_user(Entry &entry)
{
if (entry.num_metadata_users + entry.num_tree_users == 0) {
cache.erase(entry);
}
else if (entry.num_tree_users == 0) {
/* Note we replace the grid rather than clearing, so that if there is
* any other shared pointer to the grid it will keep the tree. */
entry.grid = entry.grid->copyGridWithNewTree();
entry.simplified_grids.clear();
entry.is_loaded = false;
}
}
/* Cache contents */
typedef std::unordered_set<Entry, EntryHasher, EntryEqual> EntrySet;
EntrySet cache;
/* Mutex for multithreaded access. */
std::mutex mutex;
} GLOBAL_CACHE;
/* VolumeGrid
*
* Wrapper around OpenVDB grid. Grids loaded from OpenVDB files are always
* stored in the global cache. Procedurally generated grids are not. */
struct VolumeGrid {
VolumeGrid(const VolumeFileCache::Entry &template_entry, const int simplify_level)
: entry(nullptr), simplify_level(simplify_level), is_loaded(false)
{
entry = GLOBAL_CACHE.add_metadata_user(template_entry);
}
VolumeGrid(const openvdb::GridBase::Ptr &grid)
: entry(nullptr), local_grid(grid), is_loaded(true)
{
}
VolumeGrid(const VolumeGrid &other)
: entry(other.entry),
simplify_level(other.simplify_level),
local_grid(other.local_grid),
is_loaded(other.is_loaded)
{
if (entry) {
GLOBAL_CACHE.copy_user(*entry, is_loaded);
}
}
~VolumeGrid()
{
if (entry) {
GLOBAL_CACHE.remove_user(*entry, is_loaded);
}
}
void load(const char *volume_name, const char *filepath)
{
/* If already loaded or not file-backed, nothing to do. */
if (is_loaded || entry == nullptr) {
return;
}
/* Double-checked lock. */
std::lock_guard<std::mutex> lock(entry->mutex);
if (is_loaded) {
return;
}
/* Change metadata user to tree user. */
GLOBAL_CACHE.change_to_tree_user(*entry);
/* If already loaded by another user, nothing further to do. */
if (entry->is_loaded) {
is_loaded = true;
return;
}
/* Load grid from file. */
CLOG_INFO(&LOG, 1, "Volume %s: load grid '%s'", volume_name, name());
openvdb::io::File file(filepath);
try {
file.setCopyMaxBytes(0);
file.open();
openvdb::GridBase::Ptr vdb_grid = file.readGrid(name());
entry->grid->setTree(vdb_grid->baseTreePtr());
}
catch (const openvdb::IoError &e) {
entry->error_msg = e.what();
}
std::atomic_thread_fence(std::memory_order_release);
entry->is_loaded = true;
is_loaded = true;
}
void unload(const char *volume_name)
{
/* Not loaded or not file-backed, nothing to do. */
if (!is_loaded || entry == nullptr) {
return;
}
/* Double-checked lock. */
std::lock_guard<std::mutex> lock(entry->mutex);
if (!is_loaded) {
return;
}
CLOG_INFO(&LOG, 1, "Volume %s: unload grid '%s'", volume_name, name());
/* Change tree user to metadata user. */
GLOBAL_CACHE.change_to_metadata_user(*entry);
/* Indicate we no longer have a tree. The actual grid may still
* have it due to another user. */
std::atomic_thread_fence(std::memory_order_release);
is_loaded = false;
}
void clear_reference(const char *UNUSED(volume_name))
{
/* Clear any reference to a grid in the file cache. */
local_grid = grid()->copyGridWithNewTree();
if (entry) {
GLOBAL_CACHE.remove_user(*entry, is_loaded);
entry = nullptr;
}
is_loaded = true;
}
void duplicate_reference(const char *volume_name, const char *filepath)
{
/* Make a deep copy of the grid and remove any reference to a grid in the
* file cache. Load file grid into memory first if needed. */
load(volume_name, filepath);
/* TODO: avoid deep copy if we are the only user. */
local_grid = grid()->deepCopyGrid();
if (entry) {
GLOBAL_CACHE.remove_user(*entry, is_loaded);
entry = nullptr;
}
is_loaded = true;
}
const char *name() const
{
/* Don't use vdb.getName() since it copies the string, we want a pointer to the
* original so it doesn't get freed out of scope. */
openvdb::StringMetadata::ConstPtr name_meta =
main_grid()->getMetadata<openvdb::StringMetadata>(openvdb::GridBase::META_GRID_NAME);
return (name_meta) ? name_meta->value().c_str() : "";
}
const char *error_message() const
{
if (is_loaded && entry && !entry->error_msg.empty()) {
return entry->error_msg.c_str();
}
return nullptr;
}
bool grid_is_loaded() const
{
return is_loaded;
}
openvdb::GridBase::Ptr grid() const
{
if (entry) {
return entry->simplified_grid(simplify_level);
}
return local_grid;
}
void set_simplify_level(const int simplify_level)
{
BLI_assert(simplify_level >= 0);
this->simplify_level = simplify_level;
}
private:
const openvdb::GridBase::Ptr &main_grid() const
{
return (entry) ? entry->grid : local_grid;
}
protected:
/* File cache entry when grid comes directly from a file and may be shared
* with other volume datablocks. */
VolumeFileCache::Entry *entry;
/* If this volume grid is in the global file cache, we can reference a simplified version of it,
* instead of the original high resolution grid. */
int simplify_level = 0;
/* OpenVDB grid if it's not shared through the file cache. */
openvdb::GridBase::Ptr local_grid;
/* Indicates if the tree has been loaded for this grid. Note that vdb.tree()
* may actually be loaded by another user while this is false. But only after
* calling load() and is_loaded changes to true is it safe to access. */
bool is_loaded;
};
/* Volume Grid Vector
*
* List of grids contained in a volume datablock. This is runtime-only data,
* the actual grids are always saved in a VDB file. */
struct VolumeGridVector : public std::list<VolumeGrid> {
VolumeGridVector() : metadata(new openvdb::MetaMap())
{
filepath[0] = '\0';
}
VolumeGridVector(const VolumeGridVector &other)
: std::list<VolumeGrid>(other), error_msg(other.error_msg), metadata(other.metadata)
{
memcpy(filepath, other.filepath, sizeof(filepath));
}
bool is_loaded() const
{
return filepath[0] != '\0';
}
void clear_all()
{
std::list<VolumeGrid>::clear();
filepath[0] = '\0';
error_msg.clear();
metadata.reset();
}
/* Absolute file path that grids have been loaded from. */
char filepath[FILE_MAX];
/* File loading error message. */
std::string error_msg;
/* File Metadata. */
openvdb::MetaMap::Ptr metadata;
/* Mutex for file loading of grids list. */
std::mutex mutex;
};
#endif
/* Module */
void BKE_volumes_init()
{
#ifdef WITH_OPENVDB
openvdb::initialize();
#endif
}
/* Volume datablock */
static void volume_init_data(ID *id)
{
Volume *volume = (Volume *)id;
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(volume, id));
MEMCPY_STRUCT_AFTER(volume, DNA_struct_default_get(Volume), id);
BKE_volume_init_grids(volume);
}
static void volume_copy_data(Main *UNUSED(bmain),
ID *id_dst,
const ID *id_src,
const int UNUSED(flag))
{
Volume *volume_dst = (Volume *)id_dst;
const Volume *volume_src = (const Volume *)id_src;
if (volume_src->packedfile) {
volume_dst->packedfile = BKE_packedfile_duplicate(volume_src->packedfile);
}
volume_dst->mat = (Material **)MEM_dupallocN(volume_src->mat);
#ifdef WITH_OPENVDB
if (volume_src->runtime.grids) {
const VolumeGridVector &grids_src = *(volume_src->runtime.grids);
volume_dst->runtime.grids = OBJECT_GUARDED_NEW(VolumeGridVector, grids_src);
}
#endif
}
static void volume_free_data(ID *id)
{
Volume *volume = (Volume *)id;
BKE_animdata_free(&volume->id, false);
BKE_volume_batch_cache_free(volume);
MEM_SAFE_FREE(volume->mat);
#ifdef WITH_OPENVDB
OBJECT_GUARDED_SAFE_DELETE(volume->runtime.grids, VolumeGridVector);
#endif
}
static void volume_foreach_id(ID *id, LibraryForeachIDData *data)
{
Volume *volume = (Volume *)id;
for (int i = 0; i < volume->totcol; i++) {
BKE_LIB_FOREACHID_PROCESS(data, volume->mat[i], IDWALK_CB_USER);
}
}
static void volume_foreach_cache(ID *id,
IDTypeForeachCacheFunctionCallback function_callback,
void *user_data)
{
Volume *volume = (Volume *)id;
IDCacheKey key = {
/* id_session_uuid */ id->session_uuid,
/*offset_in_ID*/ offsetof(Volume, runtime.grids),
/* cache_v */ volume->runtime.grids,
};
function_callback(id, &key, (void **)&volume->runtime.grids, 0, user_data);
}
static void volume_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
Volume *volume = (Volume *)id;
const bool is_undo = BLO_write_is_undo(writer);
if (volume->id.us > 0 || is_undo) {
/* Clean up, important in undo case to reduce false detection of changed datablocks. */
volume->runtime.grids = nullptr;
/* Do not store packed files in case this is a library override ID. */
if (ID_IS_OVERRIDE_LIBRARY(volume) && !is_undo) {
volume->packedfile = nullptr;
}
/* write LibData */
BLO_write_id_struct(writer, Volume, id_address, &volume->id);
BKE_id_blend_write(writer, &volume->id);
/* direct data */
BLO_write_pointer_array(writer, volume->totcol, volume->mat);
if (volume->adt) {
BKE_animdata_blend_write(writer, volume->adt);
}
BKE_packedfile_blend_write(writer, volume->packedfile);
}
}
static void volume_blend_read_data(BlendDataReader *reader, ID *id)
{
Volume *volume = (Volume *)id;
BLO_read_data_address(reader, &volume->adt);
BKE_animdata_blend_read_data(reader, volume->adt);
BKE_packedfile_blend_read(reader, &volume->packedfile);
volume->runtime.frame = 0;
/* materials */
BLO_read_pointer_array(reader, (void **)&volume->mat);
}
static void volume_blend_read_lib(BlendLibReader *reader, ID *id)
{
Volume *volume = (Volume *)id;
/* Needs to be done *after* cache pointers are restored (call to
* `foreach_cache`/`blo_cache_storage_entry_restore_in_new`), easier for now to do it in
* lib_link... */
BKE_volume_init_grids(volume);
for (int a = 0; a < volume->totcol; a++) {
BLO_read_id_address(reader, volume->id.lib, &volume->mat[a]);
}
}
static void volume_blend_read_expand(BlendExpander *expander, ID *id)
{
Volume *volume = (Volume *)id;
for (int a = 0; a < volume->totcol; a++) {
BLO_expand(expander, volume->mat[a]);
}
}
IDTypeInfo IDType_ID_VO = {
/* id_code */ ID_VO,
/* id_filter */ FILTER_ID_VO,
/* main_listbase_index */ INDEX_ID_VO,
/* struct_size */ sizeof(Volume),
/* name */ "Volume",
/* name_plural */ "volumes",
/* translation_context */ BLT_I18NCONTEXT_ID_VOLUME,
/* flags */ 0,
/* init_data */ volume_init_data,
/* copy_data */ volume_copy_data,
/* free_data */ volume_free_data,
/* make_local */ nullptr,
/* foreach_id */ volume_foreach_id,
/* foreach_cache */ volume_foreach_cache,
/* blend_write */ volume_blend_write,
/* blend_read_data */ volume_blend_read_data,
/* blend_read_lib */ volume_blend_read_lib,
/* blend_read_expand */ volume_blend_read_expand,
/* blend_read_undo_preserve */ nullptr,
};
void BKE_volume_init_grids(Volume *volume)
{
#ifdef WITH_OPENVDB
if (volume->runtime.grids == nullptr) {
volume->runtime.grids = OBJECT_GUARDED_NEW(VolumeGridVector);
}
#else
UNUSED_VARS(volume);
#endif
}
void *BKE_volume_add(Main *bmain, const char *name)
{
Volume *volume = (Volume *)BKE_id_new(bmain, ID_VO, name);
return volume;
}
/* Sequence */
static int volume_sequence_frame(const Depsgraph *depsgraph, const Volume *volume)
{
if (!volume->is_sequence) {
return 0;
}
char filepath[FILE_MAX];
STRNCPY(filepath, volume->filepath);
int path_frame, path_digits;
if (!(volume->is_sequence && BLI_path_frame_get(filepath, &path_frame, &path_digits))) {
return 0;
}
const int scene_frame = DEG_get_ctime(depsgraph);
const VolumeSequenceMode mode = (VolumeSequenceMode)volume->sequence_mode;
const int frame_duration = volume->frame_duration;
const int frame_start = volume->frame_start;
const int frame_offset = volume->frame_offset;
if (frame_duration == 0) {
return VOLUME_FRAME_NONE;
}
int frame = scene_frame - frame_start + 1;
switch (mode) {
case VOLUME_SEQUENCE_CLIP: {
if (frame < 1 || frame > frame_duration) {
return VOLUME_FRAME_NONE;
}
break;
}
case VOLUME_SEQUENCE_EXTEND: {
frame = clamp_i(frame, 1, frame_duration);
break;
}
case VOLUME_SEQUENCE_REPEAT: {
frame = frame % frame_duration;
if (frame < 0) {
frame += frame_duration;
}
if (frame == 0) {
frame = frame_duration;
}
break;
}
case VOLUME_SEQUENCE_PING_PONG: {
const int pingpong_duration = frame_duration * 2 - 2;
frame = frame % pingpong_duration;
if (frame < 0) {
frame += pingpong_duration;
}
if (frame == 0) {
frame = pingpong_duration;
}
if (frame > frame_duration) {
frame = frame_duration * 2 - frame;
}
break;
}
}
/* Important to apply after, else we cant loop on e.g. frames 100 - 110. */
frame += frame_offset;
return frame;
}
#ifdef WITH_OPENVDB
static void volume_filepath_get(const Main *bmain, const Volume *volume, char r_filepath[FILE_MAX])
{
BLI_strncpy(r_filepath, volume->filepath, FILE_MAX);
BLI_path_abs(r_filepath, ID_BLEND_PATH(bmain, &volume->id));
int path_frame, path_digits;
if (volume->is_sequence && BLI_path_frame_get(r_filepath, &path_frame, &path_digits)) {
char ext[32];
BLI_path_frame_strip(r_filepath, ext);
BLI_path_frame(r_filepath, volume->runtime.frame, path_digits);
BLI_path_extension_ensure(r_filepath, FILE_MAX, ext);
}
}
#endif
/* File Load */
bool BKE_volume_is_loaded(const Volume *volume)
{
#ifdef WITH_OPENVDB
/* Test if there is a file to load, or if already loaded. */
return (volume->filepath[0] == '\0' || volume->runtime.grids->is_loaded());
#else
UNUSED_VARS(volume);
return true;
#endif
}
bool BKE_volume_load(Volume *volume, Main *bmain)
{
#ifdef WITH_OPENVDB
VolumeGridVector &grids = *volume->runtime.grids;
if (volume->runtime.frame == VOLUME_FRAME_NONE) {
/* Skip loading this frame, outside of sequence range. */
return true;
}
if (BKE_volume_is_loaded(volume)) {
return grids.error_msg.empty();
}
/* Double-checked lock. */
std::lock_guard<std::mutex> lock(grids.mutex);
if (BKE_volume_is_loaded(volume)) {
return grids.error_msg.empty();
}
/* Get absolute file path at current frame. */
const char *volume_name = volume->id.name + 2;
volume_filepath_get(bmain, volume, grids.filepath);
CLOG_INFO(&LOG, 1, "Volume %s: load %s", volume_name, grids.filepath);
/* Test if file exists. */
if (!BLI_exists(grids.filepath)) {
char filename[FILE_MAX];
BLI_split_file_part(grids.filepath, filename, sizeof(filename));
grids.error_msg = filename + std::string(" not found");
CLOG_INFO(&LOG, 1, "Volume %s: %s", volume_name, grids.error_msg.c_str());
return false;
}
/* Open OpenVDB file. */
openvdb::io::File file(grids.filepath);
openvdb::GridPtrVec vdb_grids;
try {
file.setCopyMaxBytes(0);
file.open();
vdb_grids = *(file.readAllGridMetadata());
grids.metadata = file.getMetadata();
}
catch (const openvdb::IoError &e) {
grids.error_msg = e.what();
CLOG_INFO(&LOG, 1, "Volume %s: %s", volume_name, grids.error_msg.c_str());
}
/* Add grids read from file to own vector, filtering out any NULL pointers. */
for (const openvdb::GridBase::Ptr &vdb_grid : vdb_grids) {
if (vdb_grid) {
VolumeFileCache::Entry template_entry(grids.filepath, vdb_grid);
grids.emplace_back(template_entry, volume->runtime.default_simplify_level);
}
}
return grids.error_msg.empty();
#else
UNUSED_VARS(bmain, volume);
return true;
#endif
}
void BKE_volume_unload(Volume *volume)
{
#ifdef WITH_OPENVDB
VolumeGridVector &grids = *volume->runtime.grids;
if (grids.filepath[0] != '\0') {
const char *volume_name = volume->id.name + 2;
CLOG_INFO(&LOG, 1, "Volume %s: unload", volume_name);
grids.clear_all();
}
#else
UNUSED_VARS(volume);
#endif
}
/* File Save */
bool BKE_volume_save(Volume *volume, Main *bmain, ReportList *reports, const char *filepath)
{
#ifdef WITH_OPENVDB
if (!BKE_volume_load(volume, bmain)) {
BKE_reportf(reports, RPT_ERROR, "Could not load volume for writing");
return false;
}
VolumeGridVector &grids = *volume->runtime.grids;
openvdb::GridCPtrVec vdb_grids;
for (VolumeGrid &grid : grids) {
vdb_grids.push_back(BKE_volume_grid_openvdb_for_read(volume, &grid));
}
try {
openvdb::io::File file(filepath);
file.write(vdb_grids, *grids.metadata);
file.close();
}
catch (const openvdb::IoError &e) {
BKE_reportf(reports, RPT_ERROR, "Could not write volume: %s", e.what());
return false;
}
return true;
#else
UNUSED_VARS(volume, bmain, reports, filepath);
return false;
#endif
}
BoundBox *BKE_volume_boundbox_get(Object *ob)
{
BLI_assert(ob->type == OB_VOLUME);
if (ob->runtime.bb != nullptr && (ob->runtime.bb->flag & BOUNDBOX_DIRTY) == 0) {
return ob->runtime.bb;
}
if (ob->runtime.bb == nullptr) {
Volume *volume = (Volume *)ob->data;
ob->runtime.bb = (BoundBox *)MEM_callocN(sizeof(BoundBox), "volume boundbox");
float min[3], max[3];
bool have_minmax = false;
INIT_MINMAX(min, max);
/* TODO: if we know the volume is going to be displayed, it may be good to
* load it as part of dependency graph evaluation for better threading. We
* could also share the bounding box computation in the global volume cache. */
if (BKE_volume_load(volume, G.main)) {
const int num_grids = BKE_volume_num_grids(volume);
for (int i = 0; i < num_grids; i++) {
VolumeGrid *grid = BKE_volume_grid_get(volume, i);
float grid_min[3], grid_max[3];
BKE_volume_grid_load(volume, grid);
if (BKE_volume_grid_bounds(grid, grid_min, grid_max)) {
DO_MIN(grid_min, min);
DO_MAX(grid_max, max);
have_minmax = true;
}
}
}
if (!have_minmax) {
min[0] = min[1] = min[2] = -1.0f;
max[0] = max[1] = max[2] = 1.0f;
}
BKE_boundbox_init_from_minmax(ob->runtime.bb, min, max);
}
return ob->runtime.bb;
}
bool BKE_volume_is_y_up(const Volume *volume)
{
/* Simple heuristic for common files to open the right way up. */
#ifdef WITH_OPENVDB
VolumeGridVector &grids = *volume->runtime.grids;
if (grids.metadata) {
openvdb::StringMetadata::ConstPtr creator =
grids.metadata->getMetadata<openvdb::StringMetadata>("creator");
if (!creator) {
creator = grids.metadata->getMetadata<openvdb::StringMetadata>("Creator");
}
return (creator && creator->str().rfind("Houdini", 0) == 0);
}
#else
UNUSED_VARS(volume);
#endif
return false;
}
bool BKE_volume_is_points_only(const Volume *volume)
{
int num_grids = BKE_volume_num_grids(volume);
if (num_grids == 0) {
return false;
}
for (int i = 0; i < num_grids; i++) {
VolumeGrid *grid = BKE_volume_grid_get(volume, i);
if (BKE_volume_grid_type(grid) != VOLUME_GRID_POINTS) {
return false;
}
}
return true;
}
/* Dependency Graph */
static void volume_update_simplify_level(Volume *volume, const Depsgraph *depsgraph)
{
#ifdef WITH_OPENVDB
const int simplify_level = BKE_volume_simplify_level(depsgraph);
if (volume->runtime.grids) {
for (VolumeGrid &grid : *volume->runtime.grids) {
grid.set_simplify_level(simplify_level);
}
}
volume->runtime.default_simplify_level = simplify_level;
#else
UNUSED_VARS(volume, depsgraph);
#endif
}
static Volume *volume_evaluate_modifiers(struct Depsgraph *depsgraph,
struct Scene *scene,
Object *object,
Volume *volume_input)
{
Volume *volume = volume_input;
/* Modifier evaluation modes. */
const bool use_render = (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER);
const int required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime;
ModifierApplyFlag apply_flag = use_render ? MOD_APPLY_RENDER : MOD_APPLY_USECACHE;
const ModifierEvalContext mectx = {depsgraph, object, apply_flag};
/* Get effective list of modifiers to execute. Some effects like shape keys
* are added as virtual modifiers before the user created modifiers. */
VirtualModifierData virtualModifierData;
ModifierData *md = BKE_modifiers_get_virtual_modifierlist(object, &virtualModifierData);
/* Evaluate modifiers. */
for (; md; md = md->next) {
const ModifierTypeInfo *mti = (const ModifierTypeInfo *)BKE_modifier_get_info(
(ModifierType)md->type);
if (!BKE_modifier_is_enabled(scene, md, required_mode)) {
continue;
}
if (mti->modifyVolume) {
/* Ensure we are not modifying the input. */
if (volume == volume_input) {
volume = BKE_volume_copy_for_eval(volume, true);
}
Volume *volume_next = mti->modifyVolume(md, &mectx, volume);
if (volume_next && volume_next != volume) {
/* If the modifier returned a new volume, release the old one. */
if (volume != volume_input) {
BKE_id_free(nullptr, volume);
}
volume = volume_next;
}
}
}
return volume;
}
void BKE_volume_eval_geometry(struct Depsgraph *depsgraph, Volume *volume)
{
volume_update_simplify_level(volume, depsgraph);
/* TODO: can we avoid modifier re-evaluation when frame did not change? */
int frame = volume_sequence_frame(depsgraph, volume);
if (frame != volume->runtime.frame) {
BKE_volume_unload(volume);
volume->runtime.frame = frame;
}
/* Flush back to original. */
if (DEG_is_active(depsgraph)) {
Volume *volume_orig = (Volume *)DEG_get_original_id(&volume->id);
if (volume_orig->runtime.frame != volume->runtime.frame) {
BKE_volume_unload(volume_orig);
volume_orig->runtime.frame = volume->runtime.frame;
}
}
}
void BKE_volume_data_update(struct Depsgraph *depsgraph, struct Scene *scene, Object *object)
{
/* Free any evaluated data and restore original data. */
BKE_object_free_derived_caches(object);
/* Evaluate modifiers. */
Volume *volume = (Volume *)object->data;
Volume *volume_eval = volume_evaluate_modifiers(depsgraph, scene, object, volume);
/* Assign evaluated object. */
const bool is_owned = (volume != volume_eval);
BKE_object_eval_assign_data(object, &volume_eval->id, is_owned);
}
void BKE_volume_grids_backup_restore(Volume *volume, VolumeGridVector *grids, const char *filepath)
{
#ifdef WITH_OPENVDB
/* Restore grids after datablock was re-copied from original by depsgraph,
* we don't want to load them again if possible. */
BLI_assert(volume->id.tag & LIB_TAG_COPIED_ON_WRITE);
BLI_assert(volume->runtime.grids != nullptr && grids != nullptr);
if (!grids->is_loaded()) {
/* No grids loaded in CoW datablock, nothing lost by discarding. */
OBJECT_GUARDED_DELETE(grids, VolumeGridVector);
}
else if (!STREQ(volume->filepath, filepath)) {
/* Filepath changed, discard grids from CoW datablock. */
OBJECT_GUARDED_DELETE(grids, VolumeGridVector);
}
else {
/* Keep grids from CoW datablock. We might still unload them a little
* later in BKE_volume_eval_geometry if the frame changes. */
OBJECT_GUARDED_DELETE(volume->runtime.grids, VolumeGridVector);
volume->runtime.grids = grids;
}
#else
UNUSED_VARS(volume, grids, filepath);
#endif
}
/* Draw Cache */
void (*BKE_volume_batch_cache_dirty_tag_cb)(Volume *volume, int mode) = nullptr;
void (*BKE_volume_batch_cache_free_cb)(Volume *volume) = nullptr;
void BKE_volume_batch_cache_dirty_tag(Volume *volume, int mode)
{
if (volume->batch_cache) {
BKE_volume_batch_cache_dirty_tag_cb(volume, mode);
}
}
void BKE_volume_batch_cache_free(Volume *volume)
{
if (volume->batch_cache) {
BKE_volume_batch_cache_free_cb(volume);
}
}
/* Grids */
int BKE_volume_num_grids(const Volume *volume)
{
#ifdef WITH_OPENVDB
return volume->runtime.grids->size();
#else
UNUSED_VARS(volume);
return 0;
#endif
}
const char *BKE_volume_grids_error_msg(const Volume *volume)
{
#ifdef WITH_OPENVDB
return volume->runtime.grids->error_msg.c_str();
#else
UNUSED_VARS(volume);
return "";
#endif
}
const char *BKE_volume_grids_frame_filepath(const Volume *volume)
{
#ifdef WITH_OPENVDB
return volume->runtime.grids->filepath;
#else
UNUSED_VARS(volume);
return "";
#endif
}
VolumeGrid *BKE_volume_grid_get(const Volume *volume, int grid_index)
{
#ifdef WITH_OPENVDB
VolumeGridVector &grids = *volume->runtime.grids;
for (VolumeGrid &grid : grids) {
if (grid_index-- == 0) {
return &grid;
}
}
return nullptr;
#else
UNUSED_VARS(volume, grid_index);
return nullptr;
#endif
}
VolumeGrid *BKE_volume_grid_active_get(const Volume *volume)
{
const int num_grids = BKE_volume_num_grids(volume);
if (num_grids == 0) {
return nullptr;
}
const int index = clamp_i(volume->active_grid, 0, num_grids - 1);
return BKE_volume_grid_get(volume, index);
}
/* Tries to find a grid with the given name. Make sure that that the volume has been loaded. */
VolumeGrid *BKE_volume_grid_find(const Volume *volume, const char *name)
{
int num_grids = BKE_volume_num_grids(volume);
for (int i = 0; i < num_grids; i++) {
VolumeGrid *grid = BKE_volume_grid_get(volume, i);
if (STREQ(BKE_volume_grid_name(grid), name)) {
return grid;
}
}
return nullptr;
}
/* Grid Loading */
bool BKE_volume_grid_load(const Volume *volume, VolumeGrid *grid)
{
#ifdef WITH_OPENVDB
VolumeGridVector &grids = *volume->runtime.grids;
const char *volume_name = volume->id.name + 2;
grid->load(volume_name, grids.filepath);
const char *error_msg = grid->error_message();
if (error_msg) {
grids.error_msg = error_msg;
return false;
}
return true;
#else
UNUSED_VARS(volume, grid);
return true;
#endif
}
void BKE_volume_grid_unload(const Volume *volume, VolumeGrid *grid)
{
#ifdef WITH_OPENVDB
const char *volume_name = volume->id.name + 2;
grid->unload(volume_name);
#else
UNUSED_VARS(volume, grid);
#endif
}
bool BKE_volume_grid_is_loaded(const VolumeGrid *grid)
{
#ifdef WITH_OPENVDB
return grid->grid_is_loaded();
#else
UNUSED_VARS(grid);
return true;
#endif
}
/* Grid Metadata */
const char *BKE_volume_grid_name(const VolumeGrid *volume_grid)
{
#ifdef WITH_OPENVDB
return volume_grid->name();
#else
UNUSED_VARS(volume_grid);
return "density";
#endif
}
#ifdef WITH_OPENVDB
VolumeGridType BKE_volume_grid_type_openvdb(const openvdb::GridBase::Ptr &grid)
{
if (grid->isType<openvdb::FloatGrid>()) {
return VOLUME_GRID_FLOAT;
}
if (grid->isType<openvdb::Vec3fGrid>()) {
return VOLUME_GRID_VECTOR_FLOAT;
}
if (grid->isType<openvdb::BoolGrid>()) {
return VOLUME_GRID_BOOLEAN;
}
if (grid->isType<openvdb::DoubleGrid>()) {
return VOLUME_GRID_DOUBLE;
}
if (grid->isType<openvdb::Int32Grid>()) {
return VOLUME_GRID_INT;
}
if (grid->isType<openvdb::Int64Grid>()) {
return VOLUME_GRID_INT64;
}
if (grid->isType<openvdb::Vec3IGrid>()) {
return VOLUME_GRID_VECTOR_INT;
}
if (grid->isType<openvdb::Vec3dGrid>()) {
return VOLUME_GRID_VECTOR_DOUBLE;
}
if (grid->isType<openvdb::StringGrid>()) {
return VOLUME_GRID_STRING;
}
if (grid->isType<openvdb::MaskGrid>()) {
return VOLUME_GRID_MASK;
}
if (grid->isType<openvdb::points::PointDataGrid>()) {
return VOLUME_GRID_POINTS;
}
return VOLUME_GRID_UNKNOWN;
}
#endif
VolumeGridType BKE_volume_grid_type(const VolumeGrid *volume_grid)
{
#ifdef WITH_OPENVDB
const openvdb::GridBase::Ptr grid = volume_grid->grid();
return BKE_volume_grid_type_openvdb(grid);
#else
UNUSED_VARS(volume_grid);
#endif
return VOLUME_GRID_UNKNOWN;
}
int BKE_volume_grid_channels(const VolumeGrid *grid)
{
switch (BKE_volume_grid_type(grid)) {
case VOLUME_GRID_BOOLEAN:
case VOLUME_GRID_FLOAT:
case VOLUME_GRID_DOUBLE:
case VOLUME_GRID_INT:
case VOLUME_GRID_INT64:
case VOLUME_GRID_MASK:
return 1;
case VOLUME_GRID_VECTOR_FLOAT:
case VOLUME_GRID_VECTOR_DOUBLE:
case VOLUME_GRID_VECTOR_INT:
return 3;
case VOLUME_GRID_STRING:
case VOLUME_GRID_POINTS:
case VOLUME_GRID_UNKNOWN:
return 0;
}
return 0;
}
/* Transformation from index space to object space. */
void BKE_volume_grid_transform_matrix(const VolumeGrid *volume_grid, float mat[4][4])
{
#ifdef WITH_OPENVDB
const openvdb::GridBase::Ptr grid = volume_grid->grid();
const openvdb::math::Transform &transform = grid->transform();
/* Perspective not supported for now, getAffineMap() will leave out the
* perspective part of the transform. */
openvdb::math::Mat4f matrix = transform.baseMap()->getAffineMap()->getMat4();
/* Blender column-major and OpenVDB right-multiplication conventions match. */
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 4; row++) {
mat[col][row] = matrix(col, row);
}
}
#else
unit_m4(mat);
UNUSED_VARS(volume_grid);
#endif
}
/* Grid Tree and Voxels */
bool BKE_volume_grid_bounds(const VolumeGrid *volume_grid, float min[3], float max[3])
{
#ifdef WITH_OPENVDB
/* TODO: we can get this from grid metadata in some cases? */
const openvdb::GridBase::Ptr grid = volume_grid->grid();
BLI_assert(BKE_volume_grid_is_loaded(volume_grid));
openvdb::CoordBBox coordbbox;
if (!grid->baseTree().evalLeafBoundingBox(coordbbox)) {
INIT_MINMAX(min, max);
return false;
}
openvdb::BBoxd bbox = grid->transform().indexToWorld(coordbbox);
min[0] = (float)bbox.min().x();
min[1] = (float)bbox.min().y();
min[2] = (float)bbox.min().z();
max[0] = (float)bbox.max().x();
max[1] = (float)bbox.max().y();
max[2] = (float)bbox.max().z();
return true;
#else
UNUSED_VARS(volume_grid);
INIT_MINMAX(min, max);
return false;
#endif
}
/* Volume Editing */
Volume *BKE_volume_new_for_eval(const Volume *volume_src)
{
Volume *volume_dst = (Volume *)BKE_id_new_nomain(ID_VO, nullptr);
STRNCPY(volume_dst->id.name, volume_src->id.name);
volume_dst->mat = (Material **)MEM_dupallocN(volume_src->mat);
volume_dst->totcol = volume_src->totcol;
volume_dst->render = volume_src->render;
volume_dst->display = volume_src->display;
BKE_volume_init_grids(volume_dst);
return volume_dst;
}
Volume *BKE_volume_copy_for_eval(Volume *volume_src, bool reference)
{
int flags = LIB_ID_COPY_LOCALIZE;
if (reference) {
flags |= LIB_ID_COPY_CD_REFERENCE;
}
Volume *result = (Volume *)BKE_id_copy_ex(nullptr, &volume_src->id, nullptr, flags);
return result;
}
#ifdef WITH_OPENVDB
struct CreateGridOp {
template<typename GridType> typename openvdb::GridBase::Ptr operator()()
{
if constexpr (std::is_same_v<GridType, openvdb::points::PointDataGrid>) {
return {};
}
else {
return GridType::create();
}
}
};
#endif
VolumeGrid *BKE_volume_grid_add(Volume *volume, const char *name, VolumeGridType type)
{
#ifdef WITH_OPENVDB
VolumeGridVector &grids = *volume->runtime.grids;
BLI_assert(BKE_volume_grid_find(volume, name) == nullptr);
BLI_assert(type != VOLUME_GRID_UNKNOWN);
openvdb::GridBase::Ptr vdb_grid = BKE_volume_grid_type_operation(type, CreateGridOp{});
if (!vdb_grid) {
return nullptr;
}
vdb_grid->setName(name);
grids.emplace_back(vdb_grid);
return &grids.back();
#else
UNUSED_VARS(volume, name, type);
return nullptr;
#endif
}
void BKE_volume_grid_remove(Volume *volume, VolumeGrid *grid)
{
#ifdef WITH_OPENVDB
VolumeGridVector &grids = *volume->runtime.grids;
for (VolumeGridVector::iterator it = grids.begin(); it != grids.end(); it++) {
if (&*it == grid) {
grids.erase(it);
break;
}
}
#else
UNUSED_VARS(volume, grid);
#endif
}
int BKE_volume_simplify_level(const Depsgraph *depsgraph)
{
if (DEG_get_mode(depsgraph) != DAG_EVAL_RENDER) {
const Scene *scene = DEG_get_input_scene(depsgraph);
if (scene->r.mode & R_SIMPLIFY) {
const float simplify = scene->r.simplify_volumes;
if (simplify == 0.0f) {
/* log2 is not defined at 0.0f, so just use some high simplify level. */
return 16;
}
return ceilf(-log2(simplify));
}
}
return 0;
}
float BKE_volume_simplify_factor(const Depsgraph *depsgraph)
{
if (DEG_get_mode(depsgraph) != DAG_EVAL_RENDER) {
const Scene *scene = DEG_get_input_scene(depsgraph);
if (scene->r.mode & R_SIMPLIFY) {
return scene->r.simplify_volumes;
}
}
return 1.0f;
}
/* OpenVDB Grid Access */
#ifdef WITH_OPENVDB
openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_metadata(const VolumeGrid *grid)
{
return grid->grid();
}
openvdb::GridBase::ConstPtr BKE_volume_grid_openvdb_for_read(const Volume *volume,
VolumeGrid *grid)
{
BKE_volume_grid_load(volume, grid);
return grid->grid();
}
openvdb::GridBase::Ptr BKE_volume_grid_openvdb_for_write(const Volume *volume,
VolumeGrid *grid,
const bool clear)
{
const char *volume_name = volume->id.name + 2;
if (clear) {
grid->clear_reference(volume_name);
}
else {
VolumeGridVector &grids = *volume->runtime.grids;
grid->duplicate_reference(volume_name, grids.filepath);
}
return grid->grid();
}
/* Changing the resolution of a grid. */
/**
* Returns a grid of the same type as the input, but with more/less resolution. If
* resolution_factor is 1/2, the resolution on each axis is halved. The transform of the returned
* grid is adjusted to match the original grid. */
template<typename GridType>
static typename GridType::Ptr create_grid_with_changed_resolution(const GridType &old_grid,
const float resolution_factor)
{
BLI_assert(resolution_factor > 0.0f);
openvdb::Mat4R xform;
xform.setToScale(openvdb::Vec3d(resolution_factor));
openvdb::tools::GridTransformer transformer{xform};
typename GridType::Ptr new_grid = old_grid.copyWithNewTree();
transformer.transformGrid<openvdb::tools::BoxSampler>(old_grid, *new_grid);
new_grid->transform() = old_grid.transform();
new_grid->transform().preScale(1.0f / resolution_factor);
new_grid->transform().postTranslate(-new_grid->voxelSize() / 2.0f);
return new_grid;
}
struct CreateGridWithChangedResolutionOp {
const openvdb::GridBase &grid;
const float resolution_factor;
template<typename GridType> typename openvdb::GridBase::Ptr operator()()
{
if constexpr (std::is_same_v<GridType, openvdb::StringGrid>) {
return {};
}
else {
return create_grid_with_changed_resolution(static_cast<const GridType &>(grid),
resolution_factor);
}
}
};
openvdb::GridBase::Ptr BKE_volume_grid_create_with_changed_resolution(
const VolumeGridType grid_type,
const openvdb::GridBase &old_grid,
const float resolution_factor)
{
CreateGridWithChangedResolutionOp op{old_grid, resolution_factor};
return BKE_volume_grid_type_operation(grid_type, op);
}
#endif