diff --git a/intern/cycles/app/cycles_standalone.cpp b/intern/cycles/app/cycles_standalone.cpp
index 87ab84eb20925a668dabfea82f639e3c690c366f..8b40adc8d9259c134064716ad009e18b735fc993 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 17a7f897ac69c8fb6ac1075ba34cae98d5719394..aa194fb936e625dfa2866ae12f142ea15bf2d677 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 0000000000000000000000000000000000000000..329cc959ac3388f45359985224d229e3f626c670
--- /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 0000000000000000000000000000000000000000..c3d6f0f34875d5735bad08bf2646286f74624c42
--- /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 3e02a59ea83ca4b559829687c5fc92c6de799ad4..6bb9a03f8ef5a27963ccb8e8acfcb7cb49079748 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 c0b4b3a3f3839705cbbd585ff1d523a200baa57e..195b0794a7586e9a6a4a79340b24ab7721486da7 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 a20f6578270d3c19720db9385d0bc4bd9caa5e76..9406e20cef9ffafa7f1628442a2de01e3d8c96fc 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 9ae0241260c9456e8c33861f0aa0d32d1dd9eec1..8671ac742bee8cc69c75e6b6fa0f083783ef99d4 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 9c15c8d5281f455278e89761014a33fb5845d63b..7c5ee22e9d61d11c02fca5c61a2918feee3188e0 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 f6865bdedd14013bd00afe6a7199e442faa83f9f..91853dc1104376fdac6e624e13f1ef011e400b2c 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 8d5553bf6d7aeca4d1612ff99796bc96e408bb44..406c55b14cb7bf3964ce65411ec1a4442607927d 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;