Skip to content

Commit 3e695f9

Browse files
Enable loading a render engine plugin from the static plugin registry (#1152)
* Enable loading a render engine plugin from the static plugin registry Signed-off-by: Shameek Ganguly <shameek@intrinsic.ai>
1 parent 7d311d9 commit 3e695f9

File tree

6 files changed

+256
-18
lines changed

6 files changed

+256
-18
lines changed

ogre/src/OgreRenderEngine.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,3 +878,5 @@ OgreRenderEngine *OgreRenderEngine::Instance()
878878
// Register this plugin
879879
GZ_ADD_PLUGIN(rendering::OgreRenderEnginePlugin,
880880
rendering::RenderEnginePlugin)
881+
GZ_ADD_PLUGIN_ALIAS(rendering::OgreRenderEnginePlugin,
882+
"gz::rendering::ogre::Plugin")

ogre2/src/Ogre2RenderEngine.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1453,3 +1453,5 @@ Ogre2RenderEngine *Ogre2RenderEngine::Instance()
14531453
// Register this plugin
14541454
GZ_ADD_PLUGIN(rendering::Ogre2RenderEnginePlugin,
14551455
rendering::RenderEnginePlugin)
1456+
GZ_ADD_PLUGIN_ALIAS(rendering::Ogre2RenderEnginePlugin,
1457+
"gz::rendering::ogre2::Plugin")

optix/src/OptixRenderEngine.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,5 @@ OptixRenderEngine *OptixRenderEngine::Instance()
143143
// Register this plugin
144144
GZ_ADD_PLUGIN(OptixRenderEnginePlugin,
145145
rendering::RenderEnginePlugin)
146+
GZ_ADD_PLUGIN_ALIAS(OptixRenderEnginePlugin,
147+
"gz::rendering::optix::Plugin")

src/RenderEngineManager.cc

Lines changed: 113 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
*
1616
*/
1717

18+
#include <cstddef>
1819
#include <map>
1920
#include <mutex>
21+
#include <string>
2022

2123
#include <gz/common/Console.hh>
2224
#include <gz/common/SystemPaths.hh>
@@ -48,6 +50,11 @@ class gz::rendering::RenderEngineManagerPrivate
4850
/// \brief EngineMap iterator.
4951
typedef EngineMap::iterator EngineIter;
5052

53+
/// \brief Expected filename prefix for plugin to be loaded from the static
54+
/// registry.
55+
public: static constexpr std::string_view kStaticPluginFilenamePrefix =
56+
"static://";
57+
5158
/// \brief Get a pointer to the render engine from an EngineMap iterator.
5259
/// \param[in] _iter EngineMap iterator
5360
/// \param[in] _path Another search path for rendering engine plugin.
@@ -78,6 +85,11 @@ class gz::rendering::RenderEngineManagerPrivate
7885
public: bool LoadEnginePlugin(const std::string &_filename,
7986
const std::string &_path);
8087

88+
/// \brief Load a render engine plugin from the static plugin registry.
89+
/// \param[in] _filename Filename of plugin
90+
/// \return True if the plugin is loaded successfully
91+
public: bool LoadStaticEnginePlugin(const std::string &_filename);
92+
8193
/// \brief Unload a render engine plugin.
8294
/// \param[in] _engineName Name of engine associated with this plugin
8395
/// \return True if the plugin is unloaded successfully
@@ -431,32 +443,67 @@ bool RenderEngineManagerPrivate::UnloadEngine(EngineIter _iter)
431443
//////////////////////////////////////////////////
432444
void RenderEngineManagerPrivate::RegisterDefaultEngines()
433445
{
434-
// TODO(anyone): Find a cleaner way to get the default engine library name
435446

436-
// cppcheck-suppress unreadVariable
437-
std::string libName = "gz-rendering-";
447+
auto registerStaticOrSolibPlugin = [this](const std::string& _engineName,
448+
const std::string& _staticFilename, const std::string& _solibFilename)
449+
{
450+
std::lock_guard<std::recursive_mutex> lock(this->enginesMutex);
451+
// Check static plugin registry if the plugin was statically linked in.
452+
constexpr size_t prefixLen = kStaticPluginFilenamePrefix.size();
453+
const std::string staticPluginAlias = _staticFilename.substr(prefixLen);
454+
if (!this->pluginLoader.PluginsWithAlias(staticPluginAlias).empty())
455+
{
456+
this->defaultEngines[_engineName] = _staticFilename;
457+
if (this->engines.find(_staticFilename) == this->engines.end())
458+
this->engines[_staticFilename] = nullptr;
459+
return;
460+
}
461+
// Else if a .so lib plugin file is expected, register that as the default.
462+
if (!_solibFilename.empty())
463+
{
464+
this->defaultEngines[_engineName] = _solibFilename;
465+
if (this->engines.find(_solibFilename) == this->engines.end())
466+
this->engines[_solibFilename] = nullptr;
467+
}
468+
};
438469

439-
// cppcheck-suppress unusedVariable
440-
std::string engineName;
470+
// TODO(anyone): Find a cleaner way to get the default engine .so library name
471+
// cppcheck-suppress unreadVariable
472+
const std::string libNamePrefix = "gz-rendering-";
441473

442-
std::lock_guard<std::recursive_mutex> lock(this->enginesMutex);
474+
// Register Ogre
475+
const std::string ogreEngineName = "ogre";
476+
const std::string ogreStaticFilename = "static://gz::rendering::ogre::Plugin";
443477
#if GZ_RENDERING_HAVE_OGRE
444-
engineName = "ogre";
445-
this->defaultEngines[engineName] = libName + engineName;
446-
if (this->engines.find(libName + engineName) == this->engines.end())
447-
this->engines[libName + engineName] = nullptr;
478+
registerStaticOrSolibPlugin(ogreEngineName, ogreStaticFilename,
479+
libNamePrefix + ogreEngineName);
480+
#else
481+
registerStaticOrSolibPlugin(ogreEngineName, ogreStaticFilename,
482+
/*_solibFilename=*/"");
448483
#endif
484+
485+
// Register Ogre2
486+
const std::string ogre2EngineName = "ogre2";
487+
const std::string ogre2StaticFilename =
488+
"static://gz::rendering::ogre2::Plugin";
449489
#if GZ_RENDERING_HAVE_OGRE2
450-
engineName = "ogre2";
451-
this->defaultEngines[engineName] = libName + engineName;
452-
if (this->engines.find(libName + engineName) == this->engines.end())
453-
this->engines[libName + engineName] = nullptr;
490+
registerStaticOrSolibPlugin(ogre2EngineName, ogre2StaticFilename,
491+
libNamePrefix + ogre2EngineName);
492+
#else
493+
registerStaticOrSolibPlugin(ogre2EngineName, ogre2StaticFilename,
494+
/*_solibFilename=*/"");
454495
#endif
496+
497+
// Register Optix
498+
const std::string optixEngineName = "optix";
499+
const std::string optixStaticFilename =
500+
"static://gz::rendering::optix::Plugin";
455501
#if GZ_RENDERING_HAVE_OPTIX
456-
engineName = "optix";
457-
this->defaultEngines[engineName] = libName + engineName;
458-
if (this->engines.find(libName + engineName) == this->engines.end())
459-
this->engines[libName + engineName] = nullptr;
502+
registerStaticOrSolibPlugin(optixEngineName, optixStaticFilename,
503+
libNamePrefix + optixEngineName);
504+
#else
505+
registerStaticOrSolibPlugin(optixEngineName, optixStaticFilename,
506+
/*_solibFilename=*/"");
460507
#endif
461508
}
462509

@@ -466,6 +513,12 @@ bool RenderEngineManagerPrivate::LoadEnginePlugin(
466513
{
467514
gzmsg << "Loading plugin [" << _filename << "]" << std::endl;
468515

516+
constexpr size_t prefixLen = kStaticPluginFilenamePrefix.size();
517+
if (_filename.substr(0, prefixLen) == kStaticPluginFilenamePrefix)
518+
{
519+
return this->LoadStaticEnginePlugin(_filename);
520+
}
521+
469522
gz::common::SystemPaths systemPaths;
470523
systemPaths.SetPluginPathEnv(this->pluginPathEnv);
471524

@@ -560,10 +613,52 @@ bool RenderEngineManagerPrivate::LoadEnginePlugin(
560613
return true;
561614
}
562615

616+
//////////////////////////////////////////////////
617+
bool RenderEngineManagerPrivate::LoadStaticEnginePlugin(
618+
const std::string &_filename)
619+
{
620+
constexpr size_t prefixLen = kStaticPluginFilenamePrefix.size();
621+
const std::string filenameWoPrefix = _filename.substr(prefixLen);
622+
623+
gzmsg << "Loading plugin [" << filenameWoPrefix << "] from static registry"
624+
<< std::endl;
625+
626+
auto plugin = this->pluginLoader.Instantiate(filenameWoPrefix);
627+
if (!plugin)
628+
{
629+
gzerr << "Failed to load static render engine plugin [" << filenameWoPrefix
630+
<< "]. Static plugin registry does not contain this plugin."
631+
<< std::endl;
632+
return false;
633+
}
634+
635+
auto renderPlugin = plugin->QueryInterface<rendering::RenderEnginePlugin>();
636+
if (!renderPlugin)
637+
{
638+
gzerr << "Failed to find RenderEnginePlugin interface in static render "
639+
<< "engine plugin [" << _filename << "]" << std::endl;
640+
return false;
641+
}
642+
643+
// Instantiate the engine
644+
{
645+
std::lock_guard<std::recursive_mutex> lock(this->enginesMutex);
646+
this->engines[_filename] = renderPlugin->Engine();
647+
}
648+
649+
return true;
650+
}
651+
563652
//////////////////////////////////////////////////
564653
bool RenderEngineManagerPrivate::UnloadEnginePlugin(
565654
const std::string &_engineName)
566655
{
656+
const size_t prefix_len = kStaticPluginFilenamePrefix.length();
657+
if (_engineName.substr(0, prefix_len) == kStaticPluginFilenamePrefix)
658+
{
659+
return true;
660+
}
661+
567662
auto it = this->enginePlugins.find(_engineName);
568663
if (it == this->enginePlugins.end())
569664
{

test/BUILD.bazel

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package(
2+
default_applicable_licenses = ["//:license"],
3+
features = [
4+
"layering_check",
5+
"parse_headers",
6+
],
7+
)
8+
9+
cc_test(
10+
name = "INTEGRATION_load_static_render_engine_plugin",
11+
srcs = ["integration/load_static_render_engine_plugin.cc"],
12+
env = {"GZ_BAZEL": "1"},
13+
deps = [
14+
"//:gz-rendering",
15+
"@googletest//:gtest",
16+
"@googletest//:gtest_main",
17+
"@gz-common",
18+
"@gz-plugin//:loader",
19+
"@gz-plugin//:register",
20+
],
21+
)
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright (C) 2025 Open Source Robotics Foundation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
#include <memory>
19+
#include <string>
20+
21+
#include <gtest/gtest.h>
22+
23+
#include <gz/common/Console.hh>
24+
25+
#include <gz/plugin/Loader.hh>
26+
#include <gz/plugin/RegisterStatic.hh>
27+
28+
#include <gz/rendering/base/BaseRenderEngine.hh>
29+
#include <gz/rendering/base/BaseScene.hh>
30+
#include <gz/rendering/base/BaseStorage.hh>
31+
#include <gz/rendering/RenderingIface.hh>
32+
#include <gz/rendering/RenderEnginePlugin.hh>
33+
34+
using namespace gz;
35+
using namespace rendering;
36+
37+
namespace {
38+
// Dummy scene class used in the render engine below.
39+
class DummyScene final: public BaseScene
40+
{};
41+
42+
// Dummy singleton render engine used in the render engine plugin below.
43+
class DummyRenderEngine final: public BaseRenderEngine
44+
{
45+
public: typedef BaseSceneStore<DummyScene> DummySceneStore;
46+
public: typedef std::shared_ptr<DummySceneStore> DummySceneStorePtr;
47+
48+
public: static DummyRenderEngine* Instance();
49+
50+
private: DummyRenderEngine();
51+
52+
public: std::string Name() const override {return "test_engine";}
53+
54+
protected: bool LoadImpl(const std::map<std::string,
55+
std::string> &_params) override {return true;}
56+
57+
protected: bool InitImpl() override {return true;}
58+
59+
protected: ScenePtr CreateSceneImpl(unsigned int _id,
60+
const std::string &_name) override {return nullptr;}
61+
62+
protected: SceneStorePtr Scenes() const override {return this->sceneStore; }
63+
64+
private: DummySceneStorePtr sceneStore;
65+
};
66+
67+
DummyRenderEngine* DummyRenderEngine::Instance()
68+
{
69+
static DummyRenderEngine engine;
70+
return &engine;
71+
}
72+
73+
DummyRenderEngine::DummyRenderEngine()
74+
: sceneStore(std::make_shared<DummySceneStore>())
75+
{}
76+
77+
// Dummy render engine plugin used in the test below. Note that the plugin is
78+
// registered with the static plugin registry at the bottom of this file.
79+
class DummyRenderEnginePlugin final : public RenderEnginePlugin
80+
{
81+
public: DummyRenderEnginePlugin();
82+
83+
public: std::string Name() const override {return engine->Name();}
84+
85+
public: RenderEngine *Engine() const override {return engine;}
86+
87+
private: DummyRenderEngine* engine;
88+
};
89+
90+
DummyRenderEnginePlugin::DummyRenderEnginePlugin()
91+
: engine(DummyRenderEngine::Instance())
92+
{}
93+
94+
// Test that the dummy render engine plugin can be loaded from the static plugin
95+
// registry.
96+
TEST(LoadStaticRenderEnginePlugin, LoadUnloadWorks)
97+
{
98+
plugin::Loader pluginLoader;
99+
const std::string pluginName =
100+
pluginLoader.LookupPlugin("DummyRenderEnginePlugin");
101+
EXPECT_FALSE(pluginName.empty());
102+
103+
const std::string engineFilename = "static://DummyRenderEnginePlugin";
104+
RenderEngine *engine = rendering::engine(engineFilename);
105+
EXPECT_NE(nullptr, engine);
106+
107+
EXPECT_EQ(DummyRenderEngine::Instance(), engine);
108+
109+
EXPECT_TRUE(rendering::unloadEngine(engineFilename));
110+
}
111+
112+
// Register the plugin with the static registry
113+
GZ_ADD_STATIC_PLUGIN(DummyRenderEnginePlugin, RenderEnginePlugin)
114+
GZ_ADD_STATIC_PLUGIN_ALIAS(DummyRenderEnginePlugin, "DummyRenderEnginePlugin")
115+
116+
} // namespace

0 commit comments

Comments
 (0)