From 871fda82e662d889046e37061b582758f705a2af Mon Sep 17 00:00:00 2001 From: "Michael A. Kowalski" Date: Thu, 3 Dec 2020 21:01:59 -0500 Subject: USD importer: instancing improvements. Added new USDDataCache class for caching prototype mesh data. No longer using a global static cache for prototype meshes. Now precomputing the prototype meshes in a USDDataCache instance which is passed as an argument to USDXformableReade::create_objects(). These changes simplify the code, provide better support for thread safety and circumvent potential issues with dangling pointers in the previous implementation. --- source/blender/io/usd/CMakeLists.txt | 2 + source/blender/io/usd/import/usd_data_cache.cc | 57 ++++++++++++++++++++ source/blender/io/usd/import/usd_data_cache.h | 60 ++++++++++++++++++++++ .../blender/io/usd/import/usd_importer_context.h | 1 - source/blender/io/usd/import/usd_prim_iterator.cc | 53 ++++++++++++++++--- source/blender/io/usd/import/usd_prim_iterator.h | 18 ++++--- source/blender/io/usd/import/usd_reader_camera.cc | 2 +- source/blender/io/usd/import/usd_reader_camera.h | 2 +- source/blender/io/usd/import/usd_reader_light.cc | 2 +- source/blender/io/usd/import/usd_reader_light.h | 2 +- .../blender/io/usd/import/usd_reader_mesh_base.cc | 57 +++++--------------- .../blender/io/usd/import/usd_reader_mesh_base.h | 16 +----- source/blender/io/usd/import/usd_reader_xform.cc | 2 +- source/blender/io/usd/import/usd_reader_xform.h | 2 +- .../blender/io/usd/import/usd_reader_xformable.h | 4 +- source/blender/io/usd/intern/usd_capi.cc | 25 ++++----- 16 files changed, 211 insertions(+), 94 deletions(-) create mode 100644 source/blender/io/usd/import/usd_data_cache.cc create mode 100644 source/blender/io/usd/import/usd_data_cache.h diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt index dc22045476a..6d693401b6d 100644 --- a/source/blender/io/usd/CMakeLists.txt +++ b/source/blender/io/usd/CMakeLists.txt @@ -54,6 +54,7 @@ set(INC_SYS set(SRC + import/usd_data_cache.cc import/usd_import_util.cc import/usd_material_importer.cc import/usd_prim_iterator.cc @@ -75,6 +76,7 @@ set(SRC intern/usd_writer_transform.cc usd.h + import/usd_data_cache.h import/usd_importer_context.h import/usd_import_util.h import/usd_material_importer.h diff --git a/source/blender/io/usd/import/usd_data_cache.cc b/source/blender/io/usd/import/usd_data_cache.cc new file mode 100644 index 00000000000..55558aba517 --- /dev/null +++ b/source/blender/io/usd/import/usd_data_cache.cc @@ -0,0 +1,57 @@ +/* + * 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. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ + +#include "usd_data_cache.h" + +#include + +namespace blender::io::usd { + +USDDataCache::USDDataCache() +{ +} + +USDDataCache::~USDDataCache() +{ + /* TODO(makowalski): decrement use count and/or delete the cached data? */ +} + +bool USDDataCache::add_prototype_mesh(const pxr::SdfPath path, Mesh *mesh) +{ + /* TODO(makowalsk): should we increment mesh use count? */ + return prototype_meshes_.insert(std::make_pair(path, mesh)).second; +} + +void USDDataCache::clear_protype_mesh(const pxr::SdfPath &path) +{ + /* TODO(makowalsk): should we decrement mesh use count or delete mesh? */ + prototype_meshes_.erase(path); +} + +Mesh *USDDataCache::get_prototype_mesh(const pxr::SdfPath &path) const +{ + std::map::const_iterator it = prototype_meshes_.find(path); + if (it != prototype_meshes_.end()) { + return it->second; + } + + return nullptr; +} + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/import/usd_data_cache.h b/source/blender/io/usd/import/usd_data_cache.h new file mode 100644 index 00000000000..6f3767387f6 --- /dev/null +++ b/source/blender/io/usd/import/usd_data_cache.h @@ -0,0 +1,60 @@ +/* + * 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. + * + * The Original Code is Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ +#pragma once + +#include + +#include + +struct Mesh; + +namespace blender::io::usd { + +/* Caches data imported from USD, typically shared data for instanced primitives. */ + +class USDDataCache { + protected: + /* Shared meshes for instancing. */ + std::map prototype_meshes_; + + public: + USDDataCache(); + + ~USDDataCache(); + + const std::map &prototype_meshes() const + { + return prototype_meshes_; + } + + void clear_prototype_meshes() + { + /* TODO(makowalsk): should we decrement mesh use counts or delete meshes? */ + prototype_meshes_.clear(); + } + + bool add_prototype_mesh(const pxr::SdfPath path, Mesh *mesh); + + void clear_protype_mesh(const pxr::SdfPath &path); + + Mesh *get_prototype_mesh(const pxr::SdfPath &path) const; + +}; + +} // namespace blender::io::usd diff --git a/source/blender/io/usd/import/usd_importer_context.h b/source/blender/io/usd/import/usd_importer_context.h index 0970fd9e789..49750a55dfc 100644 --- a/source/blender/io/usd/import/usd_importer_context.h +++ b/source/blender/io/usd/import/usd_importer_context.h @@ -31,7 +31,6 @@ typedef std::map ObjectReaderMap; struct USDImporterContext { const pxr::TfToken stage_up_axis; const USDImportParams import_params; - ObjectReaderMap *proto_readers; }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/import/usd_prim_iterator.cc b/source/blender/io/usd/import/usd_prim_iterator.cc index 7f6f311e90a..18d4bc11d46 100644 --- a/source/blender/io/usd/import/usd_prim_iterator.cc +++ b/source/blender/io/usd/import/usd_prim_iterator.cc @@ -20,6 +20,7 @@ #include "usd_prim_iterator.h" #include "usd.h" +#include "usd_data_cache.h" #include "usd_importer_context.h" #include "usd_reader_camera.h" #include "usd_reader_light.h" @@ -41,23 +42,24 @@ namespace blender::io::usd { -USDPrimIterator::USDPrimIterator(pxr::UsdStageRefPtr stage) : stage_(stage) +USDPrimIterator::USDPrimIterator(pxr::UsdStageRefPtr stage, + const USDImporterContext &context, + Main *bmain) + : stage_(stage), context_(context), bmain_(bmain) { } -void USDPrimIterator::create_object_readers(const USDImporterContext &context, - std::vector &r_readers) const +void USDPrimIterator::create_object_readers(std::vector &r_readers) const { if (!stage_) { return; } std::vector child_readers; - create_object_readers(stage_->GetPseudoRoot(), context, r_readers, child_readers); + create_object_readers(stage_->GetPseudoRoot(), context_, r_readers, child_readers); } void USDPrimIterator::create_prototype_object_readers( - const USDImporterContext &context, std::map &r_proto_readers) const { if (!stage_) { @@ -70,7 +72,7 @@ void USDPrimIterator::create_prototype_object_readers( std::vector proto_readers; std::vector child_readers; - create_object_readers(proto_prim, context, proto_readers, child_readers); + create_object_readers(proto_prim, context_, proto_readers, child_readers); for (USDXformableReader *reader : proto_readers) { if (reader) { @@ -168,6 +170,45 @@ void USDPrimIterator::create_object_readers(const pxr::UsdPrim &prim, } } +void USDPrimIterator::cache_prototype_data(USDDataCache &r_cache) const +{ + if (!stage_) { + return; + } + + std::vector protos = stage_->GetMasters(); + + for (const pxr::UsdPrim &proto_prim : protos) { + std::vector proto_readers; + std::vector child_readers; + + create_object_readers(proto_prim, context_, proto_readers, child_readers); + + for (USDXformableReader *reader : proto_readers) { + if (reader) { + pxr::UsdPrim reader_prim = reader->prim(); + if (reader_prim) { + + if (USDMeshReaderBase *mesh_reader = dynamic_cast(reader)) { + Mesh *proto_mesh = mesh_reader->create_mesh(bmain_, 0.0); + if (proto_mesh) { + /* TODO(makowalski): Do we want to decrement the mesh's use count to 0? + * Might have a small memory leak otherwise. Also, check if mesh is + * already in cache before adding? */ + r_cache.add_prototype_mesh(reader_prim.GetPath(), proto_mesh); + } + } + } + } + } + + /* Clean up the readers. */ + for (USDXformableReader *reader : proto_readers) { + delete reader; + } + } +} + void USDPrimIterator::debug_traverse_stage(const pxr::UsdStageRefPtr &usd_stage) { if (!usd_stage) { diff --git a/source/blender/io/usd/import/usd_prim_iterator.h b/source/blender/io/usd/import/usd_prim_iterator.h index 672dbb098c6..1605d919051 100644 --- a/source/blender/io/usd/import/usd_prim_iterator.h +++ b/source/blender/io/usd/import/usd_prim_iterator.h @@ -18,30 +18,36 @@ */ #pragma once -#include "pxr/usd/usd/common.h" +#include "usd_importer_context.h" + +#include #include #include +struct Main; + namespace blender::io::usd { -struct USDImporterContext; +class USDDataCache; class USDXformableReader; class USDPrimIterator { protected: pxr::UsdStageRefPtr stage_; + USDImporterContext context_; + Main *bmain_; public: - USDPrimIterator(pxr::UsdStageRefPtr stage); + USDPrimIterator(pxr::UsdStageRefPtr stage, const USDImporterContext &context, Main *bmain); - void create_object_readers(const USDImporterContext &context, - std::vector &r_readers) const; + void create_object_readers(std::vector &r_readers) const; void create_prototype_object_readers( - const USDImporterContext &context, std::map &r_proto_readers) const; + void cache_prototype_data(USDDataCache &r_cache) const; + void debug_traverse_stage() const; static USDXformableReader *get_object_reader(const pxr::UsdPrim &prim, diff --git a/source/blender/io/usd/import/usd_reader_camera.cc b/source/blender/io/usd/import/usd_reader_camera.cc index 6ad2b55dcb0..418d6a4ee77 100644 --- a/source/blender/io/usd/import/usd_reader_camera.cc +++ b/source/blender/io/usd/import/usd_reader_camera.cc @@ -43,7 +43,7 @@ bool USDCameraReader::valid() const return static_cast(camera_); } -void USDCameraReader::create_object(Main *bmain, double time) +void USDCameraReader::create_object(Main *bmain, double time, USDDataCache *data_cache) { if (!this->valid()) { return; diff --git a/source/blender/io/usd/import/usd_reader_camera.h b/source/blender/io/usd/import/usd_reader_camera.h index 26614f86d28..85e71e66bea 100644 --- a/source/blender/io/usd/import/usd_reader_camera.h +++ b/source/blender/io/usd/import/usd_reader_camera.h @@ -35,7 +35,7 @@ class USDCameraReader : public USDXformableReader { bool valid() const override; - void create_object(Main *bmain, double time) override; + void create_object(Main *bmain, double time, USDDataCache *data_cache) override; void read_matrix(float r_mat[4][4], const double time, const float scale) const override; }; diff --git a/source/blender/io/usd/import/usd_reader_light.cc b/source/blender/io/usd/import/usd_reader_light.cc index cdfdb293b49..a006523b2ec 100644 --- a/source/blender/io/usd/import/usd_reader_light.cc +++ b/source/blender/io/usd/import/usd_reader_light.cc @@ -50,7 +50,7 @@ bool USDLightReader::valid() const return static_cast(light_); } -void USDLightReader::create_object(Main *bmain, double time) +void USDLightReader::create_object(Main *bmain, double time, USDDataCache *data_cache) { if (!this->valid()) { return; diff --git a/source/blender/io/usd/import/usd_reader_light.h b/source/blender/io/usd/import/usd_reader_light.h index 318e0e50af7..db061c9f101 100644 --- a/source/blender/io/usd/import/usd_reader_light.h +++ b/source/blender/io/usd/import/usd_reader_light.h @@ -35,7 +35,7 @@ class USDLightReader : public USDXformableReader { bool valid() const override; - void create_object(Main *bmain, double time) override; + void create_object(Main *bmain, double time, USDDataCache *data_cache) override; void read_matrix(float r_mat[4][4], const double time, const float scale) const override; }; diff --git a/source/blender/io/usd/import/usd_reader_mesh_base.cc b/source/blender/io/usd/import/usd_reader_mesh_base.cc index 954ffcfc8a5..44bdd4ccde7 100644 --- a/source/blender/io/usd/import/usd_reader_mesh_base.cc +++ b/source/blender/io/usd/import/usd_reader_mesh_base.cc @@ -18,6 +18,7 @@ */ #include "usd_reader_mesh_base.h" +#include "usd_data_cache.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -34,8 +35,6 @@ namespace blender::io::usd { -std::map USDMeshReaderBase::s_prototype_meshes; - USDMeshReaderBase::USDMeshReaderBase(const pxr::UsdPrim &prim, const USDImporterContext &context) : USDXformableReader(prim, context) { @@ -45,7 +44,7 @@ USDMeshReaderBase::~USDMeshReaderBase() { } -void USDMeshReaderBase::create_object(Main *bmain, double time) +void USDMeshReaderBase::create_object(Main *bmain, double time, USDDataCache *data_cache) { if (!this->valid()) { return; @@ -76,7 +75,7 @@ void USDMeshReaderBase::create_object(Main *bmain, double time) } object_ = BKE_object_add_only_object(bmain, OB_MESH, obj_name.c_str()); - Mesh *mesh = this->read_mesh(bmain, time); + Mesh *mesh = this->read_mesh(bmain, time, data_cache); object_->data = mesh; if (this->context_.import_params.import_materials) { @@ -84,13 +83,12 @@ void USDMeshReaderBase::create_object(Main *bmain, double time) } } -Mesh *USDMeshReaderBase::read_mesh(Main *bmain, double time) +Mesh *USDMeshReaderBase::read_mesh(Main *bmain, double time, USDDataCache *data_cache) { /* If this prim is an instance proxy and instancing is enabled, * return the shared mesh created by the instance prototype. */ - if (this->context_.import_params.use_instancing && this->context_.proto_readers && - this->prim_.IsInstanceProxy()) { + if (this->context_.import_params.use_instancing && data_cache && this->prim_.IsInstanceProxy()) { pxr::UsdPrim proto_prim = this->prim_.GetPrimInMaster(); @@ -98,45 +96,16 @@ Mesh *USDMeshReaderBase::read_mesh(Main *bmain, double time) pxr::SdfPath proto_path = proto_prim.GetPath(); - /* See if the prototype is already been cached. */ - std::map::const_iterator proto_mesh_iter = s_prototype_meshes.find( - proto_path); - if (proto_mesh_iter != s_prototype_meshes.end()) { - Mesh *cached_mesh = proto_mesh_iter->second; - if (cached_mesh) { - /* Increment the user count before returning. */ - id_us_plus(&cached_mesh->id); - } - return cached_mesh; - } - - /* No cached mesh. Lookup the reader for the prototype prim. */ - - ObjectReaderMap::iterator proto_reader_iter = this->context_.proto_readers->find(proto_path); - - if (proto_reader_iter != this->context_.proto_readers->end()) { + Mesh *proto_mesh = data_cache->get_prototype_mesh(proto_path); - USDXformableReader *proto_reader = proto_reader_iter->second; - - USDMeshReaderBase *proto_mesh_reader = dynamic_cast(proto_reader); - - if (proto_mesh_reader) { - Mesh *proto_mesh = proto_mesh_reader->create_mesh(bmain, time); - - if (proto_mesh) { - s_prototype_meshes.insert(std::make_pair(proto_path, proto_mesh)); - return proto_mesh; - } - else { - std::cerr << "Couldn't evaluate prototype " << proto_path.GetString() - << " mesh for instance " << this->prim_path_ << std::endl; - } - } - else { - std::cerr << "Invalid prototype " << proto_path.GetString() - << " reader type for instance " << this->prim_path_ << std::endl; - } + if (proto_mesh) { + /* Increment the user count before returning. */ + id_us_plus(&proto_mesh->id); + return proto_mesh; } + + std::cerr << "WARNING: no cached mesh for prototype " << proto_path << " for instance " + << this->prim_path_ << std::endl; } } diff --git a/source/blender/io/usd/import/usd_reader_mesh_base.h b/source/blender/io/usd/import/usd_reader_mesh_base.h index d30e5c05f92..15cb6643071 100644 --- a/source/blender/io/usd/import/usd_reader_mesh_base.h +++ b/source/blender/io/usd/import/usd_reader_mesh_base.h @@ -32,29 +32,17 @@ namespace blender::io::usd { class USDMeshReaderBase : public USDXformableReader { protected: - /* Shared meshes for instancing. */ - static std::map s_prototype_meshes; - public: USDMeshReaderBase(const pxr::UsdPrim &prim, const USDImporterContext &context); virtual ~USDMeshReaderBase(); - void create_object(Main *bmain, double time) override; + void create_object(Main *bmain, double time, USDDataCache *data_cache) override; - struct Mesh *read_mesh(Main *bmain, double time); + struct Mesh *read_mesh(Main *bmain, double time, USDDataCache *data_cache); virtual struct Mesh *create_mesh(Main *bmain, double time) = 0; virtual void assign_materials(Main *bmain, Mesh *mesh, double time) = 0; - - static const std::map &prototype_meshes() - { - return s_prototype_meshes; - } - static void clear_prototype_meshes() - { - s_prototype_meshes.clear(); - } }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/import/usd_reader_xform.cc b/source/blender/io/usd/import/usd_reader_xform.cc index 2b670d173e0..8ed44d709ff 100644 --- a/source/blender/io/usd/import/usd_reader_xform.cc +++ b/source/blender/io/usd/import/usd_reader_xform.cc @@ -36,7 +36,7 @@ bool USDXformReader::valid() const return static_cast(xform_); } -void USDXformReader::create_object(Main *bmain, double time) +void USDXformReader::create_object(Main *bmain, double time, USDDataCache *data_cache) { if (!this->valid()) { return; diff --git a/source/blender/io/usd/import/usd_reader_xform.h b/source/blender/io/usd/import/usd_reader_xform.h index 4be0f2d1273..5f685047972 100644 --- a/source/blender/io/usd/import/usd_reader_xform.h +++ b/source/blender/io/usd/import/usd_reader_xform.h @@ -40,7 +40,7 @@ class USDXformReader : public USDXformableReader { return false; } - void create_object(Main *bmain, double time) override; + void create_object(Main *bmain, double time, USDDataCache *data_cache) override; }; } // namespace blender::io::usd diff --git a/source/blender/io/usd/import/usd_reader_xformable.h b/source/blender/io/usd/import/usd_reader_xformable.h index 3f3503dcfda..e9b7dc49ea6 100644 --- a/source/blender/io/usd/import/usd_reader_xformable.h +++ b/source/blender/io/usd/import/usd_reader_xformable.h @@ -31,6 +31,8 @@ struct Object; namespace blender::io::usd { +class USDDataCache; + /* Wraps the UsdGeomXformable schema. Abstract base class for all readers * that create a Blender object and compute its transform. */ @@ -82,7 +84,7 @@ class USDXformableReader : public USDPrimReader { virtual bool valid() const = 0; - virtual void create_object(Main *bmain, double time) = 0; + virtual void create_object(Main *bmain, double time, USDDataCache *data_cache) = 0; virtual bool can_merge_with_parent() const { diff --git a/source/blender/io/usd/intern/usd_capi.cc b/source/blender/io/usd/intern/usd_capi.cc index 90ffe235736..4532335b799 100644 --- a/source/blender/io/usd/intern/usd_capi.cc +++ b/source/blender/io/usd/intern/usd_capi.cc @@ -17,6 +17,7 @@ * All rights reserved. */ +#include "import/usd_data_cache.h" #include "import/usd_importer_context.h" #include "import/usd_prim_iterator.h" #include "import/usd_reader_mesh_base.h" @@ -234,7 +235,7 @@ struct ImportJobData { pxr::UsdStageRefPtr stage; std::vector readers; - std::map proto_readers; + USDDataCache data_cache; }; static void import_startjob(void *user_data, short *stop, short *do_update, float *progress) @@ -260,9 +261,9 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa } pxr::TfToken up_axis = pxr::UsdGeomGetStageUpAxis(data->stage); - USDImporterContext import_ctx{up_axis, data->params, &data->proto_readers}; + USDImporterContext import_ctx{up_axis, data->params}; - USDPrimIterator usd_prim_iter(data->stage); + USDPrimIterator usd_prim_iter(data->stage, import_ctx, data->bmain); // Optionally print the stage contents for debugging. if (data->params.debug) { @@ -277,13 +278,13 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa *data->do_update = true; *data->progress = 0.1f; - /* Optionally get the prototype prim readers for instancing. */ + /* Optionally, cache the prototype data for instancing. */ if (data->params.use_instancing) { - usd_prim_iter.create_prototype_object_readers(import_ctx, data->proto_readers); + usd_prim_iter.cache_prototype_data(data->data_cache); } /* Get the xformable prim readers. */ - usd_prim_iter.create_object_readers(import_ctx, data->readers); + usd_prim_iter.create_object_readers(data->readers); // Create objects @@ -297,7 +298,7 @@ static void import_startjob(void *user_data, short *stop, short *do_update, floa USDXformableReader *reader = *iter; if (reader->valid()) { - reader->create_object(data->bmain, time); + reader->create_object(data->bmain, time, &data->data_cache); } else { std::cerr << "Object " << reader->prim_path() << " in USD file " << data->filename @@ -410,15 +411,7 @@ static void import_endjob(void *user_data) data->readers.clear(); - for (ObjectReaderMap::iterator map_iter = data->proto_readers.begin(); - map_iter != data->proto_readers.end(); - ++map_iter) { - delete map_iter->second; - } - - data->proto_readers.clear(); - - USDMeshReaderBase::clear_prototype_meshes(); + /* TODO(makowalski): Explicitly clear the data cache as well? */ WM_set_locked_interface(data->wm, false); -- cgit v1.2.3