From 9b92ce9dc00971cd9f7f0d0ba6109a9cba2090bd Mon Sep 17 00:00:00 2001
From: Brecht Van Lommel <brecht@blender.org>
Date: Sun, 17 Apr 2022 07:01:13 +0200
Subject: [PATCH] Cycles: add USD as a file format for Cycles standalone
 rendering

Long term, this should replace the XML format. This reuses the Hydra render
delegate implementation, and so supports the same features. The same command
line options and GUI work for both XML and USD also.

The implementation of this is still disabled, waiting for some refactoring of
USD library linking. However we want the Cycles code to be in sync between
repositories for the 3.2 release.

Ref T96731
---
 intern/cycles/app/cycles_standalone.cpp |  16 +++-
 intern/cycles/hydra/CMakeLists.txt      |   2 +
 intern/cycles/hydra/file_reader.cpp     | 121 ++++++++++++++++++++++++
 intern/cycles/hydra/file_reader.h       |  17 ++++
 intern/cycles/hydra/geometry.inl        |   9 +-
 intern/cycles/hydra/light.cpp           |   6 +-
 intern/cycles/hydra/material.cpp        |   5 +-
 intern/cycles/hydra/render_delegate.cpp |   6 +-
 intern/cycles/hydra/render_delegate.h   |   3 +-
 intern/cycles/hydra/session.cpp         |   5 +-
 intern/cycles/hydra/session.h           |   3 +-
 11 files changed, 181 insertions(+), 12 deletions(-)
 create mode 100644 intern/cycles/hydra/file_reader.cpp
 create mode 100644 intern/cycles/hydra/file_reader.h

diff --git a/intern/cycles/app/cycles_standalone.cpp b/intern/cycles/app/cycles_standalone.cpp
index 87ab84eb209..8b40adc8d92 100644
--- a/intern/cycles/app/cycles_standalone.cpp
+++ b/intern/cycles/app/cycles_standalone.cpp
@@ -23,6 +23,10 @@
 #include "util/unique_ptr.h"
 #include "util/version.h"
 
+#ifdef WITH_USD
+#  include "hydra/file_reader.h"
+#endif
+
 #include "app/cycles_xml.h"
 #include "app/oiio_output_driver.h"
 
@@ -94,8 +98,16 @@ static void scene_init()
 {
   options.scene = options.session->scene;
 
-  /* Read XML */
-  xml_read_file(options.scene, options.filepath.c_str());
+  /* Read XML or USD */
+#ifdef WITH_USD
+  if (!string_endswith(string_to_lower(options.filepath), ".xml")) {
+    HD_CYCLES_NS::HdCyclesFileReader::read(options.session, options.filepath.c_str());
+  }
+  else
+#endif
+  {
+    xml_read_file(options.scene, options.filepath.c_str());
+  }
 
   /* Camera width/height override? */
   if (!(options.width == 0 || options.height == 0)) {
diff --git a/intern/cycles/hydra/CMakeLists.txt b/intern/cycles/hydra/CMakeLists.txt
index 17a7f897ac6..aa194fb936e 100644
--- a/intern/cycles/hydra/CMakeLists.txt
+++ b/intern/cycles/hydra/CMakeLists.txt
@@ -27,6 +27,7 @@ set(INC_HD_CYCLES
   config.h
   curves.h
   field.h
+  file_reader.h
   geometry.h
   geometry.inl
   instancer.h
@@ -48,6 +49,7 @@ set(SRC_HD_CYCLES
   curves.cpp
   camera.cpp
   field.cpp
+  file_reader.cpp
   instancer.cpp
   light.cpp
   material.cpp
diff --git a/intern/cycles/hydra/file_reader.cpp b/intern/cycles/hydra/file_reader.cpp
new file mode 100644
index 00000000000..329cc959ac3
--- /dev/null
+++ b/intern/cycles/hydra/file_reader.cpp
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2011-2022 Blender Foundation */
+
+#include "hydra/file_reader.h"
+#include "hydra/camera.h"
+#include "hydra/render_delegate.h"
+
+#include "util/path.h"
+#include "util/unique_ptr.h"
+
+#include "scene/scene.h"
+
+#include <pxr/base/plug/registry.h>
+#include <pxr/imaging/hd/dirtyList.h>
+#include <pxr/imaging/hd/renderDelegate.h>
+#include <pxr/imaging/hd/renderIndex.h>
+#include <pxr/imaging/hd/rprimCollection.h>
+#include <pxr/imaging/hd/task.h>
+#include <pxr/usd/usd/stage.h>
+#include <pxr/usd/usdGeom/camera.h>
+#include <pxr/usdImaging/usdImaging/delegate.h>
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+/* Dummy task whose only purpose is to provide render tag tokens to the render index. */
+class DummyHdTask : public HdTask {
+ public:
+  DummyHdTask(HdSceneDelegate *delegate, SdfPath const &id)
+      : HdTask(id), tags({HdRenderTagTokens->geometry, HdRenderTagTokens->render})
+  {
+  }
+
+ protected:
+  void Sync(HdSceneDelegate *delegate, HdTaskContext *ctx, HdDirtyBits *dirtyBits) override
+  {
+  }
+
+  void Prepare(HdTaskContext *ctx, HdRenderIndex *render_index) override
+  {
+  }
+
+  void Execute(HdTaskContext *ctx) override
+  {
+  }
+
+  const TfTokenVector &GetRenderTags() const override
+  {
+    return tags;
+  }
+
+  TfTokenVector tags;
+};
+
+void HdCyclesFileReader::read(Session *session, const char *filepath, const bool use_camera)
+{
+  /* Initialize USD. */
+  PlugRegistry::GetInstance().RegisterPlugins(path_get("usd"));
+
+  /* Open Stage. */
+  UsdStageRefPtr stage = UsdStage::Open(filepath);
+  if (!stage) {
+    fprintf(stderr, "%s read error\n", filepath);
+    return;
+  }
+
+  /* Init paths. */
+  SdfPath root_path = SdfPath::AbsoluteRootPath();
+  SdfPath task_path("/_hdCycles/DummyHdTask");
+
+  /* Create render delegate. */
+  HdRenderSettingsMap settings_map;
+  HdCyclesDelegate render_delegate(settings_map, session, true);
+
+  /* Create render index and scene delegate. */
+  unique_ptr<HdRenderIndex> render_index(HdRenderIndex::New(&render_delegate, {}));
+  unique_ptr<UsdImagingDelegate> scene_delegate = make_unique<UsdImagingDelegate>(
+      render_index.get(), root_path);
+
+  /* Add render tags and collection to render index. */
+  HdRprimCollection collection(HdTokens->geometry, HdReprSelector(HdReprTokens->smoothHull));
+  collection.SetRootPath(root_path);
+
+  render_index->InsertTask<DummyHdTask>(scene_delegate.get(), task_path);
+
+#if PXR_VERSION < 2111
+  HdDirtyListSharedPtr dirty_list = std::make_shared<HdDirtyList>(collection,
+                                                                  *(render_index.get()));
+  render_index->EnqueuePrimsToSync(dirty_list, collection);
+#else
+  render_index->EnqueueCollectionToSync(collection);
+#endif
+
+  /* Create prims. */
+  const UsdPrim &stage_root = stage->GetPseudoRoot();
+  scene_delegate->Populate(stage_root.GetStage()->GetPrimAtPath(root_path), {});
+
+  /* Sync prims. */
+  HdTaskContext task_context;
+  HdTaskSharedPtrVector tasks;
+  tasks.push_back(render_index->GetTask(task_path));
+
+  render_index->SyncAll(&tasks, &task_context);
+  render_delegate.CommitResources(&render_index->GetChangeTracker());
+
+  /* Use first camera in stage.
+   * TODO: get camera from UsdRender if available. */
+  if (use_camera) {
+    for (UsdPrim const &prim : stage->Traverse()) {
+      if (prim.IsA<UsdGeomCamera>()) {
+        HdSprim *sprim = render_index->GetSprim(HdPrimTypeTokens->camera, prim.GetPath());
+        if (sprim) {
+          HdCyclesCamera *camera = dynamic_cast<HdCyclesCamera *>(sprim);
+          camera->ApplyCameraSettings(session->scene->camera);
+          break;
+        }
+      }
+    }
+  }
+}
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/file_reader.h b/intern/cycles/hydra/file_reader.h
new file mode 100644
index 00000000000..c3d6f0f3487
--- /dev/null
+++ b/intern/cycles/hydra/file_reader.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright 2011-2022 Blender Foundation */
+
+#pragma once
+
+#include "hydra/config.h"
+
+#include "session/session.h"
+
+HDCYCLES_NAMESPACE_OPEN_SCOPE
+
+class HdCyclesFileReader {
+ public:
+  static void read(Session *session, const char *filepath, const bool use_camera = true);
+};
+
+HDCYCLES_NAMESPACE_CLOSE_SCOPE
diff --git a/intern/cycles/hydra/geometry.inl b/intern/cycles/hydra/geometry.inl
index 3e02a59ea83..6bb9a03f8ef 100644
--- a/intern/cycles/hydra/geometry.inl
+++ b/intern/cycles/hydra/geometry.inl
@@ -192,11 +192,16 @@ void HdCyclesGeometry<Base, CyclesBase>::Finalize(HdRenderParam *renderParam)
   }
 
   const SceneLock lock(renderParam);
+  const bool keep_nodes = static_cast<const HdCyclesSession *>(renderParam)->keep_nodes;
 
-  lock.scene->delete_node(_geom);
+  if (!keep_nodes) {
+    lock.scene->delete_node(_geom);
+  }
   _geom = nullptr;
 
-  lock.scene->delete_nodes(set<Object *>(_instances.begin(), _instances.end()));
+  if (!keep_nodes) {
+    lock.scene->delete_nodes(set<Object *>(_instances.begin(), _instances.end()));
+  }
   _instances.clear();
   _instances.shrink_to_fit();
 }
diff --git a/intern/cycles/hydra/light.cpp b/intern/cycles/hydra/light.cpp
index c0b4b3a3f38..195b0794a75 100644
--- a/intern/cycles/hydra/light.cpp
+++ b/intern/cycles/hydra/light.cpp
@@ -352,8 +352,12 @@ void HdCyclesLight::Finalize(HdRenderParam *renderParam)
   }
 
   const SceneLock lock(renderParam);
+  const bool keep_nodes = static_cast<const HdCyclesSession *>(renderParam)->keep_nodes;
+
+  if (!keep_nodes) {
+    lock.scene->delete_node(_light);
+  }
 
-  lock.scene->delete_node(_light);
   _light = nullptr;
 }
 
diff --git a/intern/cycles/hydra/material.cpp b/intern/cycles/hydra/material.cpp
index a20f6578270..9406e20cef9 100644
--- a/intern/cycles/hydra/material.cpp
+++ b/intern/cycles/hydra/material.cpp
@@ -562,10 +562,13 @@ void HdCyclesMaterial::Finalize(HdRenderParam *renderParam)
   }
 
   const SceneLock lock(renderParam);
+  const bool keep_nodes = static_cast<const HdCyclesSession *>(renderParam)->keep_nodes;
 
   _nodes.clear();
 
-  lock.scene->delete_node(_shader);
+  if (!keep_nodes) {
+    lock.scene->delete_node(_shader);
+  }
   _shader = nullptr;
 }
 
diff --git a/intern/cycles/hydra/render_delegate.cpp b/intern/cycles/hydra/render_delegate.cpp
index 9ae0241260c..8671ac742be 100644
--- a/intern/cycles/hydra/render_delegate.cpp
+++ b/intern/cycles/hydra/render_delegate.cpp
@@ -119,10 +119,12 @@ SessionParams GetSessionParams(const HdRenderSettingsMap &settings)
 
 }  // namespace
 
-HdCyclesDelegate::HdCyclesDelegate(const HdRenderSettingsMap &settingsMap, Session *session_)
+HdCyclesDelegate::HdCyclesDelegate(const HdRenderSettingsMap &settingsMap,
+                                   Session *session_,
+                                   const bool keep_nodes)
     : HdRenderDelegate()
 {
-  _renderParam = session_ ? std::make_unique<HdCyclesSession>(session_) :
+  _renderParam = session_ ? std::make_unique<HdCyclesSession>(session_, keep_nodes) :
                             std::make_unique<HdCyclesSession>(GetSessionParams(settingsMap));
 
   // If the delegate owns the session, pull any remaining settings
diff --git a/intern/cycles/hydra/render_delegate.h b/intern/cycles/hydra/render_delegate.h
index 9c15c8d5281..7c5ee22e9d6 100644
--- a/intern/cycles/hydra/render_delegate.h
+++ b/intern/cycles/hydra/render_delegate.h
@@ -14,7 +14,8 @@ HDCYCLES_NAMESPACE_OPEN_SCOPE
 class HdCyclesDelegate final : public PXR_NS::HdRenderDelegate {
  public:
   HdCyclesDelegate(const PXR_NS::HdRenderSettingsMap &settingsMap,
-                   CCL_NS::Session *session_ = nullptr);
+                   CCL_NS::Session *session_ = nullptr,
+                   const bool keep_nodes = false);
   ~HdCyclesDelegate() override;
 
   void SetDrivers(const PXR_NS::HdDriverVector &drivers) override;
diff --git a/intern/cycles/hydra/session.cpp b/intern/cycles/hydra/session.cpp
index f6865bdedd1..91853dc1104 100644
--- a/intern/cycles/hydra/session.cpp
+++ b/intern/cycles/hydra/session.cpp
@@ -36,12 +36,13 @@ SceneLock::~SceneLock()
 {
 }
 
-HdCyclesSession::HdCyclesSession(Session *session_) : session(session_), _ownCyclesSession(false)
+HdCyclesSession::HdCyclesSession(Session *session_, const bool keep_nodes)
+    : session(session_), keep_nodes(true), _ownCyclesSession(false)
 {
 }
 
 HdCyclesSession::HdCyclesSession(const SessionParams &params)
-    : session(new Session(params, SceneParams())), _ownCyclesSession(true)
+    : session(new Session(params, SceneParams())), keep_nodes(false), _ownCyclesSession(true)
 {
   Scene *const scene = session->scene;
 
diff --git a/intern/cycles/hydra/session.h b/intern/cycles/hydra/session.h
index 8d5553bf6d7..406c55b14cb 100644
--- a/intern/cycles/hydra/session.h
+++ b/intern/cycles/hydra/session.h
@@ -23,7 +23,7 @@ struct SceneLock {
 
 class HdCyclesSession final : public PXR_NS::HdRenderParam {
  public:
-  HdCyclesSession(CCL_NS::Session *session_);
+  HdCyclesSession(CCL_NS::Session *session_, const bool keep_nodes);
   HdCyclesSession(const CCL_NS::SessionParams &params);
   ~HdCyclesSession() override;
 
@@ -59,6 +59,7 @@ class HdCyclesSession final : public PXR_NS::HdRenderParam {
   void RemoveAovBinding(PXR_NS::HdRenderBuffer *renderBuffer);
 
   CCL_NS::Session *session;
+  bool keep_nodes;
 
  private:
   const bool _ownCyclesSession;
-- 
GitLab